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