bug 510505 - add unit tests for breakpad exception handler. r=bsmedberg
authorTed Mielczarek <ted.mielczarek@gmail.com>
Thu, 10 Sep 2009 07:49:42 -0400
changeset 35719 b35ebf2606ed51ae72532bddcea4648cdcd5943a
parent 35718 44c392db667242eaa8d9279583cda6d62e65cfd9
child 35720 4354c4d85277168e35e819f8a956c960672bbf99
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg
bugs510505
milestone1.9.3a1pre
bug 510505 - add unit tests for breakpad exception handler. r=bsmedberg
testing/xpcshell/Makefile.in
toolkit/crashreporter/Makefile.in
toolkit/crashreporter/test/Makefile.in
toolkit/crashreporter/test/nsITestCrasher.idl
toolkit/crashreporter/test/nsTestCrasher.cpp
toolkit/crashreporter/test/unit/crasher_subprocess_head.js
toolkit/crashreporter/test/unit/crasher_subprocess_tail.js
toolkit/crashreporter/test/unit/head_crashreporter.js
toolkit/crashreporter/test/unit/test_crashreporter_crash.js
--- a/testing/xpcshell/Makefile.in
+++ b/testing/xpcshell/Makefile.in
@@ -65,16 +65,21 @@ EXTRA_BUILD_FILES := \
   $(NULL)
 
 # Components / typelibs that don't get packaged with
 # the build, but that we need for the test harness.
 TEST_HARNESS_COMPONENTS := \
   httpd.js \
   $(NULL)
 
+ifdef MOZ_CRASHREPORTER
+#XXX: should find a better way to do this
+TEST_HARNESS_COMPONENTS +=  crashreporter_test.xpt
+endif
+
 # Rules for staging the necessary harness bits for a test package
 PKG_STAGE = $(DIST)/test-package-stage
 
 stage-package:
 	$(NSINSTALL) -D $(PKG_STAGE)/xpcshell/tests
 	@(cd $(srcdir) && tar $(TAR_CREATE_FLAGS) - $(TEST_HARNESS_FILES)) | (cd $(PKG_STAGE)/xpcshell && tar -xf -)
 	@(cd $(topsrcdir)/build && tar $(TAR_CREATE_FLAGS) - $(EXTRA_BUILD_FILES)) | (cd $(PKG_STAGE)/xpcshell && tar -xf -)
 	@(cd $(DEPTH)/_tests/xpcshell/ && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/xpcshell/tests && tar -xf -)
--- a/toolkit/crashreporter/Makefile.in
+++ b/toolkit/crashreporter/Makefile.in
@@ -105,12 +105,12 @@ EXPORTS = \
 
 CPPSRCS = \
 	nsExceptionHandler.cpp \
 	$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 ifdef ENABLE_TESTS
-DIRS += test
+TOOL_DIRS = test
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/crashreporter/test/Makefile.in
+++ b/toolkit/crashreporter/test/Makefile.in
@@ -38,23 +38,40 @@
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = toolkit/crashreporter/test
 
 include $(DEPTH)/config/autoconf.mk
 
-MODULE = crashreporter
+MODULE = crashreporter_test
 XPCSHELL_TESTS = unit
 
+LIBRARY_NAME = testcrasher
+IS_COMPONENT    = 1
+USE_STATIC_LIBS = 1
+
+XPIDLSRCS = nsITestCrasher.idl
+
+CPPSRCS = \
+  nsTestCrasher.cpp \
+  $(NULL)
+
+EXTRA_DSO_LIBS += xpcom
+EXTRA_DSO_LDOPTS += $(LIBS_DIR) $(MOZ_COMPONENT_LIBS) $(XPCOM_GLUE_LDOPTS)
+LOCAL_INCLUDES += -I$(XPIDL_GEN_DIR)
+
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
   browser/aboutcrashes_utils.js \
   browser/crashreport.sjs \
   browser/browser_aboutCrashes.js \
   browser/browser_bug471404.js \
   browser/browser_aboutCrashesResubmit.js \
   $(NULL)
 
 libs::  $(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)/browser
+	$(NSINSTALL) -D $(DEPTH)/_tests/xpcshell/$(MODULE)/unit/components
+	$(INSTALL) $(SHARED_LIBRARY) \
+          $(DEPTH)/_tests/xpcshell/$(MODULE)/unit/components
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/nsITestCrasher.idl
@@ -0,0 +1,7 @@
+#include "nsISupports.idl"
+
+[scriptable, uuid(95464a04-6949-46cb-b621-d167790704a0)]
+interface nsITestCrasher : nsISupports
+{
+  void crash();
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/nsTestCrasher.cpp
@@ -0,0 +1,44 @@
+#include "nsServiceManagerUtils.h"
+#include "nsIComponentManager.h"
+#include "nsIGenericFactory.h"
+#include "nsITestCrasher.h"
+
+class nsTestCrasher : public nsITestCrasher
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITESTCRASHER
+
+  nsTestCrasher() {}
+
+private:
+  ~nsTestCrasher() {};
+};
+
+
+NS_IMPL_ISUPPORTS1(nsTestCrasher, nsITestCrasher)
+
+/* void crash (); */
+NS_IMETHODIMP nsTestCrasher::Crash()
+{
+  volatile int* foo = (int*)0x42;
+  *foo = 0;
+  // not reached
+  return NS_OK;
+}
+
+// 54afce51-38d7-4df0-9750-2f90f9ffbca2
+#define NS_TESTCRASHER_CID \
+{ 0x54afce51, 0x38d7, 0x4df0, {0x97, 0x50, 0x2f, 0x90, 0xf9, 0xff, 0xbc, 0xa2} }
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsTestCrasher)
+
+static const nsModuleComponentInfo components[] = {
+    { "Test Crasher",
+      NS_TESTCRASHER_CID,
+      "@mozilla.org/testcrasher;1",
+      nsTestCrasherConstructor
+    }
+};
+
+NS_IMPL_NSGETMODULE(nsTestCrasherModule, components)
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/crasher_subprocess_head.js
@@ -0,0 +1,9 @@
+// enable crash reporting first
+let cwd = Components.classes["@mozilla.org/file/directory_service;1"]
+      .getService(Components.interfaces.nsIProperties)
+      .get("CurWorkD", Components.interfaces.nsILocalFile);
+let crashReporter =
+  Components.classes["@mozilla.org/toolkit/crash-reporter;1"]
+    .getService(Components.interfaces.nsICrashReporter);
+crashReporter.enabled = true;
+crashReporter.minidumpPath = cwd;
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/crasher_subprocess_tail.js
@@ -0,0 +1,7 @@
+// now actually crash
+let cd = cwd.clone();
+cd.append("components");
+Components.manager instanceof Components.interfaces.nsIComponentRegistrar;
+Components.manager.autoRegister(cd);
+let crasher = Components.classes["@mozilla.org/testcrasher;1"].createInstance(Components.interfaces.nsITestCrasher);
+crasher.crash();
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/head_crashreporter.js
@@ -0,0 +1,123 @@
+/*
+ * Run an xpcshell subprocess and crash it.
+ *
+ * @param setup
+ *        A string of JavaScript code to execute in the subprocess
+ *        before crashing. If this is a function and not a string,
+ *        it will have .toSource() called on it, and turned into
+ *        a call to itself. (for programmer convenience)
+ *        This code will be evaluted between crasher_subprocess_head.js
+ *        and crasher_subprocess_tail.js, so it will have access
+ *        to everything defined in crasher_subprocess_head.js,
+ *        which includes "crashReporter", a variable holding
+ *        the crash reporter service.
+ *
+ * @param callback
+ *        A JavaScript function to be called after the subprocess
+ *        crashes. It will be passed (minidump, extra), where
+ *         minidump is an nsILocalFile of the minidump file produced,
+ *         and extra is an object containing the key,value pairs from
+ *         the .extra file.
+ */
+function do_crash(setup, callback)
+{
+  // get current process filename (xpcshell)
+  let ds = Components.classes["@mozilla.org/file/directory_service;1"]
+    .getService(Components.interfaces.nsIProperties);
+  let bin = ds.get("CurProcD", Components.interfaces.nsILocalFile);
+  bin.append("xpcshell");
+  if (!bin.exists()) {
+    bin.leafName = "xpcshell.exe";
+    do_check_true(bin.exists());
+    if (!bin.exists())
+      // weird, can't find xpcshell binary?
+      do_throw("Can't find xpcshell binary!");
+  }
+  // get Gre dir (GreD)
+  let greD = ds.get("GreD", Components.interfaces.nsILocalFile);
+  let headfile = do_get_file("crasher_subprocess_head.js");
+  let tailfile = do_get_file("crasher_subprocess_tail.js");
+  // run xpcshell -g GreD -f head -e "some setup code" -f tail
+  let process = Components.classes["@mozilla.org/process/util;1"]
+                  .createInstance(Components.interfaces.nsIProcess);
+  process.init(bin);
+  let args = ['-g', greD.path,
+              '-f', headfile.path];
+  if (setup) {
+    if (typeof(setup) == "function")
+      // funky, but convenient
+      setup = "("+setup.toSource()+")();";
+    args.push('-e', setup);
+  }
+  args.push('-f', tailfile.path);
+  try {
+      process.run(true, args, args.length);
+  }
+  catch(ex) {} // on Windows we exit with a -1 status when crashing.
+
+  // should exit with an error (should have crashed)
+  do_check_neq(process.exitValue, 0);
+  // find minidump
+  let minidump = null;
+  let en = do_get_cwd().directoryEntries;
+  while (en.hasMoreElements()) {
+    let f = en.getNext().QueryInterface(Components.interfaces.nsILocalFile);
+    if (f.leafName.substr(-4) == ".dmp") {
+      minidump = f;
+      break;
+    }
+  }
+
+  if (minidump == null)
+    do_throw("No minidump found!");
+
+  let extrafile = minidump.clone();
+  extrafile.leafName = extrafile.leafName.slice(0, -4) + ".extra";
+  do_check_true(extrafile.exists());
+  let extra = parseKeyValuePairsFromFile(extrafile);
+
+  if (callback)
+    callback(minidump, extra);
+
+  if (minidump.exists())
+    minidump.remove(false);
+  if (extrafile.exists())
+    extrafile.remove(false);
+}
+
+// Utility functions for parsing .extra files
+function parseKeyValuePairs(text) {
+  var lines = text.split('\n');
+  var data = {};
+  for (let i = 0; i < lines.length; i++) {
+    if (lines[i] == '')
+      continue;
+
+    // can't just .split() because the value might contain = characters
+    let eq = lines[i].indexOf('=');
+    if (eq != -1) {
+      let [key, value] = [lines[i].substring(0, eq),
+                          lines[i].substring(eq + 1)];
+      if (key && value)
+        data[key] = value.replace("\\n", "\n", "g").replace("\\\\", "\\", "g");
+    }
+  }
+  return data;
+}
+
+function parseKeyValuePairsFromFile(file) {
+  var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].
+                createInstance(Components.interfaces.nsIFileInputStream);
+  fstream.init(file, -1, 0, 0);
+  var is = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
+           createInstance(Components.interfaces.nsIConverterInputStream);
+  is.init(fstream, "UTF-8", 1024, Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
+  var str = {};
+  var contents = '';
+  while (is.readString(4096, str) != 0) {
+    contents += str.value;
+  }
+  is.close();
+  fstream.close();
+  return parseKeyValuePairs(contents);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/crashreporter/test/unit/test_crashreporter_crash.js
@@ -0,0 +1,26 @@
+function run_test()
+{
+  if (!("@mozilla.org/toolkit/crash-reporter;1" in Components.classes)) {
+    dump("INFO | test_crashreporter.js | Can't test crashreporter in a non-libxul build.\n");
+    return;
+  }
+
+  // try a basic crash
+  do_crash(null, function(mdump, extra) {
+             do_check_true(mdump.exists());
+             do_check_true(mdump.fileSize > 0);
+             do_check_true('StartupTime' in extra);
+             do_check_true('CrashTime' in extra);
+           });
+
+  // check setting some basic data
+  do_crash(function() {
+             crashReporter.annotateCrashReport("TestKey", "TestValue");
+             crashReporter.appendAppNotesToCrashReport("Junk");
+             crashReporter.appendAppNotesToCrashReport("MoreJunk");
+           },
+           function(mdump, extra) {
+             do_check_eq(extra.TestKey, "TestValue");
+             do_check_eq(extra.Notes, "JunkMoreJunk");
+           });
+}