tor-browser

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

test_dmd.js (7706B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 "use strict";
      8 
      9 const { FileUtils } = ChromeUtils.importESModule(
     10  "resource://gre/modules/FileUtils.sys.mjs"
     11 );
     12 
     13 // The xpcshell test harness sets PYTHON so we can read it here.
     14 var gPythonName = Services.env.get("PYTHON");
     15 
     16 const gCwd = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
     17 
     18 function getRelativeFile(...components) {
     19  return new FileUtils.File(PathUtils.join(gCwd.path, ...components));
     20 }
     21 
     22 // If we're testing locally, the executable file is in "CurProcD". Otherwise,
     23 // it is in another location that we have to find.
     24 function getExecutable(aFilename) {
     25  let file = new FileUtils.File(
     26    PathUtils.join(Services.dirsvc.get("CurProcD", Ci.nsIFile).path, aFilename)
     27  );
     28  if (!file.exists()) {
     29    file = gCwd.clone();
     30    while (file.path.includes("xpcshell")) {
     31      file = file.parent;
     32    }
     33    file.append("bin");
     34    file.append(aFilename);
     35  }
     36  return file;
     37 }
     38 
     39 var gIsWindows = Services.appinfo.OS === "WINNT";
     40 var gDmdTestFile = getExecutable("SmokeDMD" + (gIsWindows ? ".exe" : ""));
     41 
     42 var gDmdScriptFile = getExecutable("dmd.py");
     43 
     44 var gScanTestFile = getRelativeFile("scan-test.py");
     45 
     46 function readFile(aFile) {
     47  let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
     48    Ci.nsIFileInputStream
     49  );
     50  let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
     51    Ci.nsIConverterInputStream
     52  );
     53  fstream.init(aFile, -1, 0, 0);
     54  cstream.init(fstream, "UTF-8", 0, 0);
     55 
     56  let data = "";
     57  let str = {};
     58  let read = 0;
     59  do {
     60    // Read as much as we can and put it in str.value.
     61    read = cstream.readString(0xffffffff, str);
     62    data += str.value;
     63  } while (read != 0);
     64 
     65  cstream.close(); // this closes fstream
     66  return data.replace(/\r/g, ""); // normalize line endings
     67 }
     68 
     69 function runProcess(aExeFile, aArgs) {
     70  let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
     71  process.init(aExeFile);
     72  process.run(/* blocking = */ true, aArgs, aArgs.length);
     73  return process.exitValue;
     74 }
     75 
     76 function test(aPrefix, aArgs) {
     77  // DMD writes the JSON files to CurWorkD, so we do likewise here with
     78  // |actualFile| for consistency. It is removed once we've finished.
     79  let expectedFile = getRelativeFile(`${aPrefix}-expected.txt`);
     80  let actualFile = getRelativeFile(`${aPrefix}-actual.txt`);
     81 
     82  // Run dmd.py on the JSON file, producing |actualFile|.
     83 
     84  let args = [
     85    gDmdScriptFile.path,
     86    "--filter-stacks-for-testing",
     87    "-o",
     88    actualFile.path,
     89  ].concat(aArgs);
     90 
     91  runProcess(new FileUtils.File(gPythonName), args);
     92 
     93  // Compare |expectedFile| with |actualFile|. We produce nice diffs with
     94  // /usr/bin/diff on systems that have it (Mac and Linux). Otherwise (Windows)
     95  // we do a string compare of the file contents and then print them both if
     96  // they don't match.
     97 
     98  let success;
     99  try {
    100    let rv = runProcess(new FileUtils.File("/usr/bin/diff"), [
    101      "-u",
    102      expectedFile.path,
    103      actualFile.path,
    104    ]);
    105    success = rv == 0;
    106  } catch (e) {
    107    let expectedData = readFile(expectedFile);
    108    let actualData = readFile(actualFile);
    109    success = expectedData === actualData;
    110    if (!success) {
    111      expectedData = expectedData.split("\n");
    112      actualData = actualData.split("\n");
    113      for (let i = 0; i < expectedData.length; i++) {
    114        print("EXPECTED:" + expectedData[i]);
    115      }
    116      for (let i = 0; i < actualData.length; i++) {
    117        print("  ACTUAL:" + actualData[i]);
    118      }
    119    }
    120  }
    121 
    122  ok(success, aPrefix);
    123 
    124  actualFile.remove(true);
    125 }
    126 
    127 // Run scan-test.py on the JSON file and see if it succeeds.
    128 function scanTest(aJsonFilePath, aExtraArgs) {
    129  let args = [gScanTestFile.path, aJsonFilePath].concat(aExtraArgs);
    130 
    131  return runProcess(new FileUtils.File(gPythonName), args) == 0;
    132 }
    133 
    134 function run_test() {
    135  let jsonFile, jsonFile2;
    136 
    137  // These tests do complete end-to-end testing of DMD, i.e. both the C++ code
    138  // that generates the JSON output, and the script that post-processes that
    139  // output.
    140  //
    141  // Run these synchronously, because test() updates the complete*.json files
    142  // in-place (to fix stacks) when it runs dmd.py, and that's not safe to do
    143  // asynchronously.
    144 
    145  Services.env.set("DMD", "1");
    146 
    147  runProcess(gDmdTestFile, []);
    148 
    149  function test2(aTestName, aMode) {
    150    let name = "complete-" + aTestName + "-" + aMode;
    151    jsonFile = getRelativeFile(`${name}.json`);
    152    test(name, [jsonFile.path]);
    153    jsonFile.remove(true);
    154  }
    155 
    156  // Please keep this in sync with RunTests() in SmokeDMD.cpp.
    157 
    158  test2("empty", "live");
    159  test2("empty", "dark-matter");
    160  test2("empty", "cumulative");
    161 
    162  test2("full1", "live");
    163  test2("full1", "dark-matter");
    164 
    165  test2("full2", "dark-matter");
    166  test2("full2", "cumulative");
    167 
    168  test2("partial", "live");
    169 
    170  // Heap scan testing.
    171  jsonFile = getRelativeFile("basic-scan.json");
    172  ok(scanTest(jsonFile.path), "Basic scan test");
    173 
    174  let is64Bit = Services.appinfo.is64Bit;
    175  let basicScanFileName = "basic-scan-" + (is64Bit ? "64" : "32");
    176  test(basicScanFileName, ["--clamp-contents", jsonFile.path]);
    177  ok(
    178    scanTest(jsonFile.path, ["--clamp-contents"]),
    179    "Scan with address clamping"
    180  );
    181 
    182  // Run the generic test a second time to ensure that the first time produced
    183  // valid JSON output. "--clamp-contents" is passed in so we don't have to have
    184  // more variants of the files.
    185  test(basicScanFileName, ["--clamp-contents", jsonFile.path]);
    186  jsonFile.remove(true);
    187 
    188  // These tests only test the post-processing script. They use hand-written
    189  // JSON files as input. Ideally the JSON files would contain comments
    190  // explaining how they work, but JSON doesn't allow comments, so I've put
    191  // explanations here.
    192 
    193  // This just tests that stack traces of various lengths are truncated
    194  // appropriately. The number of records in the output is different for each
    195  // of the tested values.
    196  jsonFile = getRelativeFile("script-max-frames.json");
    197  test("script-max-frames-8", [jsonFile.path]); // --max-frames=8 is the default
    198  test("script-max-frames-3", [
    199    "--max-frames=3",
    200    "--no-fix-stacks",
    201    jsonFile.path,
    202  ]);
    203  test("script-max-frames-1", ["--max-frames=1", jsonFile.path]);
    204 
    205  // This file has three records that are shown in a different order for each
    206  // of the different sort values. It also tests the handling of gzipped JSON
    207  // files.
    208  jsonFile = getRelativeFile("script-sort-by.json.gz");
    209  test("script-sort-by-usable", ["--sort-by=usable", jsonFile.path]);
    210  test("script-sort-by-req", [
    211    "--sort-by=req",
    212    "--no-fix-stacks",
    213    jsonFile.path,
    214  ]);
    215  test("script-sort-by-slop", ["--sort-by=slop", jsonFile.path]);
    216  test("script-sort-by-num-blocks", ["--sort-by=num-blocks", jsonFile.path]);
    217 
    218  // This file has several real stack traces taken from Firefox execution, each
    219  // of which tests a different allocator function (or functions).
    220  jsonFile = getRelativeFile("script-ignore-alloc-fns.json");
    221  test("script-ignore-alloc-fns", ["--ignore-alloc-fns", jsonFile.path]);
    222 
    223  // This tests "live"-mode diffs.
    224  jsonFile = getRelativeFile("script-diff-live1.json");
    225  jsonFile2 = getRelativeFile("script-diff-live2.json");
    226  test("script-diff-live", [jsonFile.path, jsonFile2.path]);
    227 
    228  // This tests "dark-matter"-mode diffs.
    229  jsonFile = getRelativeFile("script-diff-dark-matter1.json");
    230  jsonFile2 = getRelativeFile("script-diff-dark-matter2.json");
    231  test("script-diff-dark-matter", [jsonFile.path, jsonFile2.path]);
    232 }