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 }