mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
First installment of 'meteor run' tests.
Tests meteor --once and restarting on upgrade.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
1
tools/tests/apps/once/.meteor/.gitignore
vendored
Normal file
1
tools/tests/apps/once/.meteor/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
local
|
||||
6
tools/tests/apps/once/.meteor/packages
Normal file
6
tools/tests/apps/once/.meteor/packages
Normal 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
|
||||
1
tools/tests/apps/once/.meteor/release
Normal file
1
tools/tests/apps/once/.meteor/release
Normal file
@@ -0,0 +1 @@
|
||||
none
|
||||
19
tools/tests/apps/once/once.js
Normal file
19
tools/tests/apps/once/once.js
Normal 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);
|
||||
}
|
||||
1
tools/tests/apps/once/programs/other/.gitignore
vendored
Normal file
1
tools/tests/apps/once/programs/other/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.build*
|
||||
2
tools/tests/apps/once/programs/other/other.js
Normal file
2
tools/tests/apps/once/programs/other/other.js
Normal file
@@ -0,0 +1,2 @@
|
||||
process.stdout.write("other program\n");
|
||||
process.exit(44);
|
||||
7
tools/tests/apps/once/programs/other/package.js
Normal file
7
tools/tests/apps/once/programs/other/package.js
Normal file
@@ -0,0 +1,7 @@
|
||||
Package.describe({
|
||||
summary: "another program, for testing"
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.add_files(["other.js"], 'server');
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user