summaryrefslogtreecommitdiffstats
path: root/toolkit/components/crashes/tests/xpcshell/test_crash_service.js
blob: 63ff3343d6487b4ec74010b9c8b7499549de4564 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { getCrashManagerNoCreate } = ChromeUtils.importESModule(
  "resource://gre/modules/CrashManager.sys.mjs"
);
const { makeFakeAppDir } = ChromeUtils.importESModule(
  "resource://testing-common/AppData.sys.mjs"
);

add_task(async function test_instantiation() {
  Assert.ok(
    !getCrashManagerNoCreate(),
    "CrashManager global instance not initially defined."
  );

  do_get_profile();
  await makeFakeAppDir();

  // Fake profile creation.
  Cc["@mozilla.org/crashservice;1"]
    .getService(Ci.nsIObserver)
    .observe(null, "profile-after-change", null);

  Assert.ok(getCrashManagerNoCreate(), "Profile creation makes it available.");
  Assert.ok(Services.crashmanager, "CrashManager available via Services.");
  Assert.strictEqual(
    getCrashManagerNoCreate(),
    Services.crashmanager,
    "The objects are the same."
  );
});

var gMinidumpDir = do_get_tempdir();

// Ensure that the nsICrashReporter methods can find the dump
Services.appinfo.minidumpPath = gMinidumpDir;

var gDumpFile;
var gExtraFile;

// Sets up a fake crash dump and sets up the crashreporter so that it will be
// able to find it.
async function setup(crashId) {
  const cwd = Services.dirsvc.get("CurWorkD", Ci.nsIFile).path;
  const minidump = PathUtils.join(cwd, "crash.dmp");
  const extra = PathUtils.join(cwd, "crash.extra");

  // Make a copy of the files because the .extra file will be modified
  gDumpFile = PathUtils.join(gMinidumpDir.path, `${crashId}.dmp`);
  await IOUtils.copy(minidump, gDumpFile);
  gExtraFile = PathUtils.join(gMinidumpDir.path, `${crashId}.extra`);
  await IOUtils.copy(extra, gExtraFile);
}

// Cleans up the fake crash dump and resets the minidump path
async function teardown() {
  await IOUtils.remove(gDumpFile);
  await IOUtils.remove(gExtraFile);
}

async function addCrash(id, type = Ci.nsICrashService.CRASH_TYPE_CRASH) {
  let cs = Cc["@mozilla.org/crashservice;1"].getService(Ci.nsICrashService);
  return cs.addCrash(Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT, type, id);
}

async function getCrash(crashId) {
  let crashes = await Services.crashmanager.getCrashes();
  return crashes.find(c => {
    return c.id === crashId;
  });
}

async function test_addCrashBase(crashId, allThreads) {
  await setup(crashId);

  let crashType = Ci.nsICrashService.CRASH_TYPE_CRASH;
  if (allThreads) {
    crashType = Ci.nsICrashService.CRASH_TYPE_HANG;
  }
  await addCrash(crashId, crashType);
  let crash = await getCrash(crashId);
  Assert.ok(crash, "Crash " + crashId + " has been stored successfully.");
  Assert.equal(crash.metadata.ProcessType, "content");
  Assert.equal(
    crash.metadata.MinidumpSha256Hash,
    "c8ad56a2096310f40c8a4b46c890625a740fdd72e409f412933011ff947c5a40"
  );
  Assert.ok(crash.metadata.StackTraces, "The StackTraces field is present.\n");

  try {
    let stackTraces = crash.metadata.StackTraces;
    Assert.equal(stackTraces.status, "OK");
    Assert.ok(stackTraces.crash_info, "The crash_info field is populated.");
    Assert.ok(
      stackTraces.modules && !!stackTraces.modules.length,
      "The module list is populated."
    );
    Assert.ok(
      stackTraces.threads && !!stackTraces.threads.length,
      "The thread list is populated."
    );

    if (allThreads) {
      Assert.ok(
        stackTraces.threads.length > 1,
        "The stack trace contains more than one thread."
      );
    } else {
      Assert.ok(
        stackTraces.threads.length == 1,
        "The stack trace contains exactly one thread."
      );
    }

    let frames = stackTraces.threads[0].frames;
    Assert.ok(frames && !!frames.length, "The stack trace is present.\n");
  } catch (e) {
    Assert.ok(false, "StackTraces does not contain valid JSON.");
  }

  try {
    let telemetryEnvironment = JSON.parse(crash.metadata.TelemetryEnvironment);
    Assert.equal(telemetryEnvironment.EscapedField, "EscapedData\n\nfoo");
  } catch (e) {
    Assert.ok(
      false,
      "TelemetryEnvironment contents were not properly escaped\n"
    );
  }

  await teardown();
}

add_task(async function test_addCrash() {
  await test_addCrashBase("56cd87bc-bb26-339b-3a8e-f00c0f11380e", false);
});

add_task(async function test_addCrashAllThreads() {
  await test_addCrashBase("071843c4-da89-4447-af9f-965163e0b253", true);
});

add_task(async function test_addCrash_shutdownOnCrash() {
  const crashId = "de7f63dd-7516-4525-a44b-6d2f2bd3934a";
  await setup(crashId);

  // Set the MOZ_CRASHREPORTER_SHUTDOWN environment variable
  Services.env.set("MOZ_CRASHREPORTER_SHUTDOWN", "1");

  await addCrash(crashId);

  let crash = await getCrash(crashId);
  Assert.ok(crash, "Crash " + crashId + " has been stored successfully.");
  Assert.ok(
    crash.metadata.StackTraces === undefined,
    "The StackTraces field is not present because the minidump " +
      "analyzer did not start.\n"
  );

  Services.env.set("MOZ_CRASHREPORTER_SHUTDOWN", ""); // Unset the environment variable
  await teardown();
});

add_task(async function test_addCrash_quitting() {
  const firstCrashId = "0e578a74-a887-48cb-b270-d4775d01e715";
  const secondCrashId = "208379e5-1979-430d-a066-f6e57a8130ce";

  await setup(firstCrashId);

  let minidumpAnalyzerKilledPromise = new Promise((resolve, reject) => {
    Services.obs.addObserver((subject, topic, data) => {
      if (topic === "test-minidump-analyzer-killed") {
        resolve();
      }

      reject();
    }, "test-minidump-analyzer-killed");
  });

  let addCrashPromise = addCrash(firstCrashId);

  // Spin the event loop so that the minidump analyzer is launched
  await new Promise(resolve => {
    executeSoon(resolve);
  });

  // Pretend we're quitting
  let cs = Cc["@mozilla.org/crashservice;1"].getService(Ci.nsICrashService);
  let obs = cs.QueryInterface(Ci.nsIObserver);
  obs.observe(null, "quit-application", null);

  // Wait for the minidump analyzer to be killed
  await minidumpAnalyzerKilledPromise;

  // Now wait for the crash to be recorded
  await addCrashPromise;
  let crash = await getCrash(firstCrashId);
  Assert.ok(crash, "Crash " + firstCrashId + " has been stored successfully.");

  // Cleanup the fake crash and generate a new one
  await teardown();
  await setup(secondCrashId);

  await addCrash(secondCrashId);
  crash = await getCrash(secondCrashId);
  Assert.ok(crash, "Crash " + secondCrashId + " has been stored successfully.");
  Assert.ok(
    crash.metadata.StackTraces === undefined,
    "The StackTraces field is not present because the minidump " +
      "analyzer did not start.\n"
  );
  await teardown();
});