diff --git a/scripts/admin/build-tools-tree.sh b/scripts/admin/build-tools-tree.sh index c674f6d4b0..92e7df519c 100755 --- a/scripts/admin/build-tools-tree.sh +++ b/scripts/admin/build-tools-tree.sh @@ -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 diff --git a/scripts/run-tools-tests.sh b/scripts/run-tools-tests.sh deleted file mode 100644 index b78436e417..0000000000 --- a/scripts/run-tools-tests.sh +++ /dev/null @@ -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 diff --git a/tools/run-all.js b/tools/run-all.js index 5cc2499fba..ba5887e0c9 100644 --- a/tools/run-all.js +++ b/tools/run-all.js @@ -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; diff --git a/tools/run-app.js b/tools/run-app.js index 535a997373..3196c4601f 100644 --- a/tools/run-app.js +++ b/tools/run-app.js @@ -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") diff --git a/tools/run-mongo.js b/tools/run-mongo.js index a4e66e9a9a..7ac392aef4 100644 --- a/tools/run-mongo.js +++ b/tools/run-mongo.js @@ -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 diff --git a/tools/selftest.js b/tools/selftest.js index 050b56e043..419f4bdf98 100644 --- a/tools/selftest.js +++ b/tools/selftest.js @@ -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"); diff --git a/tools/tests/apps/once/.meteor/.gitignore b/tools/tests/apps/once/.meteor/.gitignore new file mode 100644 index 0000000000..4083037423 --- /dev/null +++ b/tools/tests/apps/once/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/tools/tests/apps/once/.meteor/packages b/tools/tests/apps/once/.meteor/packages new file mode 100644 index 0000000000..5d992a7f9a --- /dev/null +++ b/tools/tests/apps/once/.meteor/packages @@ -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 diff --git a/tools/tests/apps/once/.meteor/release b/tools/tests/apps/once/.meteor/release new file mode 100644 index 0000000000..621e94f0ec --- /dev/null +++ b/tools/tests/apps/once/.meteor/release @@ -0,0 +1 @@ +none diff --git a/tools/tests/apps/once/once.js b/tools/tests/apps/once/once.js new file mode 100644 index 0000000000..8791c8d171 --- /dev/null +++ b/tools/tests/apps/once/once.js @@ -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); +} diff --git a/tools/tests/apps/once/programs/other/.gitignore b/tools/tests/apps/once/programs/other/.gitignore new file mode 100644 index 0000000000..677a6fc263 --- /dev/null +++ b/tools/tests/apps/once/programs/other/.gitignore @@ -0,0 +1 @@ +.build* diff --git a/tools/tests/apps/once/programs/other/other.js b/tools/tests/apps/once/programs/other/other.js new file mode 100644 index 0000000000..ba2e072799 --- /dev/null +++ b/tools/tests/apps/once/programs/other/other.js @@ -0,0 +1,2 @@ +process.stdout.write("other program\n"); +process.exit(44); diff --git a/tools/tests/apps/once/programs/other/package.js b/tools/tests/apps/once/programs/other/package.js new file mode 100644 index 0000000000..e69d549c52 --- /dev/null +++ b/tools/tests/apps/once/programs/other/package.js @@ -0,0 +1,7 @@ +Package.describe({ + summary: "another program, for testing" +}); + +Package.on_use(function (api) { + api.add_files(["other.js"], 'server'); +}); diff --git a/tools/tests/releases.js b/tools/tests/releases.js index 4a49ab1f8b..2aa02a7747 100644 --- a/tools/tests/releases.js +++ b/tools/tests/releases.js @@ -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); }); diff --git a/tools/tests/run.js b/tools/tests/run.js index d910440590..c85e89f7e0 100644 --- a/tools/tests/run.js +++ b/tools/tests/run.js @@ -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"); }); +