First installment of 'meteor run' tests.

Tests meteor --once and restarting on upgrade.
This commit is contained in:
Geoff Schmidt
2014-01-29 02:02:17 -08:00
parent 377d940ba0
commit e1994f6ece
15 changed files with 223 additions and 52 deletions

View File

@@ -44,8 +44,7 @@ CPR examples "$TARGET_DIR"
# Script is not actually used, but it's nice to distribute it for users.
cp scripts/admin/launch-meteor "$TARGET_DIR"
# Trim tests and unfinished examples.
rm -rf "$TARGET_DIR"/tools/tests
# Trim unfinished examples.
rm -rf "$TARGET_DIR"/examples/unfinished
rm -rf "$TARGET_DIR"/examples/other

View File

@@ -1,14 +0,0 @@
#!/bin/bash
# XXX does anyone call this script anymore? can it be removed? former
# users should invoke 'meteor self-test' directly
# Die with message on failure, print commands being executed
trap 'echo FAILED' EXIT
set -e -u -x
cd `dirname $0`
./meteor self-test --slow
trap - EXIT
echo PASSED

View File

@@ -22,8 +22,8 @@ var Runner = function (appDir, options) {
throw new Error("no port?");
var listenPort = options.port;
var appPort = listenPort + 1;
var mongoPort = listenPort + 2;
var mongoPort = listenPort + 1;
var appPort = 20000 + Math.floor(Math.random() * 10000);
self.stopped = false;
self.quiet = options.quiet;

View File

@@ -51,6 +51,7 @@ var AppProcess = function (options) {
self.bundlePath = options.bundlePath;
self.port = options.port;
self.rootUrl = options.rootUrl;
self.mongoUrl = options.mongoUrl;
self.oplogUrl = options.oplogUrl;
self.runLog = options.runLog;
@@ -102,7 +103,8 @@ _.extend(AppProcess.prototype, {
});
// Watch for exit
self.proc.on('close', function (code, signal) {
var thisPid = self.proc.pid;
self.proc.on('exit', function (code, signal) {
if (! self.madeExitCallback)
self.onExit && self.onExit(code, signal);
self.madeExitCallback = true;
@@ -137,7 +139,8 @@ _.extend(AppProcess.prototype, {
}, 2000);
},
// Idempotent
// Idempotent. Once stop() returns it is guaranteed that you will
// receive no more callbacks from this AppProcess.
stop: function () {
var self = this;
@@ -150,6 +153,9 @@ _.extend(AppProcess.prototype, {
if (self.keepaliveTimer)
clearInterval(self.keepaliveTimer);
self.keepaliveTimer = null;
self.onListen = null;
self.onExit = null;
},
_computeEnvironment: function () {
@@ -203,14 +209,17 @@ _.extend(AppProcess.prototype, {
return;
if (! archinfo.matches(archinfo.host(), p.arch))
return; // can't run here
programPath = path.join(options.bundlePath, p.path);
programPath = path.join(self.bundlePath, p.path);
});
if (! programPath)
return null;
console.log(programPath);
return child_process.spawn(programPath, [], {
env: self._computeEnvironment()
env: _.extend(self._computeEnvironment(), {
DATA_DIR: files.mkdtemp()
})
});
}
}
@@ -435,12 +444,14 @@ _.extend(AppRunner.prototype, {
oplogUrl: self.oplogUrl,
runLog: self.runLog,
onExit: function (code, signal) {
self.runFuture['return']({
outcome: 'terminated',
code: code,
signal: signal,
bundleResult: bundleResult
});
if (self.runFuture) {
self.runFuture['return']({
outcome: 'terminated',
code: code,
signal: signal,
bundleResult: bundleResult
});
}
},
program: self.program,
onListen: function () {
@@ -463,10 +474,12 @@ _.extend(AppRunner.prototype, {
watcher = new watch.Watcher({
watchSet: watchSet,
onChange: function () {
self.runFuture['return']({
outcome: 'changed',
bundleResult: bundleResult
});
if (self.runFuture) {
self.runFuture['return']({
outcome: 'changed',
bundleResult: bundleResult
});
}
}
});
}
@@ -489,7 +502,7 @@ _.extend(AppRunner.prototype, {
var crashCount = 0;
var crashTimer = null;
var firstListen = true;
var firstRun = true;
while (true) {
var crashTimer = setTimeout(function () {
@@ -498,11 +511,10 @@ _.extend(AppRunner.prototype, {
var runResult = self._runOnce(function () {
/* onListen */
if (! self.noRestartBanner && ! firstListen)
if (! self.noRestartBanner && ! firstRun)
self.runLog.logRestart();
firstListen = false;
});
firstRun = false;
clearTimeout(crashTimer);
if (runResult.outcome !== "terminated")

View File

@@ -189,16 +189,18 @@ var launchMongo = function (options) {
var portFile = path.join(dbPath, 'METEOR-PORT');
var portFileExists = false;
var createReplSet;
var createReplSet = true;
try {
createReplSet = +(fs.readFileSync(portFile)) !== options.port &&
! noOplog;
createReplSet = +(fs.readFileSync(portFile)) !== options.port;
portFileExists = true;
} catch (e) {
if (!e || e.code !== 'ENOENT')
throw e;
}
if (noOplog)
createReplSet = false;
// If this is the first time we're using this DB, or we changed
// port since the last time, then we want to destroying any
// existing replSet configuration and create a new one. First we

View File

@@ -552,7 +552,6 @@ var Run = function (execPath, options) {
self.fakeMongoPort = 20000 + Math.floor(Math.random() * 10000);
self.env.METEOR_TEST_FAKE_MONGOD_CONTROL_PORT = self.fakeMongoPort;
}
};
_.extend(Run.prototype, {
@@ -594,7 +593,6 @@ _.extend(Run.prototype, {
f['return']();
});
self.outputLog.end();
self.stdoutMatcher.end();
self.stderrMatcher.end();
},
@@ -782,8 +780,10 @@ _.extend(Run.prototype, {
if (! self.fakeMongoPort)
throw new Error("fakeMongo option on sandbox must be set");
self._ensureStarted();
// If it's the first time we've called tellMongo on this sandbox,
// open a connection to fake-mongod. Wait up to 5 seconds for it
// open a connection to fake-mongod. Wait up to 2 seconds for it
// to accept the connection, retrying every 100ms.
//
// XXX we never clean up this connection. Hopefully once
@@ -795,7 +795,7 @@ _.extend(Run.prototype, {
var net = require('net');
var lastStartTime = 0;
for (var attempts = 0; ! self.fakeMongoConnection && attempts < 50;
for (var attempts = 0; ! self.fakeMongoConnection && attempts < 20;
attempts ++) {
// Throttle attempts to one every 100ms
utils.sleep((lastStartTime + 100) - (+ new Date));
@@ -827,7 +827,7 @@ _.extend(Run.prototype, {
}
if (! self.fakeMongoConnection)
throw new TestFailure("mongo-not-running");
throw new TestFailure("mongo-not-running", { run: self });
}
self.fakeMongoConnection.write(JSON.stringify(command) + "\n");
@@ -1010,6 +1010,7 @@ var runTests = function (options) {
}
if (failure.details.run) {
failure.details.run.outputLog.end();
var lines = failure.details.run.outputLog.get();
if (! lines.length) {
process.stderr.write(" => No output\n");

View File

@@ -0,0 +1 @@
local

View File

@@ -0,0 +1,6 @@
# Meteor packages used by this project, one per line.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
standard-app-packages

View File

@@ -0,0 +1 @@
none

View File

@@ -0,0 +1,19 @@
process.stdout.write("once test\n");
if (process.env.RUN_ONCE_OUTCOME === "exit")
process.exit(123);
if (process.env.RUN_ONCE_OUTCOME === "kill") {
process.kill(process.pid, 'SIGKILL');
}
if (process.env.RUN_ONCE_OUTCOME === "hang") {
// The outstanding timeout will prevent node from exiting
setTimeout(function () {}, 365 * 24 * 60 * 60);
}
if (process.env.RUN_ONCE_OUTCOME === "mongo") {
var test = new Meteor.Collection('test');
test.insert({ value: 86 });
process.exit(test.findOne().value);
}

View File

@@ -0,0 +1 @@
.build*

View File

@@ -0,0 +1,2 @@
process.stdout.write("other program\n");
process.exit(44);

View File

@@ -0,0 +1,7 @@
Package.describe({
summary: "another program, for testing"
});
Package.on_use(function (api) {
api.add_files(["other.js"], 'server');
});

View File

@@ -111,7 +111,7 @@ selftest.define("checkout", ['checkout'], function () {
s.write(".meteor/release", "something");
run = s.run("list", "--using");
run.readErr("=> Running Meteor from a checkout");
run.matchErr("project version (something)\n\n");
run.matchErr("project version (something)\n");
run.expectExit(0);
});

View File

@@ -1,17 +1,151 @@
var selftest = require('../selftest.js');
var Sandbox = selftest.Sandbox;
var utils = require('../utils.js');
selftest.define("x", function () {
var MONGO_LISTENING =
{ stdout: " [initandlisten] waiting for connections on port" };
var SIMPLE_WAREHOUSE = {
v1: { tools: 'tools1' },
v2: { tools: 'tools1', latest: true },
v3: { tools: 'tools1' },
};
selftest.define("run --once", function () {
var s = new Sandbox({ fakeMongo: true });
var run;
s.createApp("onceapp", "once");
s.cd("onceapp");
// Basic run --once
s.set("RUN_ONCE_OUTCOME", "exit");
run = s.run("--once");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(5);
run.match("once test\n");
run.expectExit(123);
// run --once, exit on signal
s.set("RUN_ONCE_OUTCOME", "kill");
run = s.run("--once");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(5);
run.match("once test\n");
run.matchErr("Killed (SIGKILL)\n");
run.expectExit(255);
// run --once, bundle failure
s.set("RUN_ONCE_OUTCOME", "exit");
s.write("junk.js", "]");
run = s.run("--once");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(5);
run.matchErr("Build failed");
run.matchErr("Unexpected token");
run.expectExit(254);
s.unlink("junk.js");
// file changes don't make it restart
s.set("RUN_ONCE_OUTCOME", "hang");
run = s.run("--once");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(5);
run.match("once test\n");
s.write('empty.js', 'null');
s.write('.meteor/release', 'v1');
utils.sleep(2); // sorry, hard to avoid
run.stop();
run.forbidAll("updated");
s.unlink('empty.js');
s.write('.meteor/release', 'none');
// running a different program
run = s.run("--once", "--program", "other");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(5);
run.match("other program\n");
run.expectExit(44);
// bad program name
run = s.run("--once", "--program", "xyzzy");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(5);
run.match("xyzzy");
run.expectExit(254);
// Try it with a real Mongo. Make sure that it actually starts one.
s = new Sandbox;
s.createApp("onceapp", "once");
s.cd("onceapp");
s.set("RUN_ONCE_OUTCOME", "mongo");
run = s.run("--once");
run.waitSecs(5);
run.expectExit(86);
});
selftest.define("update during run", ["checkout"], function () {
var s = new Sandbox({
warehouse: SIMPLE_WAREHOUSE,
fakeMongo: true
});
var run;
s.createApp("myapp", "empty");
s.cd("myapp");
var run = s.run();
run.match('');
run.tellMongo({ stdout: " [initandlisten] waiting for connections on port" });
run.tellMongo({ exit: 99 });
run.waitSecs(0);
run.expectExit(123);
// If the app version changes, we exit with an error message.
s.write('.meteor/release', 'v1');
run = s.run();
run.tellMongo(MONGO_LISTENING);
run.waitSecs(2)
run.match('localhost:3000');
s.write('.meteor/release', 'v2');
run.matchErr('to Meteor v2 from Meteor v1');
run.expectExit(254);
// But not if the release was forced (case 1)
s.write('.meteor/release', 'v1');
run = s.run("--release", "v3");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(2)
run.match('localhost:3000');
s.write('.meteor/release', 'v2');
s.write('empty.js', '');
run.waitSecs(2)
run.match('restarted');
run.stop();
run.forbidAll("updated");
// But not if the release was forced (case 2)
s.write('.meteor/release', 'v1');
run = s.run("--release", "v1");
run.tellMongo(MONGO_LISTENING);
run.waitSecs(2)
run.match('localhost:3000');
s.write('.meteor/release', 'v2');
s.write('empty.js', '');
run.waitSecs(2)
run.match('restarted');
run.stop();
run.forbidAll("updated");
// Nor do we do it if you're running from a checkout
s = new Sandbox({ fakeMongo: true });
s.createApp("myapp", "empty");
s.cd("myapp");
s.write('.meteor/release', 'v1');
run = s.run();
run.tellMongo(MONGO_LISTENING);
run.waitSecs(2)
run.match('localhost:3000');
s.write('.meteor/release', 'v2');
s.write('empty.js', '');
run.waitSecs(2)
run.match('restarted');
run.stop();
run.forbidAll("updated");
});