Bug 1103958 - Dumping Gecko AppInfos and about:memory in LogShake. r=gwagner
☠☠ backed out by ed2256056875 ☠ ☠
authorAlexandre Lissy <lissyx@lissyx.dyndns.org>
Thu, 08 Jan 2015 06:05:00 +0100
changeset 236053 a5adaf7a846cbf3065b96d9b4d39c9b6cd849c02
parent 236052 6aefb4703601d8731a9c25a315bf4f12b37913da
child 236054 f4e1c6a9533ec2b6c214878301894d1c5e0f1e20
push id384
push usermartin.thomson@gmail.com
push dateFri, 09 Jan 2015 21:26:39 +0000
reviewersgwagner
bugs1103958
milestone37.0a1
Bug 1103958 - Dumping Gecko AppInfos and about:memory in LogShake. r=gwagner
b2g/components/LogCapture.jsm
b2g/components/LogShake.jsm
b2g/components/test/unit/test_logcapture.js
b2g/components/test/unit/test_logcapture_gonk.js
b2g/components/test/unit/test_logshake_gonk.js
b2g/components/test/unit/xpcshell.ini
--- a/b2g/components/LogCapture.jsm
+++ b/b2g/components/LogCapture.jsm
@@ -1,59 +1,72 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* jshint moz: true */
 /* global Uint8Array, Components, dump */
 
-'use strict';
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
 
-this.EXPORTED_SYMBOLS = ['LogCapture'];
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
+
+this.EXPORTED_SYMBOLS = ["LogCapture"];
 
 const SYSTEM_PROPERTY_KEY_MAX = 32;
 const SYSTEM_PROPERTY_VALUE_MAX = 92;
 
 function debug(msg) {
-  dump('LogCapture.jsm: ' + msg + '\n');
+  dump("LogCapture.jsm: " + msg + "\n");
 }
 
 let LogCapture = {
   ensureLoaded: function() {
     if (!this.ctypes) {
       this.load();
     }
   },
 
   load: function() {
     // load in everything on first use
-    Components.utils.import('resource://gre/modules/ctypes.jsm', this);
+    Cu.import("resource://gre/modules/ctypes.jsm", this);
 
-    this.libc = this.ctypes.open(this.ctypes.libraryName('c'));
+    this.libc = this.ctypes.open(this.ctypes.libraryName("c"));
 
-    this.read = this.libc.declare('read',
+    this.read = this.libc.declare("read",
       this.ctypes.default_abi,
       this.ctypes.int,       // bytes read (out)
       this.ctypes.int,       // file descriptor (in)
       this.ctypes.voidptr_t, // buffer to read into (in)
       this.ctypes.size_t     // size_t size of buffer (in)
     );
 
-    this.open = this.libc.declare('open',
+    this.open = this.libc.declare("open",
       this.ctypes.default_abi,
       this.ctypes.int,      // file descriptor (returned)
       this.ctypes.char.ptr, // path
       this.ctypes.int       // flags
     );
 
-    this.close = this.libc.declare('close',
+    this.close = this.libc.declare("close",
       this.ctypes.default_abi,
       this.ctypes.int, // error code (returned)
       this.ctypes.int  // file descriptor
     );
 
+    this.getpid = this.libc.declare("getpid",
+      this.ctypes.default_abi,
+      this.ctypes.int  // PID
+    );
+
     this.property_find_nth =
       this.libc.declare("__system_property_find_nth",
                         this.ctypes.default_abi,
                         this.ctypes.voidptr_t,     // return value: nullable prop_info*
                         this.ctypes.unsigned_int); // n: the index of the property to return
 
     this.property_read =
       this.libc.declare("__system_property_read",
@@ -148,12 +161,32 @@ let LogCapture = {
       let key = this.key_buf.readString();;
       let value = this.value_buf.readString()
 
       propertyDict[key] = value;
       n++;
     }
 
     return propertyDict;
+  },
+
+  /**
+   * Dumping about:memory to a file in /data/local/tmp/, returning a Promise.
+   * Will be resolved with the dumped file name.
+   */
+  readAboutMemory: function() {
+    this.ensureLoaded();
+    let deferred = Promise.defer();
+
+    // Perform the dump
+    let dumper = Cc["@mozilla.org/memory-info-dumper;1"]
+                    .getService(Ci.nsIMemoryInfoDumper);
+
+    let file = "/data/local/tmp/logshake-about_memory-" + this.getpid() + ".json.gz";
+    dumper.dumpMemoryReportsToNamedFile(file, function() {
+      deferred.resolve(file);
+    }, null, false);
+
+    return deferred.promise;
   }
 };
 
 this.LogCapture = LogCapture;
--- a/b2g/components/LogShake.jsm
+++ b/b2g/components/LogShake.jsm
@@ -16,54 +16,54 @@
 /* enable Mozilla javascript extensions and global strictness declaration,
  * disable valid this checking */
 /* jshint moz: true */
 /* jshint -W097 */
 /* jshint -W040 */
 /* global Services, Components, dump, LogCapture, LogParser,
    OS, Promise, volumeService, XPCOMUtils, SystemAppProxy */
 
-'use strict';
+"use strict";
 
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, 'LogCapture', 'resource://gre/modules/LogCapture.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'LogParser', 'resource://gre/modules/LogParser.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'OS', 'resource://gre/modules/osfile.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Promise', 'resource://gre/modules/Promise.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Services', 'resource://gre/modules/Services.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'SystemAppProxy', 'resource://gre/modules/SystemAppProxy.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, "LogCapture", "resource://gre/modules/LogCapture.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LogParser", "resource://gre/modules/LogParser.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise", "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", "resource://gre/modules/SystemAppProxy.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, 'powerManagerService',
-                                   '@mozilla.org/power/powermanagerservice;1',
-                                   'nsIPowerManagerService');
+XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService",
+                                   "@mozilla.org/power/powermanagerservice;1",
+                                   "nsIPowerManagerService");
 
-XPCOMUtils.defineLazyServiceGetter(this, 'volumeService',
-                                   '@mozilla.org/telephony/volume-service;1',
-                                   'nsIVolumeService');
+XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
+                                   "@mozilla.org/telephony/volume-service;1",
+                                   "nsIVolumeService");
 
-this.EXPORTED_SYMBOLS = ['LogShake'];
+this.EXPORTED_SYMBOLS = ["LogShake"];
 
 function debug(msg) {
-  dump('LogShake.jsm: '+msg+'\n');
+  dump("LogShake.jsm: "+msg+"\n");
 }
 
 /**
  * An empirically determined amount of acceleration corresponding to a
  * shake
  */
 const EXCITEMENT_THRESHOLD = 500;
-const DEVICE_MOTION_EVENT = 'devicemotion';
-const SCREEN_CHANGE_EVENT = 'screenchange';
-const CAPTURE_LOGS_START_EVENT = 'capture-logs-start';
-const CAPTURE_LOGS_ERROR_EVENT = 'capture-logs-error';
-const CAPTURE_LOGS_SUCCESS_EVENT = 'capture-logs-success';
+const DEVICE_MOTION_EVENT = "devicemotion";
+const SCREEN_CHANGE_EVENT = "screenchange";
+const CAPTURE_LOGS_START_EVENT = "capture-logs-start";
+const CAPTURE_LOGS_ERROR_EVENT = "capture-logs-error";
+const CAPTURE_LOGS_SUCCESS_EVENT = "capture-logs-success";
 
 let LogShake = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
   /**
    * If LogShake is listening for device motion events. Required due to lag
    * between HAL layer of device motion events and listening for device motion
    * events.
    */
@@ -74,27 +74,28 @@ let LogShake = {
    * debouncing.
    */
   captureRequested: false,
 
   /**
    * Map of files which have log-type information to their parsers
    */
   LOGS_WITH_PARSERS: {
-    '/dev/log/main': LogParser.prettyPrintLogArray,
-    '/dev/log/system': LogParser.prettyPrintLogArray,
-    '/dev/log/radio': LogParser.prettyPrintLogArray,
-    '/dev/log/events': LogParser.prettyPrintLogArray,
-    '/proc/cmdline': LogParser.prettyPrintArray,
-    '/proc/kmsg': LogParser.prettyPrintArray,
-    '/proc/meminfo': LogParser.prettyPrintArray,
-    '/proc/uptime': LogParser.prettyPrintArray,
-    '/proc/version': LogParser.prettyPrintArray,
-    '/proc/vmallocinfo': LogParser.prettyPrintArray,
-    '/proc/vmstat': LogParser.prettyPrintArray
+    "/dev/log/main": LogParser.prettyPrintLogArray,
+    "/dev/log/system": LogParser.prettyPrintLogArray,
+    "/dev/log/radio": LogParser.prettyPrintLogArray,
+    "/dev/log/events": LogParser.prettyPrintLogArray,
+    "/proc/cmdline": LogParser.prettyPrintArray,
+    "/proc/kmsg": LogParser.prettyPrintArray,
+    "/proc/meminfo": LogParser.prettyPrintArray,
+    "/proc/uptime": LogParser.prettyPrintArray,
+    "/proc/version": LogParser.prettyPrintArray,
+    "/proc/vmallocinfo": LogParser.prettyPrintArray,
+    "/proc/vmstat": LogParser.prettyPrintArray,
+    "/system/b2g/application.ini": LogParser.prettyPrintArray
   },
 
   /**
    * Start existing, observing motion events if the screen is turned on.
    */
   init: function() {
     // TODO: no way of querying screen state from power manager
     // this.handleScreenChangeEvent({ detail: {
@@ -104,17 +105,17 @@ let LogShake = {
     // However, the screen is always on when we are being enabled because it is
     // either due to the phone starting up or a user enabling us directly.
     this.handleScreenChangeEvent({ detail: {
       screenEnabled: true
     }});
 
     SystemAppProxy.addEventListener(SCREEN_CHANGE_EVENT, this, false);
 
-    Services.obs.addObserver(this, 'xpcom-shutdown', false);
+    Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   /**
    * Handle an arbitrary event, passing it along to the proper function
    */
   handleEvent: function(event) {
     switch (event.type) {
     case DEVICE_MOTION_EVENT:
@@ -129,17 +130,17 @@ let LogShake = {
       break;
     }
   },
 
   /**
    * Handle an observation from Services.obs
    */
   observe: function(subject, topic) {
-    if (topic === 'xpcom-shutdown') {
+    if (topic === "xpcom-shutdown") {
       this.uninit();
     }
   },
 
   startDeviceMotionListener: function() {
     if (!this.deviceMotionEnabled) {
       SystemAppProxy.addEventListener(DEVICE_MOTION_EVENT, this, false);
       this.deviceMotionEnabled = true;
@@ -147,18 +148,18 @@ let LogShake = {
   },
 
   stopDeviceMotionListener: function() {
     SystemAppProxy.removeEventListener(DEVICE_MOTION_EVENT, this, false);
     this.deviceMotionEnabled = false;
   },
 
   /**
-   * Handle a motion event, keeping track of 'excitement', the magnitude
-   * of the device's acceleration.
+   * Handle a motion event, keeping track of "excitement", the magnitude
+   * of the device"s acceleration.
    */
   handleDeviceMotionEvent: function(event) {
     // There is a lag between disabling the event listener and event arrival
     // ceasing.
     if (!this.deviceMotionEnabled) {
       return;
     }
 
@@ -212,21 +213,35 @@ let LogShake = {
 
     try {
       logArrays["properties"] =
         LogParser.prettyPrintPropertiesArray(LogCapture.readProperties());
     } catch (ex) {
       Cu.reportError("Unable to get device properties: " + ex);
     }
 
+    // Let Gecko perfom the dump to a file, and just collect it
+    try {
+      LogCapture.readAboutMemory().then(aboutMemory => {
+        let file = OS.Path.basename(aboutMemory);
+        logArrays[file] =
+          LogParser.prettyPrintArray(LogCapture.readLogFile(aboutMemory));
+        // We need to remove the dumped file, now that we have it in memory
+        OS.File.remove(aboutMemory);
+      });
+    } catch (ex) {
+      Cu.reportError("Unable to get about:memory dump: " + ex);
+    }
+
     for (let loc in this.LOGS_WITH_PARSERS) {
       let logArray;
       try {
         logArray = LogCapture.readLogFile(loc);
         if (!logArray) {
+          debug("LogCapture.readLogFile() returned nothing for: " + loc);
           continue;
         }
       } catch (ex) {
         Cu.reportError("Unable to LogCapture.readLogFile('" + loc + "'): " + ex);
         continue;
       }
 
       try {
@@ -240,93 +255,101 @@ let LogShake = {
   },
 
   /**
    * Stop logshake, removing all listeners
    */
   uninit: function() {
     this.stopDeviceMotionListener();
     SystemAppProxy.removeEventListener(SCREEN_CHANGE_EVENT, this, false);
-    Services.obs.removeObserver(this, 'xpcom-shutdown');
+    Services.obs.removeObserver(this, "xpcom-shutdown");
   }
 };
 
 function getLogFilename(logLocation) {
   // sanitize the log location
-  let logName = logLocation.replace(/\//g, '-');
-  if (logName[0] === '-') {
+  let logName = logLocation.replace(/\//g, "-");
+  if (logName[0] === "-") {
     logName = logName.substring(1);
   }
-  return logName + '.log';
+
+  // If no extension is provided, default to forcing .log
+  let extension = ".log";
+  let logLocationExt = logLocation.split(".");
+  if (logLocationExt.length > 1) {
+    // otherwise, just append nothing
+    extension = "";
+  }
+
+  return logName + extension;
 }
 
 function getSdcardPrefix() {
-  return volumeService.getVolumeByName('sdcard').mountPoint;
+  return volumeService.getVolumeByName("sdcard").mountPoint;
 }
 
 function getLogDirectoryRoot() {
-  return 'logs';
+  return "logs";
 }
 
 function getLogDirectory() {
   let d = new Date();
   d = new Date(d.getTime() - d.getTimezoneOffset() * 60000);
-  let timestamp = d.toISOString().slice(0, -5).replace(/[:T]/g, '-');
-  // return directory name of format 'logs/timestamp/'
+  let timestamp = d.toISOString().slice(0, -5).replace(/[:T]/g, "-");
   return timestamp;
 }
 
 /**
  * Save the formatted arrays of log files to an sdcard if available
  */
 function saveLogs(logArrays) {
   if (!logArrays || Object.keys(logArrays).length === 0) {
     return Promise.resolve({
       logFilenames: [],
-      logPrefix: ''
+      logPrefix: ""
     });
   }
 
   let sdcardPrefix, dirNameRoot, dirName;
   try {
     sdcardPrefix = getSdcardPrefix();
     dirNameRoot = getLogDirectoryRoot();
     dirName = getLogDirectory();
   } catch(e) {
     // Return promise failed with exception e
     // Handles missing sdcard
     return Promise.reject(e);
   }
 
-  debug('making a directory all the way from '+sdcardPrefix+' to '+(sdcardPrefix + '/' + dirNameRoot + '/' + dirName));
+  debug("making a directory all the way from " + sdcardPrefix + " to " + (sdcardPrefix + "/" + dirNameRoot + "/" + dirName) );
   let logsRoot = OS.Path.join(sdcardPrefix, dirNameRoot);
   return OS.File.makeDir(logsRoot, {from: sdcardPrefix}).then(
     function() {
       let logsDir = OS.Path.join(logsRoot, dirName);
       return OS.File.makeDir(logsDir, {ignoreExisting: false}).then(
         function() {
           // Now the directory is guaranteed to exist, save the logs
           let logFilenames = [];
           let saveRequests = [];
 
           for (let logLocation in logArrays) {
-            debug('requesting save of ' + logLocation);
+            debug("requesting save of " + logLocation);
             let logArray = logArrays[logLocation];
             // The filename represents the relative path within the SD card, not the
             // absolute path because Gaia will refer to it using the DeviceStorage
             // API
             let filename = OS.Path.join(dirNameRoot, dirName, getLogFilename(logLocation));
             logFilenames.push(filename);
             let saveRequest = OS.File.writeAtomic(OS.Path.join(sdcardPrefix, filename), logArray);
             saveRequests.push(saveRequest);
           }
 
           return Promise.all(saveRequests).then(
             function() {
-              debug('returning logfilenames: '+logFilenames.toSource());
+              debug("returning logfilenames: "+logFilenames.toSource());
               return {
                 logFilenames: logFilenames,
                 logPrefix: OS.Path.join(dirNameRoot, dirName)
               };
             });
         });
     });
 }
--- a/b2g/components/test/unit/test_logcapture.js
+++ b/b2g/components/test/unit/test_logcapture.js
@@ -1,30 +1,13 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-
 /**
- * Test that LogCapture successfully reads from the /dev/log devices, returning
- * a Uint8Array of some length, including zero. This tests a few standard
- * log devices
+ * Testing non Gonk-specific code path
  */
 function run_test() {
   Components.utils.import("resource:///modules/LogCapture.jsm");
-
-  function verifyLog(log) {
-    // log exists
-    notEqual(log, null);
-    // log has a length and it is non-negative (is probably array-like)
-    ok(log.length >= 0);
-  }
+  run_next_test();
+}
 
-  let propertiesLog = LogCapture.readProperties();
-  notEqual(propertiesLog, null, "Properties should not be null");
-  notEqual(propertiesLog, undefined, "Properties should not be undefined");
-  equal(propertiesLog["ro.kernel.qemu"], "1", "QEMU property should be 1");
-
-  let mainLog = LogCapture.readLogFile("/dev/log/main");
-  verifyLog(mainLog);
-
-  let meminfoLog = LogCapture.readLogFile("/proc/meminfo");
-  verifyLog(meminfoLog);
-}
+// Trivial test just to make sure we have no syntax error
+add_test(function test_logCapture_loads() {
+  ok(LogCapture, "LogCapture object exists");
+  run_next_test();
+});
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/unit/test_logcapture_gonk.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+
+/**
+ * Test that LogCapture successfully reads from the /dev/log devices, returning
+ * a Uint8Array of some length, including zero. This tests a few standard
+ * log devices
+ */
+function run_test() {
+  Components.utils.import("resource:///modules/LogCapture.jsm");
+  run_next_test();
+}
+
+function verifyLog(log) {
+  // log exists
+  notEqual(log, null);
+  // log has a length and it is non-negative (is probably array-like)
+  ok(log.length >= 0);
+}
+
+add_test(function test_readLogFile() {
+  let mainLog = LogCapture.readLogFile("/dev/log/main");
+  verifyLog(mainLog);
+
+  let meminfoLog = LogCapture.readLogFile("/proc/meminfo");
+  verifyLog(meminfoLog);
+
+  run_next_test();
+});
+
+add_test(function test_readProperties() {
+  let propertiesLog = LogCapture.readProperties();
+  notEqual(propertiesLog, null, "Properties should not be null");
+  notEqual(propertiesLog, undefined, "Properties should not be undefined");
+  equal(propertiesLog["ro.kernel.qemu"], "1", "QEMU property should be 1");
+
+  run_next_test();
+});
+
+add_test(function test_readAppIni() {
+  let appIni = LogCapture.readLogFile("/system/b2g/application.ini");
+  verifyLog(appIni);
+
+  run_next_test();
+});
+
+
+add_test(function test_get_about_memory() {
+  let memLog = LogCapture.readAboutMemory();
+
+  ok(memLog, "Should have returned a valid Promise object");
+
+  memLog.then(file => {
+    ok(file, "Should have returned a filename");
+    run_next_test();
+  }, error => {
+    ok(false, "Dumping about:memory promise rejected: " + error);
+    run_next_test();
+  });
+});
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/unit/test_logshake_gonk.js
@@ -0,0 +1,121 @@
+/**
+ * Test the log capturing capabilities of LogShake.jsm, checking
+ * for Gonk-specific parts
+ */
+
+/* jshint moz: true */
+/* global Components, LogCapture, LogShake, ok, add_test, run_next_test, dump */
+/* exported run_test */
+
+/* disable use strict warning */
+/* jshint -W097 */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "volumeService",
+                                   "@mozilla.org/telephony/volume-service;1",
+                                   "nsIVolumeService");
+
+let sdcard;
+
+function run_test() {
+  Cu.import("resource://gre/modules/LogShake.jsm");
+  Cu.import("resource://gre/modules/Promise.jsm");
+  Cu.import("resource://gre/modules/osfile.jsm");
+  do_get_profile();
+  debug("Starting");
+  run_next_test();
+}
+
+function debug(msg) {
+  var timestamp = Date.now();
+  dump("LogShake: " + timestamp + ": " + msg + "\n");
+}
+
+add_test(function setup_fs() {
+  OS.File.makeDir("/data/local/tmp/sdcard/", {from: "/data"}).then(function() {
+    run_next_test();
+  });
+});
+
+add_test(function setup_sdcard() {
+  let volName = "sdcard";
+  let mountPoint = "/data/local/tmp/sdcard";
+  volumeService.createFakeVolume(volName, mountPoint);
+
+  let vol = volumeService.getVolumeByName(volName);
+  ok(vol, "volume shouldn't be null");
+  equal(volName, vol.name, "name");
+
+  volumeService.SetFakeVolumeState(volName, Ci.nsIVolume.STATE_MOUNTED);
+  equal(Ci.nsIVolume.STATE_MOUNTED, vol.state, "state");
+
+  run_next_test();
+});
+
+add_test(function test_ensure_sdcard() {
+  sdcard = volumeService.getVolumeByName("sdcard").mountPoint;
+  ok(sdcard, "Should have a valid sdcard mountpoint");
+  run_next_test();
+});
+
+add_test(function test_logShake_captureLogs_returns() {
+  // Enable LogShake
+  LogShake.init();
+
+  LogShake.captureLogs().then(logResults => {
+    LogShake.uninit();
+
+    ok(logResults.logFilenames.length > 0, "Should have filenames");
+    ok(logResults.logPrefix.length > 0, "Should have prefix");
+
+    run_next_test();
+  },
+  error => {
+    LogShake.uninit();
+
+    ok(false, "Should not have received error: " + error);
+
+    run_next_test();
+  });
+});
+
+add_test(function test_logShake_captureLogs_writes() {
+  // Enable LogShake
+  LogShake.init();
+
+  let expectedFiles = [];
+
+  LogShake.captureLogs().then(logResults => {
+    LogShake.uninit();
+
+    logResults.logFilenames.forEach(f => {
+      let p = OS.Path.join(sdcard, f);
+      ok(p, "Should have a valid result path: " + p);
+
+      let t = OS.File.exists(p).then(rv => {
+        ok(rv, "File exists: " + p);
+      });
+
+      expectedFiles.push(t);
+    });
+
+    Promise.all(expectedFiles).then(() => {
+      ok(true, "Completed all files checks");
+      run_next_test();
+    });
+  },
+  error => {
+    LogShake.uninit();
+
+    ok(false, "Should not have received error: " + error);
+
+    run_next_test();
+  });
+});
--- a/b2g/components/test/unit/xpcshell.ini
+++ b/b2g/components/test/unit/xpcshell.ini
@@ -9,15 +9,22 @@ support-files =
 
 [test_bug832946.js]
 
 [test_fxaccounts.js]
 [test_signintowebsite.js]
 head = head_identity.js
 tail =
 
+# testing non gonk-specific stuff
 [test_logcapture.js]
+
+[test_logcapture_gonk.js]
 # only run on b2g builds due to requiring b2g-specific log files to exist
 skip-if = toolkit != "gonk"
 
 [test_logparser.js]
 
 [test_logshake.js]
+
+[test_logshake_gonk.js]
+# only run on b2g builds due to requiring b2g-specific log files to exist
+skip-if = toolkit != "gonk"