Skip to content

Commit 4a58229

Browse files
committed
add garbage-counting tests for long/deep chains
Inspecting the object reference graph with a simple DFS through properties is possible as Creed does not use any closures for its internals The tests are currently failing :-)
1 parent 0100f8f commit 4a58229

File tree

2 files changed

+217
-0
lines changed

2 files changed

+217
-0
lines changed

test/garbage-test.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { describe, it } from 'mocha'
2+
import assert from 'assert'
3+
4+
import { resolve, reject, fulfill, never } from '../src/main'
5+
import getCounts, { collect, formatResults, sumResults } from './lib/refcount-util'
6+
7+
describe('reference counts', () => {
8+
const C = 3
9+
const len = C*C*C
10+
it('should be constant for recursive resolving with no handler', () => {
11+
return getCounts(delay => {
12+
// return delay(1).chain( ()=>delay(2).chain( ()=>delay(3) ) )
13+
function recurse(i) {
14+
return i < len ? delay(i+1).chain(recurse) : fulfill()
15+
}
16+
return recurse(1)
17+
}, (na, ev, co) => sumResults(co)).then(logs => { for (let sum of logs) assert(sum <= C+5, sum+"<="+(C+5)) })
18+
})
19+
it('should be constant for recursive resolving with a result', () => {
20+
return getCounts(delay => {
21+
// return delay(1).chain( ()=>delay(2).chain( ()=>delay(3) ) ).map(l => 3-l)
22+
function recurse(i) {
23+
return i < len ? delay(i+1).chain(recurse) : fulfill(i)
24+
}
25+
return recurse(1).map(l => len - l)
26+
}, (na, ev, co) => sumResults(co)).then(logs => { for (let sum of logs) assert(sum <= C+6, sum+"<="+(C+6)) })
27+
})
28+
it('should be constant for recursive resolving with a late handler', () => {
29+
return getCounts((delay, timeout) => {
30+
// p = delay(1).chain( ()=>delay(2).chain( ()=>delay(3) ) )
31+
// largeDelay().then(() => p.map(l => 3-l))
32+
function recurse(i) {
33+
if (i < len) return delay(i+1).chain(recurse)
34+
timeout(i+1, () => p.map(l => len - l))
35+
return fulfill(i)
36+
}
37+
var p = recurse(1)
38+
return p
39+
}, (na, ev, co) => sumResults(co)).then(logs => { for (let sum of logs) assert(sum <= C+5, sum+"<="+(C+5)) })
40+
})
41+
it('should be not more than linear for recursive resolving with many handlers', () => {
42+
return getCounts(delay => {
43+
function recurse(i) {
44+
if (i>1) p.map(l => len - l)
45+
return i < len ? delay(i+1).chain(recurse) : fulfill(i)
46+
}
47+
var p = recurse(1)
48+
return p
49+
}, (na, ev, co) => [na, co.Futures, co.Maps, sumResults(co)]).then(logs => {
50+
for (let [i, f, m, sum] of logs) {
51+
assert(f <= C+i, f+"<="+(C+i)+" Futures") // 1 Future result per handler
52+
assert(m <= C+i, m+"<="+(C+i)+" Maps") // 1 Map action per handler
53+
sum -= f + m
54+
assert(sum <= C+5, sum+"<="+(C+5)+" others")
55+
}
56+
})
57+
})
58+
it('should be constant for recursive resolving with many late handlers', () => {
59+
return getCounts((delay, timeout) => {
60+
// p = delay(1).chain( ()=>delay(2).chain( ()=>delay(3) ) )
61+
// largeDelay().then(() => { p.map(l => 3-l); p.map(l => 3-l); p.map(l => 3-l); })
62+
function recurse(i) {
63+
if (i < len) return delay(i+1).chain(recurse)
64+
timeout(i+1, () => { for (var j=0; j<len/2; j++) p.map(l => len - l) }) // dropping the results
65+
return fulfill(i)
66+
}
67+
var p = recurse(1)
68+
return p
69+
}, (na, ev, co) => sumResults(co)).then(logs => { for (let sum of logs) assert(sum <= C+5, sum+"<="+(C+5)) })
70+
})
71+
72+
/*
73+
function log (na, ev, co) {
74+
console.log("\t"+na+" - "+ev+" ["+formatResults(co)+"]")
75+
}
76+
it('should be interesting1', () => {
77+
return getCounts(delay => {
78+
return delay(1).chain(()=>delay(2)).chain(()=>delay(3))
79+
}, log)
80+
})
81+
it('should be interesting2', () => {
82+
return getCounts(delay => {
83+
return delay(1).chain(()=>delay(2).chain(()=>delay(3).chain(()=>delay(4).chain(()=>delay(5)))))
84+
}, log)
85+
})
86+
it('should be interesting3', () => {
87+
return getCounts(delay => {
88+
return delay(delay(delay(0, 1), 2), 3)
89+
}, log)
90+
})
91+
*/
92+
})

test/lib/refcount-util.js

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { resolve, isRejected, isNever } from '../../src/main'
2+
import { Future } from '../../src/Promise'
3+
import Action from '../../src/Action'
4+
5+
const knownNames = ['Handle', 'Action',
6+
'Then', 'Chain', 'Map', 'Delay',
7+
'Future', 'Fulfilled', 'Rejected',
8+
'Settle', 'Merge', 'Any', 'Race']
9+
const DELAY_TIME = 5
10+
11+
export default function getCounts(exec, report, targets = knownNames, withResult = false) {
12+
function asyncReport(x) {
13+
timeoutCount++
14+
setTimeout(() => {
15+
res.push(report(x, 'result after', collect([result], targets, seenBefore)))
16+
if (globals.size > +withResult) res.push(report(x, 'globals after', collect(globals, targets, seenBefore)))
17+
if (--timeoutCount == 0) promise._fulfill(res)
18+
}, 2) // after the TaskQueue is drained
19+
}
20+
function monitoredTimeout(log, fn, captures = []) {
21+
for (let c of captures)
22+
globals.add(c)
23+
timeoutCount++
24+
setTimeout(() => {
25+
for (let c of captures)
26+
globals.delete(c)
27+
if (captures.length) res.push(report(log, 'captures from timeout', collect(captures, targets, seenBefore)))
28+
res.push(report(log, 'result before', collect([result], targets, seenBefore)))
29+
if (globals.size > +withResult) res.push(report(log, 'globals before', collect(globals, targets, seenBefore)))
30+
fn()
31+
asyncReport(log)
32+
if (--timeoutCount == 0) promise._fulfill(res)
33+
}, DELAY_TIME)
34+
}
35+
function monitoredDelay (x, id) {
36+
const p = resolve(x)
37+
if (isRejected(p) || isNever(p)) {
38+
return p
39+
} else {
40+
var promise = new Future()
41+
p._runAction(new Delay(promise, id))
42+
return promise
43+
}
44+
}
45+
class Delay extends Action {
46+
constructor (promise, id) {
47+
super(promise)
48+
this.id = id
49+
}
50+
51+
fulfilled (p) {
52+
monitoredTimeout(this.id || p.value, () => this.promise._become(p), [this.promise, p])
53+
}
54+
}
55+
56+
const promise = new Future
57+
var timeoutCount = 0
58+
const res = []
59+
const seenBefore = new WeakSet
60+
const globals = new Set
61+
const result = exec(monitoredDelay, monitoredTimeout);
62+
if (withResult) {
63+
globals.add(result)
64+
}
65+
asyncReport(0) // 'init'
66+
return promise
67+
}
68+
69+
export function collect(sources, targets = knownNames, seenBefore = new WeakSet) {
70+
var counts = {}
71+
for (let name of targets) {
72+
counts[name+'s'] = 0
73+
counts['new'+name+'s'] = 0
74+
}
75+
var seen = new WeakSet([])
76+
function test(o) {
77+
if (seen.has(o)) {
78+
return
79+
} else {
80+
seen.add(o)
81+
}
82+
83+
const name = o.constructor.name
84+
if (targets.includes(name)) {
85+
counts[name+'s']++
86+
if (!seenBefore.has(o)) {
87+
counts['new'+name+'s']++
88+
}
89+
} else {
90+
console.log('unknown kind:', o)
91+
}
92+
seenBefore.add(o)
93+
94+
for (let property in o) {
95+
let value = o[property]
96+
if (value && typeof value == 'object') { // ignore function objects
97+
test(value)
98+
}
99+
}
100+
}
101+
for (let source of sources) {
102+
test(source)
103+
}
104+
return counts
105+
}
106+
export function formatResults(counts) {
107+
var x = []
108+
for (let name in counts) {
109+
if (counts[name] == 0) continue
110+
if (/^new/.test(name)) continue
111+
let r = name+': '+counts[name]
112+
if (counts['new'+name] > 0)
113+
r += ' (+'+counts['new'+name]+')'
114+
x.push(r)
115+
}
116+
return x.join(', ')
117+
}
118+
119+
export function sumResults(counts) {
120+
var c = 0;
121+
for (let name in counts)
122+
if (!/^new/.test(name))
123+
c += counts[name]
124+
return c
125+
}

0 commit comments

Comments
 (0)