// This test module provides infrastructure for generating and running tests on a binary // operator. // // It works by generating test functions to exercise the specified operator using // from permutations of operand value pairs. For each test, it computes the expected // result by exercising the test function once (using the LLINT) at test generation time. // The test runner later compares the result produced by the function (as it tiers up) // against the expected result. // // The generated tests can exercise the operator using the following operand types. // variable * variable // constant * variable // variable * constant // // If all goes well, this test module will terminate silently. If not, it will print // errors. //============================================================================ // Debugging options: var verbose = false; var abortOnFirstFail = false; var testFilterStr = undefined; // Define a filter string to filter tests to run. var verboseTestGeneration = false; //============================================================================ // Test generation: function stringifyIfNeeded(x) { if (typeof x == "string") return '"' + x + '"'; if (typeof x == "object") return 'objWithVal:' + x; return x; } // operandTypes are "VarVar", "VarConst", and "ConstVar". var funcIndex = 0; function generateBinaryTests(tests, opName, op, operandTypes, leftValues, rightValues) { var funcNamePrefix = opName + operandTypes; for (var i = 0; i < leftValues.length; i++) { for (var j = 0; j < rightValues.length; j++) { var test = { }; xStr = leftValues[i]; yStr = rightValues[j]; test.x = eval(xStr); test.y = eval(yStr); var funcName = funcNamePrefix + funcIndex++; if (operandTypes == "VarVar") { test.signature = funcName + "(x, y) { return x " + op + " y }"; test.name = test.signature + " with x:" + xStr + ", y:" + yStr; } else if (operandTypes == "VarConst") { test.signature = funcName + "(x, _) { return x " + op + " " + yStr + " }"; test.name = test.signature + " with x:" + xStr; } else if (operandTypes == "ConstVar") { test.signature = funcName + "(_, y) { return " + xStr + " " + op + " y }"; test.name = test.signature + " with y:" + yStr; } test.func = eval("(function " + test.signature + ")"); noInline(test.func); test.expectedResult = test.func(test.x, test.y); test.name += ", expected:" + stringifyIfNeeded(test.expectedResult); tests.push(test); if (verboseTestGeneration) print("Generated " + test.name); } } } //============================================================================ // Test running and reporting: var errorReport = ""; function isIdentical(x, y) { if (typeof x == "undefined" && typeof y == "undefined") return true; if (typeof x != typeof y) return false; if (x == y) { if (x) return true; // Distinguish between 0 and negative 0. if (1 / x == 1 / y) return true; } else if (Number.isNaN(x) && Number.isNaN(y)) return true; return false; } function runTest(test) { if (testFilterStr && !test.name.includes(testFilterStr)) return; var firstFailed = -1; try { if (verbose) print(test.name); for (var i = 0; i < 10000; i++) { var result = test.func(test.x, test.y); if (isIdentical(result, test.expectedResult)) continue; if (firstFailed < 0) { errorReport += "FAILED: " + test.name + " started failing on iteration " + i + ": actual " + stringifyIfNeeded(result) + "\n"; if (abortOnFirstFail) throw errorReport; firstFailed = i; } } } catch(e) { if (abortOnFirstFail) throw e; // Negate the catch by re-throwing. errorReport += "FAILED: Unexpected exception: " + e + "\n"; } } function run() { if (verbose) print("Start testing"); for (var test of tests) runTest(test); if (errorReport !== "") throw "Found failures:\n" + errorReport; if (verbose) print("Done testing"); }