tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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("}");