Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,43 @@
/* global assert */
'use strict';
suite('apply-manifest', function() {
var exec = require('child_process').exec;
var ROOT = __dirname + '/../../';
var BIN = ROOT + '/bin/apply-manifest';
test('manifest-blacklist.json', function(done) {
var argv = [
BIN,
__dirname + '/fixtures/manifest-blacklist.json',
'test/bin/fixtures/manifest-test-skip.js',
'test/bin/fixtures/manifest-test-use.js'
];
exec(argv.join(' '), { cwd: ROOT }, function(err, stdout) {
if (err) return done(err);
assert.equal(
stdout.trim(),
__dirname + '/fixtures/manifest-test-use.js'
);
done();
});
});
test('manifest-whitelist.json', function(done) {
var argv = [
BIN,
__dirname + '/fixtures/manifest-whitelist.json',
'test/bin/fixtures/manifest-test-skip.js',
'test/bin/fixtures/manifest-test-use.js'
];
exec(argv.join(' '), { cwd: ROOT }, function(err, stdout) {
if (err) return done(err);
assert.equal(
stdout.trim(),
__dirname + '/fixtures/manifest-test-use.js'
);
done();
});
});
});

View file

@ -0,0 +1,18 @@
/* global spawnMarionette */
'use strict';
test('console.log --verbose', function(done) {
this.timeout('10s');
// Usual process spawning stuff the important bit here is this fixture will
// fail and never complete unless we wait for 100s or kill the process.
var proc = spawnMarionette([
'--verbose',
__dirname + '/fixtures/consolelog.js'
]);
proc.stdout.on('data', function(buffer) {
if (buffer.toString().indexOf('the fox say') !== -1) {
proc.once('exit', done.bind(null, null));
return;
}
});
});

View file

@ -0,0 +1,25 @@
/* global assert, spawnMarionette */
'use strict';
suite('Crash handling', function() {
this.timeout('20s');
var proc;
test('handle crash', function(done) {
// Usual process spawning stuff the important bit here is this fixture will
// fail and never complete unless we wait for 100s or kill the process.
proc = spawnMarionette([__dirname + '/fixtures/crash.js']);
var hasCrash;
proc.stderr.on('data', function(buffer) {
if (buffer.toString().indexOf('Crash detected ') !== -1) {
hasCrash = true;
}
});
proc.once('exit', function(code) {
assert(hasCrash, 'has crash message');
assert(code !== 0, 'must exit in error');
done();
});
});
});

View file

@ -0,0 +1,17 @@
'use strict';
// Test here is only to test SIGINT functionality.
marionette('stay blocked', function() {
var client = marionette.client();
test('block', function() {
// Ensure we don't die on any startup errors...
try {
client.executeScript(function() {});
} catch (e) {}
client.setScriptTimeout(1000000);
client.executeAsyncScript(function() {
});
});
});

View file

@ -0,0 +1,10 @@
'use strict';
function Builder() {}
Builder.prototype = {
build: function(callback) {
// magic process exit number to indicate that this file was loaded.
process.exit(66);
}
};
module.exports = Builder;

View file

@ -0,0 +1,3 @@
{
"desiredCapability": true
}

View file

@ -0,0 +1,18 @@
'use strict';
/**
* @fileoverview A marionette test that simply logs something in gecko.
*/
suite('console proxy', function() {
this.timeout('10s');
var client = marionette.client();
test('log in host', function(done) {
// logging is asynchronous so wait a bit longer then just the execution
// so we actually get some logs...
client.executeScript(function() {
console.log('What does the fox say');
});
setTimeout(done, 2000);
});
});

View file

@ -0,0 +1,22 @@
/* global Components, ctypes */
'use strict';
suite('crash', function() {
var client = marionette.client();
test('crash me', function() {
client.scope({ context: 'chrome' }).executeScript(function() {
const Cu = Components.utils;
Cu.import('resource://gre/modules/ctypes.jsm');
var dies = function() {
var zero = new ctypes.intptr_t(8);
var badptr = ctypes.cast(zero, ctypes.PointerType(ctypes.int32_t));
badptr.contents;
};
dump('Et tu, Brute?');
dies();
});
});
});

View file

@ -0,0 +1,5 @@
{
"phone": "test on phone",
"tv": "test on tv",
"common": "test on both"
}

View file

@ -0,0 +1,32 @@
'use strict';
var Promise = require('promise');
var assert = require('assert');
exports.help = {
group: {
title: 'CUSTOM XFOO',
description: 'CUSTOM DESC'
},
arguments: {
'--code': {
type: 'int',
help: 'exit code'
}
}
};
var expectedHost = {
destroy: function() {
return Promise.resolve();
}
};
module.exports.createHost = function() {
return Promise.resolve(expectedHost);
};
module.exports.createSession = function(host, profile, opts) {
assert.equal(host, expectedHost);
process.exit(opts.code);
return Promise.resolve();
};

View file

@ -0,0 +1,5 @@
{
"blacklist": {
"test/bin/fixtures/manifest-test-skip.js": "Bug 12345 - whatever bug I want."
}
}

View file

@ -0,0 +1,4 @@
'use strict';
marionette('test', {}, function() {
process.exit(13);
});

View file

@ -0,0 +1,4 @@
'use strict';
marionette('test', {}, function() {
process.exit(13);
});

View file

@ -0,0 +1,4 @@
'use strict';
marionette('test', {}, function() {
process.exit(23);
});

View file

@ -0,0 +1,3 @@
{
"whitelist": ["test/bin/fixtures/manifest-test-use.js"]
}

View file

@ -0,0 +1,15 @@
marionette('device test', function() {
var client = marionette.client();
test('test on phone', { devices: ['phone'] }, function() {
1 + 1;
});
test('test on tv', { devices: ['tv'] }, function() {
1 + 1;
});
test('test on both', { devices: ['phone', 'tv'] }, function() {
1 + 1;
});
});

View file

@ -0,0 +1,8 @@
'use strict';
marionette('test', function() {
var client = marionette.client();
test('1+1', function() {
1 + 1;
});
});

View file

@ -0,0 +1,257 @@
/* global assert, spawnMarionette, spawnMocha */
'use strict';
suite('mocha integration', function() {
this.timeout('30s');
var fs = require('fs');
var fsPath = require('path');
function aggregateOutput(childProcess) {
var result = {
stderr: '',
stdout: ''
};
childProcess.stderr.on('data', function(out) {
result.stderr += out;
});
childProcess.stdout.on('data', function(out) {
result.stdout += out;
});
return result;
}
var MS_REGEXP = /(([0-9]+) ?ms)/;
var NEW_LINES = /(\n|(\s{2,}))/g;
function waitForProcess(child, done) {
var result = aggregateOutput(child);
child.on('exit', function(code) {
// there are very small newline/whitespace differences between
// mocha and our marionette reporter... these probably are not
// bugs but prevent us from verifying real content so they are stripped.
['stderr', 'stdout'].forEach(function(field) {
[MS_REGEXP, NEW_LINES].forEach(function(regex) {
result[field + 'Raw'] = result[field];
result[field] = result[field].replace(regex, '').trim();
});
});
// exit status is _really_ important
result.code = code;
done();
});
return result;
}
var tests = [
// this also tests picking up mocha.opts
['test', ['--reporter', 'spec']],
['test', [
'--reporter',
fsPath.resolve(
__dirname,
'/../../node_modules/mocha/lib/reporters/spec'
)
]],
['pending', ['--reporter', 'spec']],
['with-helper', [
'--require', fsPath.resolve(__dirname + '/../fixtures/helper.js'),
'--reporter', 'spec'
]
]
];
tests.forEach(function(pair) {
var file = pair[0];
var path = fsPath.resolve(__dirname + '/../fixtures/' + file);
var argv = [path].concat(pair[1]);
// run same test with same options on both mocha & our marionette proxy
// runner.
suite(file, function() {
var mochaOut;
var marionetteOut;
setup(function(done) {
var proc = spawnMocha(argv);
mochaOut = waitForProcess(proc, done);
});
setup(function(done) {
var proc = spawnMarionette(argv);
marionetteOut = waitForProcess(proc, done);
});
test('code', function() {
assert.equal(
JSON.stringify(mochaOut.code), JSON.stringify(marionetteOut.code)
);
});
test('stdout', function() {
assert.equal(mochaOut.stdout, marionetteOut.stdout);
});
test.skip('stderr', function() {
assert.equal(mochaOut.stderr, marionetteOut.stderr);
});
});
});
suite('--host', function() {
var result;
var argv = ['--host', __dirname + '/fixtures/host'];
var Host = require('./fixtures/host');
suite('when passing --help', function() {
setup(function(done) {
var proc = spawnMarionette(argv.concat(['--help']));
result = waitForProcess(proc, done);
});
test('custom help is shown', function() {
assert.ok(result.stdoutRaw.indexOf(Host.help.group.title) !== -1);
assert.ok(result.stdoutRaw.indexOf(Host.help.group.description) !== -1);
assert.ok(result.stdoutRaw.indexOf('--code') !== -1);
});
});
suite('run a test', function() {
setup(function(done) {
var proc = spawnMarionette(argv.concat([
__dirname + '/fixtures/marionettetest',
'--code',
'55'
]));
result = waitForProcess(proc, done);
});
test('exits with magic code', function() {
assert.equal(result.code, 55, JSON.stringify(result));
});
});
});
suite('--profile-builder', function() {
var result,
argv = [
'--profile-builder', __dirname + '/fixtures/builder',
__dirname + '/fixtures/marionettetest'
];
setup(function(done) {
var proc = spawnMarionette(argv);
result = waitForProcess(proc, done);
});
test('exits with magic code', function() {
assert.equal(result.code, 66, JSON.stringify(result));
});
});
suite('--marionette-capabilities', function() {
var result,
capabilitiesPath = __dirname + '/fixtures/capabilities.json',
expected = JSON.stringify({ capabilities: require(capabilitiesPath) }),
argv = [
'--marionette-capabilities', capabilitiesPath,
'--host-log', 'stdout',
__dirname + '/fixtures/marionettetest'
];
setup(function(done) {
var proc = spawnMarionette(argv);
result = waitForProcess(proc, done);
});
test('desired capabilities are set', function() {
assert.ok(result.stdoutRaw.indexOf(expected) !== -1);
});
});
suite('--device-type', function() {
var result,
devicesPath = __dirname + '/fixtures/devices.json',
tests = require(devicesPath),
argv = [
'--device-type', 'phone',
'--host-log', 'stdout',
__dirname + '/fixtures/marionettedevicetest'
];
setup(function(done) {
var proc = spawnMarionette(argv);
result = waitForProcess(proc, done);
});
test('desired devices is being tested', function() {
assert.ok(result.stdoutRaw.indexOf(tests.phone) !== -1);
assert.ok(result.stdoutRaw.indexOf(tests.common) !== -1);
assert.ok(result.stdoutRaw.indexOf(tests.tv) === -1);
});
});
suite('--host-log=stdout', function() {
var result,
argv = [
'--host-log', 'stdout',
__dirname + '/fixtures/marionettetest'
];
setup(function(done) {
var proc = spawnMarionette(argv);
result = waitForProcess(proc, done);
});
test('has marionette output', function() {
assert.ok(result.stdout.indexOf('newSession') !== -1);
assert.ok(result.stderr.indexOf('newSession') === -1);
});
});
suite('--host-log=stderr', function() {
var result,
argv = [
'--host-log', 'stderr',
__dirname + '/fixtures/marionettetest'
];
setup(function(done) {
var proc = spawnMarionette(argv);
result = waitForProcess(proc, done);
});
test('has marionette output', function() {
assert.ok(result.stderr.indexOf('newSession') !== -1);
assert.ok(result.stdout.indexOf('newSession') !== 0);
});
});
suite('--host-log=<file>', function() {
var file = __dirname + '/log.txt';
var result,
argv = [
'--host-log', file,
__dirname + '/fixtures/marionettetest'
];
setup(function(done) {
var proc = spawnMarionette(argv);
result = waitForProcess(proc, done);
});
teardown(function() {
fs.unlinkSync(file);
});
test('has marionette output', function() {
assert.ok(fs.existsSync(file));
assert.ok(fs.readFileSync(file, 'utf8').indexOf('newSession') !== -1);
});
});
});

View file

@ -0,0 +1,122 @@
/* global spawnMarionette */
'use strict';
suite('SIGINT', function() {
var exec = require('child_process').exec;
var assert = require('assert');
// Cleanup pid line (works for columns and value lines)
function splitPidLine(line) {
return line.trim().replace(/\s+/g, ' ').split(' ');
}
// Pretty much what you would expect out of a ps parser.
function ps(callback) {
// ppid is important here for the intent of this test but aside from the
// flags this code is actually could be general purpose.
exec('ps -o comm -o pid -o ppid', function(err, stdout) {
if (err) return callback(err);
var lines = stdout.replace('\r', '').split('\n');
var columns = splitPidLine(lines.shift());
var result = [];
lines.forEach(function(line) {
var lineParts = splitPidLine(line);
if (lineParts.length < columns.length) return;
var obj = {};
lineParts.forEach(function(part, idx) {
// lower case to better fit node conventions...
if (!columns[idx]) {
return;
}
obj[columns[idx].toLowerCase()] = part;
});
result.push(obj);
});
callback(null, result);
});
}
// Find all processes which share a starting process parent.
function recursivePpidSearch(pid, callback) {
ps(function(err, list) {
if (err) return callback(err);
var result = [];
// Yes this can stack overflow be very careful if this is used elsewhere.
function findPpid(searchPid) {
list.forEach(function(item) {
if (item.ppid == searchPid) {
findPpid(item.pid);
result.push(item);
}
});
}
findPpid(pid);
return callback(null, result);
});
}
function ensureKill(testFile, waitTime) {
suite('SIGKILL ' + testFile + ' after ' + waitTime + ' ms', function() {
var proc, processList, pid;
setup(function(done) {
// Usual process spawning stuff the important bit here is this fixture
// will fail and never complete unless we wait for 100s or kill the
// process.
proc = spawnMarionette([testFile]);
proc.stdout.pipe(process.stdout);
proc.stderr.pipe(process.stderr);
pid = proc.pid;
// Wait for all the created processes to finish cleanly
// - b2g process
// - child _mocha process
// - proxy reporter process
var processes = 3;
function search() {
recursivePpidSearch(process.pid, function(err, list) {
if (err) return done(err);
if (list.length < processes) return setTimeout(search);
processList = list;
setTimeout(done, waitTime);
});
}
search();
});
test('cleanly sigkill with no leftovers', function(done) {
proc.kill('SIGINT');
proc.once('exit', function(code) {
ps(function(err, currentProcesses) {
if (err) return done(err);
var remaining = processList.filter(function(startProc) {
for (var i = 0; i < currentProcesses.length; i++) {
if (currentProcesses[i].pid == startProc.pid) return true;
}
return false;
});
assert(
!remaining.length,
'Process still running: ' + JSON.stringify(remaining)
);
done();
});
});
});
});
}
// Basically instantly.
ensureKill(__dirname + '/../integration/multiclient_test', 10);
// Very early in the startup.
ensureKill(__dirname + '/../integration/multiclient_test', 100);
// Sessions are probably being created.
ensureKill(__dirname + '/../integration/multiclient_test', 500);
// Marionette is blocked.
ensureKill(__dirname + '/fixtures/blocked', 500);
});

View file

@ -0,0 +1,7 @@
curl http://nodejs.org/dist/v0.10.33/node-v0.10.33-linux-x64.tar.gz | tar xz
node_path=$(find $PWD -d -name 'node-v*' | head -n 1)
export PATH=$node_path:$PATH
Xvfb :99 &
export DISPLAY=:99
make test

View file

@ -0,0 +1,15 @@
/* global assert */
'use strict';
suite('error_ipc', function() {
var subject = require('../lib/error_ipc');
test('roundtrip', function() {
var error = new Error('xfoo');
var out = subject.deserialize(subject.serialize(error));
assert(out instanceof Error, 'instanceof Error');
assert.equal(out.message, error.message);
assert.equal(out.stack, error.stack);
});
});

View file

@ -0,0 +1 @@
global.MAGIC_GLOBAL_VARIABLE = true;

View file

@ -0,0 +1,4 @@
'use strict';
suite('pending', function() {
test('iampending');
});

View file

@ -0,0 +1,33 @@
'use strict';
var RPC = require('../../lib/rpc');
var Promise = require('promise');
var rpc = new RPC(process.send.bind(process));
rpc.register('test', {
$rpc: { methods: ['args'] },
getSelf: function() {
return Promise.resolve(this);
},
noArgs: function() {
return Promise.resolve();
},
args: function() {
var args = Array.prototype.slice.call(arguments);
return new Promise(function(accept) {
return accept(args);
});
},
error: function() {
return new Promise(function() {
throw new Error('xfoo');
});
}
});
var handle = rpc.handle();
process.on('message', handle);

View file

@ -0,0 +1,19 @@
'use strict';
suite('a test', function() {
console.log('before running test log');
test('test one', function() {
});
suite('another suite fail', function() {
test('throw', function() {
throw new Error('xxx');
});
});
test('multiple console logs', function(done) {
setTimeout(function() {
console.log('works');
done();
});
});
});

View file

@ -0,0 +1 @@
--magicfoobar --dodad

View file

@ -0,0 +1,2 @@
--one
--two

View file

@ -0,0 +1,4 @@
'use strict';
test('global variable', function() {
assert.ok(MAGIC_GLOBAL_VARIABLE);
});

View file

@ -0,0 +1,61 @@
'use strict';
var EventEmitter = require('events').EventEmitter;
var assert = require('chai').assert;
var spawn = require('child_process').spawn;
global.assert = assert;
global.sinon = require('sinon');
// so we can call assert.calledWith
global.sinon.assert.expose(assert, { prefix: '' });
function env() {
var vars = {};
for (var key in process.env) vars[key] = process.env[key];
vars.MOCHA_COLORS = '0';
return vars;
}
function spawnMocha(argv) {
var mocha = __dirname + '/../../.bin/mocha';
return spawn(mocha, argv, { env: env() });
}
function spawnMarionette(argv) {
var bin = __dirname + '/../bin/marionette-mocha';
argv.push('--runtime');
argv.push(__dirname + '/../../../b2g/b2g-bin');
return spawn(bin, argv, { env: env() });
}
function mockProcessSend() {
var refs = {},
originalSend;
// spy on process.send
setup(function() {
var sent = refs.sent = new EventEmitter();
originalSend = process.send;
process.send = function(event) {
if (Array.isArray(event) && typeof event[0] === 'string') {
sent.emit.apply(sent, event);
}
// if there is a send method use it.
if (originalSend) {
originalSend.apply(this, arguments);
}
};
});
teardown(function() {
process.send = originalSend;
});
return refs;
}
global.mockProcessSend = mockProcessSend;
global.spawnMocha = spawnMocha;
global.spawnMarionette = spawnMarionette;

View file

@ -0,0 +1,33 @@
'use strict';
var assert = require('assert');
marionette('default client', function() {
// create a client.
var client = marionette.client();
test('is sync', function() {
var result = client.executeScript(function() {
// attempt to dirty global state
window.wrappedJSObject.I_WAS_BAD = true;
// do complicated math in gecko
return 1 + 1;
});
// math works
assert.equal(result, 2, 'tests are async');
// state is saved inside of same test
var bad = client.executeScript(function() {
return window.wrappedJSObject.I_WAS_BAD;
});
assert.ok(bad, 'state is saved');
});
test('host is reset is each test', function() {
var bad = client.executeScript(function() {
return window.wrappedJSObject.I_WAS_BAD;
});
assert.ok(!bad, 'state is clean');
});
});

View file

@ -0,0 +1,23 @@
'use strict';
var assert = require('assert');
marionette('client with options', function() {
// create a client.
var options = {
screen: {
width: 353,
height: 577
}
};
var client = marionette.client({ profile: { hostOptions: options } });
test('screen size', function() {
var result = client.executeScript(function() {
return { width: window.innerWidth,
height: window.innerHeight};
});
assert.equal(result.width, options.screen.width);
assert.equal(result.height, options.screen.height);
});
});

View file

@ -0,0 +1,22 @@
'use strict';
var assert = require('assert');
marionette('client async', function() {
var Tcp = require('marionette-client').Drivers.Tcp;
var client = marionette.client({ driver: Tcp });
function onePlusOne() {
return 1 + 1;
}
test('async magic', function(done) {
var isAsync = false;
client.executeScript(onePlusOne, function(err, result) {
if (!isAsync) return done(new Error('is sync!'));
assert.equal(result, 2);
done();
});
isAsync = true;
});
});

View file

@ -0,0 +1,17 @@
'use strict';
var assert = require('assert');
marionette('client with capabilities', function() {
// create a client and set desired capabilities.
var client = marionette.client({
desiredCapabilities: { desiredCapability: true }
});
test('test capabilities', function(done) {
client.sessionCapabilities(function(err, capabilities) {
// Capabilities are set
assert.ok(capabilities.desiredCapability);
done();
});
});
});

View file

@ -0,0 +1,27 @@
'use strict';
var assert = require('assert');
marionette('multi-client', function() {
var one = marionette.client();
var two = marionette.client();
test('ports are not equal', function() {
assert.notEqual(
one.driver.port,
two.driver.port
);
});
// run a quick test to verify client works
function validateClient(name, client) {
test(name, function() {
var result = client.executeScript(function() {
return 1 + 1;
});
assert.equal(result, 2);
});
}
validateClient('first client', one);
validateClient('second client', two);
});

View file

@ -0,0 +1,56 @@
'use strict';
var assert = require('assert');
// Global plugin
marionette.plugin('globalme', function() {
return { isGlobal: true };
});
marionette('plugins', function() {
function PluginA(client, options) {
return PluginA;
}
var pluginId = 0;
function PluginB(client, options) {
return { id: ++pluginId };
}
// create global plugin
marionette.plugin('a', PluginA);
marionette.plugin('b', PluginB);
var client = marionette.client();
test('global plugin is exposed', function() {
assert(client.globalme.isGlobal);
});
test('a is exposed', function() {
assert.strictEqual(client.a, PluginA);
});
test('plugins are created for each test', function() {
assert.equal(client.b.id, 3);
});
suite('overrides plugin a', function() {
function MyA() { return MyA; }
marionette.plugin('a', MyA);
test('can override plugins', function() {
assert.strictEqual(client.a, MyA);
});
});
suite('a is reset after suite to original', function() {
test('after other suite', function() {
assert.deepEqual(client.a, PluginA);
});
test('b is still plugin b', function() {
assert.equal(client.b.id, 6);
});
});
});

View file

@ -0,0 +1,26 @@
/* global assert */
'use strict';
suite('optsfile', function() {
var optsfile = require('../lib/optsfileparser');
/**
* @param {String} file to load with optsfile relative to fixtures.
*/
function load(file) {
return optsfile(__dirname + '/fixtures/' + file);
}
test('parsing valid optsfile', function() {
assert.deepEqual(
load('testops.opts'),
['--magicfoobar', '--dodad']
);
});
test('optsfile with newlines', function() {
assert.deepEqual(
load('testopsnewline.opts'),
['--one', '--two']
);
});
});

View file

@ -0,0 +1,66 @@
'use strict';
suite('rpc', function() {
var RPC = require('../lib/rpc');
var fork = require('child_process').fork;
var assert = require('assert');
var proc;
suiteSetup(function() {
proc = fork(__dirname + '/fixtures/rpc');
});
suiteTeardown(function() {
proc.kill();
});
var subject, client;
setup(function() {
subject = new RPC(proc.send.bind(proc));
client = subject.client('test', ['noArgs', 'args', 'error', 'getSelf']);
proc.on('message', subject.handle());
});
test('noArgs', function() {
return client.noArgs();
});
test('args', function() {
return client.args(1, 2, 3, 4).then(function(result) {
assert.deepEqual(result, [1, 2, 3, 4]);
});
});
test('objects', function() {
var obj;
return client.getSelf().then(function(rpcObj) {
obj = rpcObj;
return rpcObj.args('woot', 'bar');
}).then(function(value) {
assert.deepEqual(value, ['woot', 'bar']);
}).then(function() {
assert(obj.destroy, 'has implicit destroy method');
return obj.destroy();
}).then(function() {
return obj.args();
}).then(
function() { throw new Error('expected error'); },
function(err) {
assert.ok(err.message.indexOf('destroyed') !== -1);
}
);
});
test('error', function() {
return client.error()
.then(function() {
process.nextTick(function() {
throw new Error('must not resolve');
});
})
.catch (function(err) {
assert.equal(err.message, 'xfoo');
});
});
});