Bug 1019816 - Developer option to store logcat to sdcard by shaking the phone. r=gerard-majax

This commit is contained in:
2014-08-22 10:32:00 +02:00
parent ad693ed26e
commit 78da0cea96
11 changed files with 939 additions and 0 deletions

View File

@ -194,6 +194,24 @@ SettingsListener.observe('devtools.overlay', false, (value) => {
}
});
#ifdef MOZ_WIDGET_GONK
let LogShake;
SettingsListener.observe('devtools.logshake', false, (value) => {
if (value) {
if (!LogShake) {
let scope = {};
Cu.import('resource://gre/modules/LogShake.jsm', scope);
LogShake = scope.LogShake;
}
LogShake.init();
} else {
if (LogShake) {
LogShake.uninit();
}
}
});
#endif
// =================== Device Storage ====================
SettingsListener.observe('device.storage.writable.name', 'sdcard', function(value) {
if (Services.prefs.getPrefType('device.storage.writable.name') != Ci.nsIPrefBranch.PREF_STRING) {

View File

@ -0,0 +1,91 @@
/* 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';
this.EXPORTED_SYMBOLS = ['LogCapture'];
/**
* readLogFile
* Read in /dev/log/{{log}} in nonblocking mode, which will return -1 if
* reading would block the thread.
*
* @param log {String} The log from which to read. Must be present in /dev/log
* @return {Uint8Array} Raw log data
*/
let readLogFile = function(logLocation) {
if (!this.ctypes) {
// load in everything on first use
Components.utils.import('resource://gre/modules/ctypes.jsm', this);
this.lib = this.ctypes.open(this.ctypes.libraryName('c'));
this.read = this.lib.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.lib.declare('open',
this.ctypes.default_abi,
this.ctypes.int, // file descriptor (returned)
this.ctypes.char.ptr, // path
this.ctypes.int // flags
);
this.close = this.lib.declare('close',
this.ctypes.default_abi,
this.ctypes.int, // error code (returned)
this.ctypes.int // file descriptor
);
}
const O_READONLY = 0;
const O_NONBLOCK = 1 << 11;
const BUF_SIZE = 2048;
let BufType = this.ctypes.ArrayType(this.ctypes.char);
let buf = new BufType(BUF_SIZE);
let logArray = [];
let logFd = this.open(logLocation, O_READONLY | O_NONBLOCK);
if (logFd === -1) {
return null;
}
let readStart = Date.now();
let readCount = 0;
while (true) {
let count = this.read(logFd, buf, BUF_SIZE);
readCount += 1;
if (count <= 0) {
// log has return due to being nonblocking or running out of things
break;
}
for(let i = 0; i < count; i++) {
logArray.push(buf[i]);
}
}
let logTypedArray = new Uint8Array(logArray);
this.close(logFd);
return logTypedArray;
};
let cleanup = function() {
this.lib.close();
this.read = this.open = this.close = null;
this.lib = null;
this.ctypes = null;
};
this.LogCapture = { readLogFile: readLogFile, cleanup: cleanup };

View File

@ -0,0 +1,301 @@
/* jshint esnext: true */
/* global DataView */
"use strict";
this.EXPORTED_SYMBOLS = ["LogParser"];
/**
* Parse an array read from a /dev/log/ file. Format taken from
* kernel/drivers/staging/android/logger.h and system/core/logcat/logcat.cpp
*
* @param array {Uint8Array} Array read from /dev/log/ file
* @return {Array} List of log messages
*/
function parseLogArray(array) {
let data = new DataView(array.buffer);
let byteString = String.fromCharCode.apply(null, array);
let logMessages = [];
let pos = 0;
while (pos < byteString.length) {
// Parse a single log entry
// Track current offset from global position
let offset = 0;
// Length of the entry, discarded
let length = data.getUint32(pos + offset, true);
offset += 4;
// Id of the process which generated the message
let processId = data.getUint32(pos + offset, true);
offset += 4;
// Id of the thread which generated the message
let threadId = data.getUint32(pos + offset, true);
offset += 4;
// Seconds since epoch when this message was logged
let seconds = data.getUint32(pos + offset, true);
offset += 4;
// Nanoseconds since the last second
let nanoseconds = data.getUint32(pos + offset, true);
offset += 4;
// Priority in terms of the ANDROID_LOG_* constants (see below)
// This is where the length field begins counting
let priority = data.getUint8(pos + offset);
// Reset pos and offset to count from here
pos += offset;
offset = 0;
offset += 1;
// Read the tag and message, represented as null-terminated c-style strings
let tag = "";
while (byteString[pos + offset] != "\0") {
tag += byteString[pos + offset];
offset ++;
}
offset ++;
let message = "";
// The kernel log driver may have cut off the null byte (logprint.c)
while (byteString[pos + offset] != "\0" && offset < length) {
message += byteString[pos + offset];
offset ++;
}
// Un-skip the missing null terminator
if (offset === length) {
offset --;
}
offset ++;
pos += offset;
// Log messages are occasionally delimited by newlines, but are also
// sometimes followed by newlines as well
if (message.charAt(message.length - 1) === "\n") {
message = message.substring(0, message.length - 1);
}
// Add an aditional time property to mimic the milliseconds since UTC
// expected by Date
let time = seconds * 1000.0 + nanoseconds/1000000.0;
// Log messages with interleaved newlines are considered to be separate log
// messages by logcat
for (let lineMessage of message.split("\n")) {
logMessages.push({
processId: processId,
threadId: threadId,
seconds: seconds,
nanoseconds: nanoseconds,
time: time,
priority: priority,
tag: tag,
message: lineMessage + "\n"
});
}
}
return logMessages;
}
/**
* Get a thread-time style formatted string from time
* @param time {Number} Milliseconds since epoch
* @return {String} Formatted time string
*/
function getTimeString(time) {
let date = new Date(time);
function pad(number) {
if ( number < 10 ) {
return "0" + number;
}
return number;
}
return pad( date.getMonth() + 1 ) +
"-" + pad( date.getDate() ) +
" " + pad( date.getHours() ) +
":" + pad( date.getMinutes() ) +
":" + pad( date.getSeconds() ) +
"." + (date.getMilliseconds() / 1000).toFixed(3).slice(2, 5);
}
/**
* Pad a string using spaces on the left
* @param str {String} String to pad
* @param width {Number} Desired string length
*/
function padLeft(str, width) {
while (str.length < width) {
str = " " + str;
}
return str;
}
/**
* Pad a string using spaces on the right
* @param str {String} String to pad
* @param width {Number} Desired string length
*/
function padRight(str, width) {
while (str.length < width) {
str = str + " ";
}
return str;
}
/** Constant values taken from system/core/liblog */
const ANDROID_LOG_UNKNOWN = 0;
const ANDROID_LOG_DEFAULT = 1;
const ANDROID_LOG_VERBOSE = 2;
const ANDROID_LOG_DEBUG = 3;
const ANDROID_LOG_INFO = 4;
const ANDROID_LOG_WARN = 5;
const ANDROID_LOG_ERROR = 6;
const ANDROID_LOG_FATAL = 7;
const ANDROID_LOG_SILENT = 8;
/**
* Map a priority number to its abbreviated string equivalent
* @param priorityNumber {Number} Log-provided priority number
* @return {String} Priority number's abbreviation
*/
function getPriorityString(priorityNumber) {
switch (priorityNumber) {
case ANDROID_LOG_VERBOSE:
return "V";
case ANDROID_LOG_DEBUG:
return "D";
case ANDROID_LOG_INFO:
return "I";
case ANDROID_LOG_WARN:
return "W";
case ANDROID_LOG_ERROR:
return "E";
case ANDROID_LOG_FATAL:
return "F";
case ANDROID_LOG_SILENT:
return "S";
default:
return "?";
}
}
/**
* Mimic the logcat "threadtime" format, generating a formatted string from a
* log message object.
* @param logMessage {Object} A log message from the list returned by parseLogArray
* @return {String} threadtime formatted summary of the message
*/
function formatLogMessage(logMessage) {
// MM-DD HH:MM:SS.ms pid tid priority tag: message
// from system/core/liblog/logprint.c:
return getTimeString(logMessage.time) +
" " + padLeft(""+logMessage.processId, 5) +
" " + padLeft(""+logMessage.threadId, 5) +
" " + getPriorityString(logMessage.priority) +
" " + padRight(logMessage.tag, 8) +
": " + logMessage.message;
}
/**
* Pretty-print an array of bytes read from a log file by parsing then
* threadtime formatting its entries.
* @param array {Uint8Array} Array of a log file's bytes
* @return {String} Pretty-printed log
*/
function prettyPrintLogArray(array) {
let logMessages = parseLogArray(array);
return logMessages.map(formatLogMessage).join("");
}
/**
* Parse an array of bytes as a properties file. The structure of the
* properties file is derived from bionic/libc/bionic/system_properties.c
* @param array {Uint8Array} Array containing property data
* @return {Object} Map from property name to property value, both strings
*/
function parsePropertiesArray(array) {
let data = new DataView(array.buffer);
let byteString = String.fromCharCode.apply(null, array);
let properties = {};
let propIndex = 0;
let propCount = data.getUint32(0, true);
// first TOC entry is at 32
let tocOffset = 32;
const PROP_NAME_MAX = 32;
const PROP_VALUE_MAX = 92;
while (propIndex < propCount) {
// Retrieve offset from file start
let infoOffset = data.getUint32(tocOffset, true) & 0xffffff;
// Now read the name, integer serial, and value
let propName = "";
let nameOffset = infoOffset;
while (byteString[nameOffset] != "\0" &&
(nameOffset - infoOffset) < PROP_NAME_MAX) {
propName += byteString[nameOffset];
nameOffset ++;
}
infoOffset += PROP_NAME_MAX;
// Skip serial number
infoOffset += 4;
let propValue = "";
nameOffset = infoOffset;
while (byteString[nameOffset] != "\0" &&
(nameOffset - infoOffset) < PROP_VALUE_MAX) {
propValue += byteString[nameOffset];
nameOffset ++;
}
// Move to next table of contents entry
tocOffset += 4;
properties[propName] = propValue;
propIndex += 1;
}
return properties;
}
/**
* Pretty-print an array read from the /dev/__properties__ file.
* @param array {Uint8Array} File data array
* @return {String} Human-readable string of property name: property value
*/
function prettyPrintPropertiesArray(array) {
let properties = parsePropertiesArray(array);
let propertiesString = "";
for(let propName in properties) {
propertiesString += propName + ": " + properties[propName] + "\n";
}
return propertiesString;
}
/**
* Pretty-print a normal array. Does nothing.
* @param array {Uint8Array} Input array
*/
function prettyPrintArray(array) {
return array;
}
this.LogParser = {
parseLogArray: parseLogArray,
parsePropertiesArray: parsePropertiesArray,
prettyPrintArray: prettyPrintArray,
prettyPrintLogArray: prettyPrintLogArray,
prettyPrintPropertiesArray: prettyPrintPropertiesArray
};

301
b2g/components/LogShake.jsm Normal file
View File

@ -0,0 +1,301 @@
/* 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/. */
/**
* LogShake is a module which listens for log requests sent by Gaia. In
* response to a sufficiently large acceleration (a shake), it will save log
* files to an arbitrary directory which it will then return on a
* 'capture-logs-success' event with detail.logFilenames representing each log
* file's filename in the directory. If an error occurs it will instead produce
* a 'capture-logs-error' event.
*/
/* 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';
const Cu = Components.utils;
const Ci = Components.interfaces;
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.defineLazyServiceGetter(this, 'powerManagerService',
'@mozilla.org/power/powermanagerservice;1',
'nsIPowerManagerService');
XPCOMUtils.defineLazyServiceGetter(this, 'volumeService',
'@mozilla.org/telephony/volume-service;1',
'nsIVolumeService');
this.EXPORTED_SYMBOLS = ['LogShake'];
function debug(msg) {
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_ERROR_EVENT = 'capture-logs-error';
const CAPTURE_LOGS_SUCCESS_EVENT = 'capture-logs-success';
// Map of files which have log-type information to their parsers
const LOGS_WITH_PARSERS = {
'/dev/__properties__': LogParser.prettyPrintPropertiesArray,
'/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
};
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.
*/
deviceMotionEnabled: false,
/**
* If a capture has been requested and is waiting for reads/parsing. Used for
* debouncing.
*/
captureRequested: false,
/**
* 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: {
// screenEnabled: powerManagerService.screenEnabled
// }});
// 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);
},
/**
* Handle an arbitrary event, passing it along to the proper function
*/
handleEvent: function(event) {
switch (event.type) {
case DEVICE_MOTION_EVENT:
if (!this.deviceMotionEnabled) {
return;
}
this.handleDeviceMotionEvent(event);
break;
case SCREEN_CHANGE_EVENT:
this.handleScreenChangeEvent(event);
break;
}
},
/**
* Handle an observation from Services.obs
*/
observe: function(subject, topic) {
if (topic === 'xpcom-shutdown') {
this.uninit();
}
},
startDeviceMotionListener: function() {
if (!this.deviceMotionEnabled) {
SystemAppProxy.addEventListener(DEVICE_MOTION_EVENT, this, false);
this.deviceMotionEnabled = true;
}
},
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.
*/
handleDeviceMotionEvent: function(event) {
// There is a lag between disabling the event listener and event arrival
// ceasing.
if (!this.deviceMotionEnabled) {
return;
}
var acc = event.accelerationIncludingGravity;
var excitement = acc.x * acc.x + acc.y * acc.y + acc.z * acc.z;
if (excitement > EXCITEMENT_THRESHOLD) {
if (!this.captureRequested) {
this.captureRequested = true;
captureLogs().then(logResults => {
// On resolution send the success event to the requester
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_SUCCESS_EVENT, {
logFilenames: logResults.logFilenames,
logPrefix: logResults.logPrefix
});
this.captureRequested = false;
},
error => {
// On an error send the error event
SystemAppProxy._sendCustomEvent(CAPTURE_LOGS_ERROR_EVENT, {error: error});
this.captureRequested = false;
});
}
}
},
handleScreenChangeEvent: function(event) {
if (event.detail.screenEnabled) {
this.startDeviceMotionListener();
} else {
this.stopDeviceMotionListener();
}
},
/**
* Stop logshake, removing all listeners
*/
uninit: function() {
this.stopDeviceMotionListener();
SystemAppProxy.removeEventListener(SCREEN_CHANGE_EVENT, this, false);
Services.obs.removeObserver(this, 'xpcom-shutdown');
}
};
function getLogFilename(logLocation) {
// sanitize the log location
let logName = logLocation.replace(/\//g, '-');
if (logName[0] === '-') {
logName = logName.substring(1);
}
return logName + '.log';
}
function getSdcardPrefix() {
return volumeService.getVolumeByName('sdcard').mountPoint;
}
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/'
return OS.Path.join('logs', timestamp, '');
}
/**
* Captures and saves the current device logs, returning a promise that will
* resolve to an array of log filenames.
*/
function captureLogs() {
let logArrays = readLogs();
return saveLogs(logArrays);
}
/**
* Read in all log files, returning their formatted contents
*/
function readLogs() {
let logArrays = {};
for (let loc in LOGS_WITH_PARSERS) {
let logArray = LogCapture.readLogFile(loc);
if (!logArray) {
continue;
}
let prettyLogArray = LOGS_WITH_PARSERS[loc](logArray);
logArrays[loc] = prettyLogArray;
}
return logArrays;
}
/**
* 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: ''
});
}
let sdcardPrefix, dirName;
try {
sdcardPrefix = getSdcardPrefix();
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 + '/' + dirName));
return OS.File.makeDir(OS.Path.join(sdcardPrefix, dirName), {from: sdcardPrefix})
.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);
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 = 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());
return {
logFilenames: logFilenames,
logPrefix: dirName
};
});
});
}
LogShake.init();
this.LogShake = LogShake;

View File

@ -52,6 +52,9 @@ EXTRA_JS_MODULES += [
'ContentRequestHelper.jsm',
'ErrorPage.jsm',
'FxAccountsMgmtService.jsm',
'LogCapture.jsm',
'LogParser.jsm',
'LogShake.jsm',
'SignInToWebsite.jsm',
'SystemAppProxy.jsm',
'TelURIParser.jsm',

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,25 @@
/* 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');
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);
}
let mainLog = LogCapture.readLogFile('/dev/log/main');
verifyLog(mainLog);
let meminfoLog = LogCapture.readLogFile('/proc/meminfo');
verifyLog(meminfoLog);
}

View File

@ -0,0 +1,49 @@
/* jshint moz: true */
const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
function run_test() {
Cu.import('resource:///modules/LogParser.jsm');
let propertiesFile = do_get_file('data/test_properties');
let loggerFile = do_get_file('data/test_logger_file');
let propertiesStream = makeStream(propertiesFile);
let loggerStream = makeStream(loggerFile);
// Initialize arrays to hold the file contents (lengths are hardcoded)
let propertiesArray = new Uint8Array(propertiesStream.readByteArray(65536));
let loggerArray = new Uint8Array(loggerStream.readByteArray(4037));
propertiesStream.close();
loggerStream.close();
let properties = LogParser.parsePropertiesArray(propertiesArray);
let logMessages = LogParser.parseLogArray(loggerArray);
// Test arbitrary property entries for correctness
equal(properties['ro.boot.console'], 'ttyHSL0');
equal(properties['net.tcp.buffersize.lte'],
'524288,1048576,2097152,262144,524288,1048576');
ok(logMessages.length === 58, 'There should be 58 messages in the log');
let expectedLogEntry = {
processId: 271, threadId: 271,
seconds: 790796, nanoseconds: 620000001, time: 790796620.000001,
priority: 4, tag: 'Vold',
message: 'Vold 2.1 (the revenge) firing up\n'
};
deepEqual(expectedLogEntry, logMessages[0]);
}
function makeStream(file) {
var fileStream = Cc['@mozilla.org/network/file-input-stream;1']
.createInstance(Ci.nsIFileInputStream);
fileStream.init(file, -1, -1, 0);
var bis = Cc['@mozilla.org/binaryinputstream;1']
.createInstance(Ci.nsIBinaryInputStream);
bis.setInputStream(fileStream);
return bis;
}

View File

@ -0,0 +1,141 @@
/**
* Test the log capturing capabilities of LogShake.jsm
*/
/* 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;
Cu.import('resource://gre/modules/LogCapture.jsm');
Cu.import('resource://gre/modules/LogShake.jsm');
// Force logshake to handle a device motion event with given components
// Does not use SystemAppProxy because event needs special
// accelerationIncludingGravity property
function sendDeviceMotionEvent(x, y, z) {
let event = {
type: 'devicemotion',
accelerationIncludingGravity: {
x: x,
y: y,
z: z
}
};
LogShake.handleEvent(event);
}
// Send a screen change event directly, does not use SystemAppProxy due to race
// conditions.
function sendScreenChangeEvent(screenEnabled) {
let event = {
type: 'screenchange',
detail: {
screenEnabled: screenEnabled
}
};
LogShake.handleEvent(event);
}
function debug(msg) {
var timestamp = Date.now();
dump('LogShake: ' + timestamp + ': ' + msg);
}
add_test(function test_do_log_capture_after_shaking() {
// Enable LogShake
LogShake.init();
let readLocations = [];
LogCapture.readLogFile = function(loc) {
readLocations.push(loc);
return null; // we don't want to provide invalid data to a parser
};
// Fire a devicemotion event that is of shake magnitude
sendDeviceMotionEvent(9001, 9001, 9001);
ok(readLocations.length > 0,
'LogShake should attempt to read at least one log');
LogShake.uninit();
run_next_test();
});
add_test(function test_do_nothing_when_resting() {
// Enable LogShake
LogShake.init();
let readLocations = [];
LogCapture.readLogFile = function(loc) {
readLocations.push(loc);
return null; // we don't want to provide invalid data to a parser
};
// Fire a devicemotion event that is relatively tiny
sendDeviceMotionEvent(0, 9.8, 9.8);
ok(readLocations.length === 0,
'LogShake should not read any logs');
debug('test_do_nothing_when_resting: stop');
LogShake.uninit();
run_next_test();
});
add_test(function test_do_nothing_when_disabled() {
debug('test_do_nothing_when_disabled: start');
// Disable LogShake
LogShake.uninit();
let readLocations = [];
LogCapture.readLogFile = function(loc) {
readLocations.push(loc);
return null; // we don't want to provide invalid data to a parser
};
// Fire a devicemotion event that would normally be a shake
sendDeviceMotionEvent(0, 9001, 9001);
ok(readLocations.length === 0,
'LogShake should not read any logs');
run_next_test();
});
add_test(function test_do_nothing_when_screen_off() {
// Enable LogShake
LogShake.init();
// Send an event as if the screen has been turned off
sendScreenChangeEvent(false);
let readLocations = [];
LogCapture.readLogFile = function(loc) {
readLocations.push(loc);
return null; // we don't want to provide invalid data to a parser
};
// Fire a devicemotion event that would normally be a shake
sendDeviceMotionEvent(0, 9001, 9001);
ok(readLocations.length === 0,
'LogShake should not read any logs');
// Restore the screen
sendScreenChangeEvent(true);
LogShake.uninit();
run_next_test();
});
function run_test() {
debug('Starting');
run_next_test();
}

View File

@ -2,6 +2,10 @@
head =
tail =
support-files =
data/test_logger_file
data/test_properties
[test_bug793310.js]
[test_bug832946.js]
@ -11,4 +15,10 @@ tail =
head = head_identity.js
tail =
[test_logcapture.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]