census.js (5403B)
1 // Functions for checking results returned by Debugger.Memory.prototype.takeCensus. 2 3 const Census = {}; 4 5 (function () { 6 7 // Census.walkCensus(subject, name, walker[, ignore]) 8 // 9 // Use |walker| to check |subject|, a census object of the sort returned by 10 // Debugger.Memory.prototype.takeCensus: a tree of objects with integers at the 11 // leaves. Use |name| as the name for |subject| in diagnostic messages. Return 12 // the number of leaves of |subject| we visited. 13 // 14 // A walker is an object with three methods: 15 // 16 // - enter(prop): Return the walker we should use to check the property of the 17 // subject census named |prop|. This is for recursing into the subobjects of 18 // the subject. 19 // 20 // - done(ignore): Called after we have called 'enter' on every property of 21 // the subject. Passed the |ignore| set of properties. 22 // 23 // - check(value): Check |value|, a leaf in the subject. 24 // 25 // Walker methods are expected to simply throw if a node we visit doesn't look 26 // right. 27 // 28 // The optional |ignore| parameter allows you to specify a |Set| of property 29 // names which should be ignored. The walker will not traverse such 30 // properties. 31 Census.walkCensus = (subject, name, walker, ignore = new Set()) => 32 walk(subject, name, walker, ignore, 0); 33 34 function walk(subject, name, walker, ignore, count) { 35 if (typeof subject === 'object') { 36 print(name); 37 for (let prop in subject) { 38 if (ignore.has(prop)) { 39 continue; 40 } 41 count = walk(subject[prop], 42 name + "[" + JSON.stringify(prop) + "]", 43 walker.enter(prop), 44 ignore, 45 count); 46 } 47 walker.done(ignore); 48 } else { 49 print(name + " = " + JSON.stringify(subject)); 50 walker.check(subject); 51 count++; 52 } 53 54 return count; 55 } 56 57 // A walker that doesn't check anything. 58 Census.walkAnything = { 59 enter: () => Census.walkAnything, 60 done: () => undefined, 61 check: () => undefined 62 }; 63 64 // A walker that requires all leaves to be zeros. 65 Census.assertAllZeros = { 66 enter: () => Census.assertAllZeros, 67 done: () => undefined, 68 check: elt => assertEq(elt, 0) 69 }; 70 71 function expectedObject() { 72 throw "Census mismatch: subject has leaf where basis has nested object"; 73 } 74 75 function expectedLeaf() { 76 throw "Census mismatch: subject has nested object where basis has leaf"; 77 } 78 79 // Return a function that, given a 'basis' census, returns a census walker that 80 // compares the subject census against the basis. The returned walker calls the 81 // given |compare|, |missing|, and |extra| functions as follows: 82 // 83 // - compare(subjectLeaf, basisLeaf): Check a leaf of the subject against the 84 // corresponding leaf of the basis. 85 // 86 // - missing(prop, value): Called when the subject is missing a property named 87 // |prop| which is present in the basis with value |value|. 88 // 89 // - extra(prop): Called when the subject has a property named |prop|, but the 90 // basis has no such property. This should return a walker that can check 91 // the subject's value. 92 function makeBasisChecker({compare, missing, extra}) { 93 return function makeWalker(basis) { 94 if (typeof basis === 'object') { 95 var unvisited = new Set(Object.getOwnPropertyNames(basis)); 96 return { 97 enter: prop => { 98 unvisited.delete(prop); 99 if (prop in basis) { 100 return makeWalker(basis[prop]); 101 } else { 102 return extra(prop); 103 } 104 }, 105 106 done: ignore => [...unvisited].filter(p => !ignore.has(p)).forEach(p => missing(p, basis[p])), 107 check: expectedObject 108 }; 109 } else { 110 return { 111 enter: expectedLeaf, 112 done: expectedLeaf, 113 check: elt => compare(elt, basis) 114 }; 115 } 116 }; 117 } 118 119 function missingProp(prop) { 120 throw "Census mismatch: subject lacks property present in basis: " + JSON.stringify(prop); 121 } 122 123 function extraProp(prop) { 124 throw "Census mismatch: subject has property not present in basis: " + JSON.stringify(prop); 125 } 126 127 // Return a walker that checks that the subject census has counts all equal to 128 // |basis|. 129 Census.assertAllEqual = makeBasisChecker({ 130 compare: assertEq, 131 missing: missingProp, 132 extra: extraProp 133 }); 134 135 // Return a walker that checks that the subject census has at least as many 136 // items of each category as |basis|. 137 Census.assertAllNotLessThan = makeBasisChecker({ 138 compare: (subject, basis) => assertEq(subject >= basis, true), 139 missing: missingProp, 140 extra: () => Census.walkAnything 141 }); 142 143 // Return a walker that checks that the subject census has at most as many 144 // items of each category as |basis|. 145 Census.assertAllNotMoreThan = makeBasisChecker({ 146 compare: (subject, basis) => assertEq(subject <= basis, true), 147 missing: missingProp, 148 extra: () => Census.walkAnything 149 }); 150 151 // Return a walker that checks that the subject census has within |fudge| 152 // items of each category of the count in |basis|. 153 Census.assertAllWithin = function (fudge, basis) { 154 return makeBasisChecker({ 155 compare: (subject, basis) => assertEq(Math.abs(subject - basis) <= fudge, true), 156 missing: missingProp, 157 extra: () => Census.walkAnything 158 })(basis); 159 } 160 161 })();