Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
2 /**
3 * A test case spec is an array of objects of the following kind:
4 * { 'match': Num|Str|Null,
5 * 'body': Num|Str|Null,
6 * 'fallthrough': Boolean }
7 *
8 * If the 'match' is null, then it represents a 'default:'
9 * If the 'match' is not null, it represents a 'case X:' where X is the value.
10 * If the 'body' is null, then it means that the case body is empty. Otherwise,
11 * it means that the case body is a single 'arr.push(V);' where "arr" is an input
12 * array to the function containing the switch statement, and V is the value.
13 * If 'fallthrough' is false, then the body is terminated with a break, otherwise
14 * it is not.
15 *
16 * So a spec: [{'match':3, 'body':null, 'fallthrough':false}, {'match':null, 'body':"foo", 'fallthrough':true}]
17 * Represents a switch function:
18 * function(x, arr) {
19 * switch(x) {
20 * case 3:
21 * break;
22 * default:
23 * arr.push('foo');
24 * }
25 * }
26 *
27 * GenerateSpecPermutes generates a bunch of relevant specs, using the given case match-values,
28 * and appends them to result the array passed into it.
29 *
30 * InterpretSwitch takes a spec, a value, and a result array, and behaves as if the switch specified
31 * by the spec had been called on the value and the result array.
32 *
33 * VerifySwitchSpec is there but not used in the code. I was using it while testing the test case
34 * generator. It verifies that a switch spec is sane.
35 *
36 * RunSpec uses eval to run the test directly. It's not used currently.
37 *
38 * GenerateSwitchCode generates a string of the form "function NAME(x, arg) { .... }" which
39 * contains the switch modeled by its input spec.
40 *
41 * RunTest is there to be used from within the generated script. Its code is dumped out
42 * to the generated script text, and invoked there.
43 *
44 * Hope all of this makes some sort of sense.
45 * -kannan
46 */
48 /** HELPERS **/
50 function ASSERT(cond, msg) { assertEq(cond, true, msg); }
52 function IsUndef(x) { return typeof(x) == 'undefined'; }
53 function IsNull(x) { return typeof(x) == 'object' && x == null; }
54 function IsNum(x) { return typeof(x) == 'number'; }
55 function IsStr(x) { return typeof(x) == 'string'; }
56 function IsBool(x) { return typeof(x) == 'boolean'; }
58 function Repr(x) {
59 ASSERT(IsNum(x) || IsStr(x), "Repr");
60 if(IsNum(x)) { return ""+x; }
61 else { return "'"+x+"'"; }
62 }
64 function RandBool() { return Math.random() >= 0.5; }
65 function RandInt(max) {
66 if(IsUndef(max)) { max = 0x7fffffff; }
67 return (Math.random() * max)|0;
68 }
70 var CHARS = "abcdefghijklmnopqrstuvywxyzABCDEFGHIJKLMNOPQRSTUVYWXYZ0123456789~!@#$%^&*()-_=+{}[]";
71 function RandStr() {
72 var arr = [];
73 var len = Math.floor(Math.random() * 10) + 1;
74 for(var i = 0; i < len; i++) {
75 var c = Math.floor(Math.random() * CHARS.length);
76 arr.push(CHARS[c]);
77 }
78 return arr.join('');
79 }
81 function RandVal() { return RandBool() ? RandInt() : RandStr(); }
83 /**
84 * Compare two arrays and ensure they are equal.
85 */
86 function ArraysEqual(arr1, arr2) {
87 ASSERT(arr1.length == arr2.length, "Lengths not equal");
88 for(var i = 0; i < arr1.length; i++) {
89 ASSERT(typeof(arr1[i]) == typeof(arr2[i]), "Types not equal for position " + i);
90 ASSERT(arr1[i] == arr2[i], "Values not equal for position " + i);
91 }
92 }
94 function VerifySwitchSpec(spec) {
95 var foundDefault = undefined;
96 for(var i = 0; i < spec.length; i++) {
97 var caseSpec = spec[i],
98 match = caseSpec.match,
99 body = caseSpec.body,
100 fallthrough = caseSpec.fallthrough;
101 ASSERT(IsNum(match) || IsStr(match) || IsNull(match), "Invalid case match for " + i);
102 ASSERT(IsNum(body) || IsStr(body) || IsNull(body), "Invalid case body for " + i);
103 ASSERT(IsBool(fallthrough), "Invalid fallthrough for " + i);
105 if(IsNull(match)) {
106 ASSERT(IsUndef(foundDefault), "Duplicate default found at " + i);
107 foundDefault = i;
108 }
109 }
110 }
112 /**
113 * Do a manual interpretation of a particular spec, given an input
114 * and outputting to an output array.
115 */
116 function InterpretSwitch(spec, input, outputArray) {
117 var foundMatch = undefined, foundDefault = undefined;
118 // Go through cases, trying to find a matching clause.
119 for(var i = 0; i < spec.length; i++) {
120 var caseSpec = spec[i], match = caseSpec.match;
122 if(IsNull(match)) {
123 foundDefault = i;
124 continue;
125 } else if(match === input) {
126 foundMatch = i;
127 break;
128 }
129 }
130 // Select either matching clause or default.
131 var matchI = IsNum(foundMatch) ? foundMatch : foundDefault;
133 // If match or default was found, interpret body from that point on.
134 if(IsNum(matchI)) {
135 for(var i = matchI; i < spec.length; i++) {
136 var caseSpec = spec[i],
137 match = caseSpec.match,
138 body = caseSpec.body,
139 fallthrough = caseSpec.fallthrough;
140 if(!IsNull(body)) { outputArray.push(body); }
141 if(!fallthrough) { break; }
142 }
143 }
144 }
146 /**
147 * Generate the code string for a javascript function containing the
148 * switch specified by the spec, in pure JS syntax.
149 */
150 function GenerateSwitchCode(spec, name) {
151 var lines = [];
152 if(!name) { name = ""; }
154 lines.push("function "+name+"(x, arr) {");
155 lines.push(" switch(x) {");
156 for(var i = 0; i < spec.length; i++) {
157 var caseSpec = spec[i],
158 match = caseSpec.match,
159 body = caseSpec.body,
160 fallthrough = caseSpec.fallthrough;
162 if(IsNull(match)) { lines.push(" default:"); }
163 else { lines.push(" case "+Repr(match)+":"); }
165 if(!IsNull(body)) { lines.push(" arr.push("+Repr(body)+");"); }
166 if(!fallthrough) { lines.push(" break;"); }
167 }
168 lines.push(" }");
169 lines.push("}");
170 return lines.join("\n");
171 }
173 /**
174 * Generates all possible specs for a given set of case match values.
175 */
176 function GenerateSpecPermutes(matchVals, resultArray) {
177 ASSERT((0 < matchVals.length) && (matchVals.length <= 5), "Invalid matchVals");
178 var maxPermuteBody = (1 << matchVals.length) - 1;
179 for(var bod_pm = 0; bod_pm <= maxPermuteBody; bod_pm++) {
180 var maxPermuteFallthrough = (1 << matchVals.length) - 1;
182 for(var ft_pm = 0; ft_pm <= maxPermuteFallthrough; ft_pm++) {
183 // use bod_m and ft_pm to decide the placement of null vs value bodies,
184 // and the placement of fallthroughs vs breaks.
185 // Empty bodies always fall through, so fallthrough bitmask 1s must be
186 // a subset of the body bitmask 1s.
187 if((bod_pm | ft_pm) != bod_pm) {
188 continue;
189 }
191 var spec = [];
192 for(var k = 0; k < matchVals.length; k++) {
193 var match = matchVals[k];
194 var body = ((bod_pm & (1 << k)) > 0) ? null : RandVal();
195 var fallthrough = ((ft_pm & (1 << k)) > 0) ? true : false;
196 var caseSpec = {'match':match, 'body':body, 'fallthrough':fallthrough};
197 spec.push(caseSpec);
198 }
200 // Variant specs for following cases:
202 // Default with empty body, fallthrough
203 GenerateDefaultPermutes(spec, null, true, resultArray);
204 // Default with nonempty body, fallthrough
205 GenerateDefaultPermutes(spec, RandVal(), true, resultArray);
206 // Default with nonempty body, break
207 GenerateDefaultPermutes(spec, RandVal(), false, resultArray);
208 }
209 }
210 }
211 function GenerateDefaultPermutes(spec, body, fallthrough, resultArray) {
212 if(spec.length <= 2) {
213 for(var i = 0; i <= spec.length; i++) {
214 var copy = CopySpec(spec);
215 if(IsNull(body)) {
216 copy.splice(i,0,{'match':null, 'body':null, 'fallthrough':true});
217 } else {
218 copy.splice(i,0,{'match':null, 'body':body, 'fallthrough':fallthrough});
219 }
220 resultArray.push(copy);
221 }
222 } else {
223 var posns = [0, Math.floor(spec.length / 2), spec.length];
224 posns.forEach(function (i) {
225 var copy = CopySpec(spec);
226 if(IsNull(body)) {
227 copy.splice(i,0,{'match':null, 'body':null, 'fallthrough':true});
228 } else {
229 copy.splice(i,0,{'match':null, 'body':body, 'fallthrough':fallthrough});
230 }
231 resultArray.push(copy);
232 });
233 }
234 }
235 function CopySpec(spec) {
236 var newSpec = [];
237 for(var i = 0; i < spec.length; i++) {
238 var caseSpec = spec[i];
239 newSpec.push({'match':caseSpec.match,
240 'body':caseSpec.body,
241 'fallthrough':caseSpec.fallthrough});
242 }
243 return newSpec;
244 }
247 function RunSpec(spec, matchVals) {
248 var code = GenerateSwitchCode(spec);
250 // Generate roughly 200 inputs for the test case spec, exercising
251 // every match value, as well as 3 randomly generated values for every
252 // iteration of the match values.
253 var inputs = [];
254 while(inputs.length < 500) {
255 for(var i = 0; i < matchVals.length; i++) { inputs.push(matchVals[i]); }
256 for(var i = 0; i < 3; i++) { inputs.push(RandVal()); }
257 }
259 // Interpret the lookupswitch with the inputs.
260 var interpResults = [];
261 for(var i = 0; i < inputs.length; i++) {
262 InterpretSwitch(spec, inputs[i], interpResults);
263 }
265 // Run compiled lookupswitch with the inputs.
266 var fn = eval("_ = " + code);
267 print("Running spec: " + code);
268 var compileResults = RunCompiled(fn, inputs);
269 print("Done Running spec");
271 // Verify that they produce the same output.
272 ASSERT(interpResults.length == compileResults.length, "Length mismatch");
273 for(var i = 0; i < interpResults.length; i++) {
274 ASSERT(interpResults[i] == compileResults[i], "Value mismatch");
275 }
276 }
277 function RunCompiled(fn, inputs) {
278 var results = [];
279 var len = inputs.length;
280 for(var i = 0; i < len; i++) { fn(inputs[i], results); }
281 return results;
282 }
284 function PrintSpec(spec, inputs, fname) {
285 var code = GenerateSwitchCode(spec, fname);
286 var input_s = fname + ".INPUTS = [" + inputs.map(Repr).join(', ') + "];";
287 var spec_s = fname + ".SPEC = " + JSON.stringify(spec) + ";";
288 print(code + "\n" + input_s + "\n" + spec_s);
289 }
291 function RunTest(test) {
292 // Exercise every test case as well as one case which won't match with any of the
293 // ("But what if it randomly generates a string case match whose value is
294 // UNMATCHED_CASE?!", you ask incredulously. Well, RandStr limits itself to 11 chars.
295 // So there.)
296 var inputs = test.INPUTS;
297 inputs.push("UNMATCHED_CASE");
298 var spec = test.SPEC;
300 var results1 = [];
301 for(var i = 0; i < 80; i++) {
302 for(var j = 0; j < inputs.length; j++) {
303 test(inputs[j], results1);
304 }
305 }
307 var results2 = [];
308 for(var i = 0; i < 80; i++) {
309 for(var j = 0; j < inputs.length; j++) {
310 InterpretSwitch(spec, inputs[j], results2);
311 }
312 }
313 ArraysEqual(results1, results2);
314 }
316 // NOTES:
317 // * RunTest is used within the generated test script.
318 // * InterpretSwitch is printed out into the generated test script.
320 print("/////////////////////////////////////////");
321 print("// This is a generated file!");
322 print("// See jit-tests/etc/generate-lookupswitch-tests.js for the code");
323 print("// that generated this code!");
324 print("/////////////////////////////////////////");
325 print("");
326 print("/////////////////////////////////////////");
327 print("// PRELUDE //");
328 print("/////////////////////////////////////////");
329 print("");
330 print("// Avoid eager compilation of the global-scope.");
331 print("try{} catch (x) {};");
332 print("");
333 print(ASSERT);
334 print(IsNull);
335 print(IsNum);
336 print(ArraysEqual);
337 print(InterpretSwitch);
338 print(RunTest);
339 print("");
340 print("/////////////////////////////////////////");
341 print("// TEST CASES //");
342 print("/////////////////////////////////////////");
343 print("");
344 print("var TESTS = [];");
345 var MATCH_SETS = [["foo", "bar", "zing"]];
346 var count = 0;
347 for(var i = 0; i < MATCH_SETS.length; i++) {
348 var matchSet = MATCH_SETS[i];
349 var specs = [];
350 GenerateSpecPermutes(matchSet, specs);
351 for(var j = 0; j < specs.length; j++) {
352 count++;
353 PrintSpec(specs[j], matchSet.slice(), 'test_'+count);
354 print("TESTS.push(test_"+count+");\n");
355 }
356 }
358 print("");
359 print("/////////////////////////////////////////");
360 print("// RUNNER //");
361 print("/////////////////////////////////////////");
362 print("");
363 print("for(var i = 0; i < TESTS.length; i++) {");
364 print(" RunTest(TESTS[i]);");
365 print("}");