Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Initial commit

  • Loading branch information...
commit 548b4b20e2004d30a1a77fa6fde2d7bd593ec6ee 0 parents
Johan Dahlberg authored

Showing 56 changed files with 8,140 additions and 0 deletions. Show diff stats Hide diff stats

  1. +19 0 LICENSE
  2. +67 0 README
  3. +223 0 bin/stage
  4. +90 0 bin/stage-abort
  5. +87 0 bin/stage-clients
  6. +93 0 bin/stage-collect
  7. +301 0 bin/stage-init-test
  8. +162 0 bin/stage-install
  9. +96 0 bin/stage-jobs
  10. +1,498 0 bin/stage-master
  11. +121 0 bin/stage-run
  12. +531 0 bin/stage-setup-machine
  13. +329 0 bin/stage-setup-smartdc
  14. +867 0 bin/stage-slave
  15. +89 0 bin/stage-tests
  16. +77 0 bin/stage-uninstall
  17. +62 0 doc/commands/abort.md
  18. +62 0 doc/commands/clients.md
  19. +62 0 doc/commands/collect.md
  20. +57 0 doc/commands/init-test.md
  21. +63 0 doc/commands/install.md
  22. +63 0 doc/commands/jobs.md
  23. +67 0 doc/commands/master.md
  24. +72 0 doc/commands/run.md
  25. +69 0 doc/commands/setup-machine.md
  26. +97 0 doc/commands/setup-smartdc.md
  27. +69 0 doc/commands/slave.md
  28. +117 0 doc/commands/stage.md
  29. +62 0 doc/commands/tests.md
  30. +63 0 doc/commands/uninstall.md
  31. +14 0 examples/long-running-test/package.json
  32. +38 0 examples/long-running-test/test.js
  33. +14 0 examples/simple-test/package.json
  34. +36 0 examples/simple-test/test.js
  35. +291 0 lib/api.js
  36. +140 0 lib/cliutil.js
  37. +47 0 lib/consts.js
  38. +9 0 lib/dashboard/bootstrap.min.css
  39. +33 0 lib/dashboard/dashboard.js
  40. +53 0 lib/dashboard/index.html
  41. +108 0 man/abort.1
  42. +108 0 man/clients.1
  43. +108 0 man/collect.1
  44. +92 0 man/init-test.1
  45. +108 0 man/install.1
  46. +111 0 man/jobs.1
  47. +123 0 man/master.1
  48. +129 0 man/run.1
  49. +129 0 man/setup-machine.1
  50. +183 0 man/setup-smartdc.1
  51. +123 0 man/slave.1
  52. +218 0 man/stage.1
  53. +108 0 man/tests.1
  54. +114 0 man/uninstall.1
  55. +33 0 package.json
  56. +35 0 scripts/buildman.sh
19 LICENSE
... ... @@ -0,0 +1,19 @@
  1 +Copyright (c) 2013, Johan Dahlberg <https://github.com/jfd>
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy of
  4 +this software and associated documentation files (the "Software"), to deal in
  5 +the Software without restriction, including without limitation the rights to
  6 +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  7 +of the Software, and to permit persons to whom the Software is furnished to do
  8 +so, subject to the following conditions:
  9 +
  10 + The above copyright notice and this permission notice shall be included in
  11 + all copies or substantial portions of the Software.
  12 +
  13 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19 +SOFTWARE.
67 README
... ... @@ -0,0 +1,67 @@
  1 +Stage - Distributed Testing Suite
  2 +=================================
  3 +
  4 +Stage is a distributed testing suite for Node.js. The main goal is to provide a test suite that can help with network related test cases such as load balancing and performance testing.
  5 +
  6 +Stage includes tools for setting up test networks (via SSH or Joyent SmartDC), distribute and running test and collecting results.
  7 +
  8 +
  9 +## Installation
  10 +
  11 +Stage installs via NPM. Run the following command in your terminal:
  12 +
  13 + $ npm install stage -g
  14 +
  15 +
  16 +## Example
  17 +
  18 +This example is creating a testing enviroment using your current Joyent Smartdc settings (expects that SDC is currently installed and configured with environmental variables).
  19 +
  20 +First of, we need to setup a Stage Master server:
  21 +
  22 + $ stage setup-smartdc master
  23 +
  24 +The Master Server it self cannot run tests. This is done by slave servers. We can simply setup a Stage Slave server with the `setup-smartdc` command as well:
  25 +
  26 + $ stage setup-smartdc slave --remote-url ws://<ip-and-port-to-master-server>
  27 +
  28 +We are now ready to create our first test. Stage comes with a tool that initialize a basic test for you:
  29 +
  30 + $ stage init-test mytest
  31 +
  32 +Our test is now created in the folder 'mytest'. The test can be run out-of-the-box but will not do much. In order for it do to something, you can edit the 'test.js' file.
  33 +
  34 +All commands from this point is need to now the address to the master server. There is two ways of telling the command who to talk to. Via the comamnd line or via an environmental variable. We will go with the environmental variable in this case.
  35 +
  36 + $ export STAGE_URL=http://<ip-and-port-to-master-server>
  37 +
  38 +You could pass the argument `--url http://<ip-and-port-to-master-server>` if you prefer to leave the environment untouched.
  39 +
  40 +It is now time to install the test on the Master Server:
  41 +
  42 + $ stage install mytest
  43 +
  44 +The test is now installed. Next step is to tell the master to run the test on the connected Slave. Note that this phase is async, the command will exit immidently.
  45 +
  46 + $ stage run mytest@1.0.0
  47 +
  48 +You can monitor the test via the `stage list` command. Once it is ready, you can collect the test results. This is done with the `stage collect` command:
  49 +
  50 + $ stage collect 1
  51 +
  52 +This is just a basic example in how to use Stage. See manpages for more information:
  53 +
  54 + $ stage help
  55 +
  56 +
  57 +## Issues
  58 +
  59 +
  60 +## License
  61 +
  62 +Stage is licensed under the MIT license. See LICENSE in this repo for more information.
  63 +
  64 +
  65 +## Copyright
  66 +
  67 +Copyright (c) 2013 Johan Dahlberg <http://jfd.github.com>
223 bin/stage
... ... @@ -0,0 +1,223 @@
  1 +#!/usr/bin/env node
  2 +
  3 +/**
  4 +Usage: stage <command>
  5 +
  6 +Most common used commands for stage:
  7 +
  8 + abort Aborts a currently running test
  9 + clients List available clients (slaves and monitors) on master process
  10 + collect Get's report for specifed test
  11 + extras Shows available extras commands
  12 + help Shows available commands for stage (this section)
  13 + install Installs a test package on master process
  14 + jobs List all running and finished jobs on master process
  15 + master Spawns a master process
  16 + run Runs a test package on master process
  17 + slave Spawns a slave process
  18 + tests List all available test packages on master process
  19 + uninstall Uninstalls a test package on master process
  20 +
  21 +Built-in maintenance commands:
  22 +
  23 + init-test Initializes a test template.
  24 + setup-machine Installs dependencies on a remote machine via SSH.
  25 + setup-smartdc Installs dependencies on a SmartDC machine.
  26 +
  27 +See 'stage <command> --help' for more information on a specific command.
  28 +*/
  29 +
  30 +"use strict";
  31 +
  32 +var resolve = require.resolve;
  33 +
  34 +var spawn = require("child_process").spawn;
  35 +
  36 +var basename = require("path").basename;
  37 +var extname = require("path").extname;
  38 +var presolve = require("path").resolve;
  39 +var join = require("path").join;
  40 +
  41 +var readFileSync = require("fs").readFileSync;
  42 +var readdirSync = require("fs").readdirSync;
  43 +var existsSync = require("fs").existsSync;
  44 +
  45 +
  46 +var BUILTIN_EXTRAS = [ resolve("./stage-setup-machine"),
  47 + resolve("./stage-setup-smartdc"),
  48 + resolve("./stage-init-test")
  49 + ];
  50 +
  51 +
  52 +function main () {
  53 + var cmd = process.argv[2];
  54 +
  55 + process.argv.splice(1, 1);
  56 +
  57 + switch (cmd) {
  58 +
  59 + case "--help":
  60 + case "help":
  61 + return help();
  62 +
  63 + case "-v":
  64 + case "--version":
  65 + return version();
  66 +
  67 + case "--usage":
  68 + return usage();
  69 +
  70 + case "master":
  71 + process.argv[1] = resolve("./stage-master");
  72 + break;
  73 +
  74 + case "slave":
  75 + process.argv[1] = resolve("./stage-slave");
  76 + break;
  77 +
  78 + case "i":
  79 + case "install":
  80 + process.argv[1] = resolve("./stage-install");
  81 + break;
  82 +
  83 + case "u":
  84 + case "uninstall":
  85 + process.argv[1] = resolve("./stage-uninstall");
  86 + break;
  87 +
  88 + case "a":
  89 + case "abort":
  90 + process.argv[1] = resolve("./stage-abort");
  91 + break;
  92 +
  93 + case "c":
  94 + case "clients":
  95 + process.argv[1] = resolve("./stage-clients");
  96 + break;
  97 +
  98 + case "j":
  99 + case "jobs":
  100 + process.argv[1] = resolve("./stage-jobs");
  101 + break;
  102 +
  103 + case "t":
  104 + case "tests":
  105 + process.argv[1] = resolve("./stage-tests");
  106 + break;
  107 +
  108 + case "r":
  109 + case "run":
  110 + process.argv[1] = resolve("./stage-run");
  111 + break;
  112 +
  113 + case "co":
  114 + case "collect":
  115 + process.argv[1] = resolve("./stage-collect");
  116 + break;
  117 +
  118 + case "e":
  119 + case "extras":
  120 + return extras();
  121 +
  122 + default:
  123 +
  124 + if (!cmd) {
  125 + return usage();
  126 + }
  127 +
  128 + process.argv[1] = getExtraByAlias(cmd);
  129 +
  130 + if (existsSync(process.argv[1]) == false) {
  131 + console.error("stage: bad command -- %s", basename(cmd));
  132 + process.exit(1);
  133 + }
  134 + break;
  135 +
  136 + }
  137 +
  138 + require(process.argv[1]);
  139 +}
  140 +
  141 +
  142 +function usage () {
  143 + var match = /\/\*\*\n([^*]+)\*\//gi;
  144 + process.stdout.write(match.exec(readFileSync(__filename))[1]);
  145 + process.exit(1);
  146 +}
  147 +
  148 +
  149 +function version () {
  150 + console.log(require("../package").version);
  151 + process.exit(1);
  152 +}
  153 +
  154 +
  155 +function help () {
  156 + spawn("man", ["stage"], { customFds : [0, 1, 2] });
  157 +}
  158 +
  159 +
  160 +function extras () {
  161 + var extras;
  162 +
  163 + console.log("Usage: stage <command>");
  164 + console.log("Available extras commands:\n");
  165 +
  166 + extras = getAvailableExtras();
  167 + extras.forEach(function (cmd) {
  168 + var alias = /^stage\-(.+)/.exec(basename(cmd))[1];
  169 + console.log(" " + alias);
  170 + });
  171 +}
  172 +
  173 +
  174 +function getExtraByAlias (alias) {
  175 + var res;
  176 +
  177 + alias = "stage-" + alias;
  178 +
  179 + res = getAvailableExtras().filter(function (t) {
  180 + return basename(t) == alias;
  181 + });
  182 +
  183 + return res[0];
  184 +}
  185 +
  186 +
  187 +function getAvailableExtras () {
  188 + var dirs;
  189 + var extras;
  190 +
  191 + extras = BUILTIN_EXTRAS.slice(0);
  192 +
  193 + if ("STAGE_EXTRAS_PATH" in process.env) {
  194 + dirs = process.env["STAGE_EXTRAS_PATH"].split(":");
  195 + } else {
  196 + return extras;
  197 + }
  198 +
  199 + dirs.forEach(function (dir) {
  200 + var files;
  201 +
  202 + try {
  203 + files = readdirSync(dir).filter(function (file) {
  204 + return (/^stage\-/.test(file));
  205 + });
  206 + } catch (err) {
  207 + return;
  208 + }
  209 +
  210 + files = files.map(function (file) {
  211 + return join(dir, file);
  212 + });
  213 +
  214 + extras = extras.concat(files);
  215 + });
  216 +
  217 + return extras;
  218 +}
  219 +
  220 +
  221 +if (process.mainModule == module) {
  222 + main();
  223 +}
90 bin/stage-abort
... ... @@ -0,0 +1,90 @@
  1 +#!/usr/bin/env node
  2 +
  3 +"use strict";
  4 +
  5 +var abortJob = require("../lib/api").abortJob;
  6 +
  7 +var version = require("../lib/cliutil").version;
  8 +var usage = require("../lib/cliutil").usage;
  9 +var help = require("../lib/cliutil").help;
  10 +var halt = require("../lib/cliutil").halt;
  11 +var url = require("../lib/cliutil").url;
  12 +var toHttpUrl = require("../lib/cliutil").toHttpUrl;
  13 +
  14 +var consts = require("../lib/consts");
  15 +
  16 +
  17 +var DEFAULT_OPTIONS = { "url" : consts.DEFAULT_URL,
  18 + "json" : false,
  19 + "id" : null
  20 + };
  21 +
  22 +
  23 +var options = Object.create(DEFAULT_OPTIONS);
  24 +
  25 +
  26 +function main () {
  27 + var args = process.argv.slice(2);
  28 + var arg;
  29 +
  30 +
  31 + while ((arg = args.shift())) {
  32 + switch (arg) {
  33 +
  34 + case "-v":
  35 + case "--version":
  36 + return version();
  37 +
  38 + case "--usage":
  39 + return usage("<jobid>");
  40 +
  41 + case "--help":
  42 + return help();
  43 +
  44 + case "--url":
  45 + case "--hostname":
  46 + case "--port":
  47 + case "--token":
  48 + case "--secure":
  49 + options.url = url(options.url, arg, args.shift());
  50 + break;
  51 +
  52 + case "--json":
  53 + options.json = true;
  54 + break;
  55 +
  56 + default:
  57 +
  58 + if (arg[0] == "-") {
  59 + halt("unknown option " + arg);
  60 + }
  61 +
  62 + options.id = parseInt(arg, 10);
  63 + break;
  64 + }
  65 + }
  66 +
  67 + options.url = toHttpUrl(options.url);
  68 +
  69 + if (!options.id || isNaN(options.id)) {
  70 + halt("expected <jobid>");
  71 + }
  72 +
  73 + abortJob(options.url, options.id, function (err, result) {
  74 + if (err) {
  75 + halt(err);
  76 + }
  77 +
  78 + if (options.json) {
  79 + process.stdout.write(JSON.stringify(result));
  80 + return;
  81 + }
  82 +
  83 + console.log("Job #%s was aborted succesfully", result.id);
  84 + });
  85 +}
  86 +
  87 +
  88 +if (process.argv[1] == __filename) {
  89 + main();
  90 +}
87 bin/stage-clients
... ... @@ -0,0 +1,87 @@
  1 +#!/usr/bin/env node
  2 +
  3 +"use strict";
  4 +
  5 +var version = require("../lib/cliutil").version;
  6 +var usage = require("../lib/cliutil").usage;
  7 +var help = require("../lib/cliutil").help;
  8 +var halt = require("../lib/cliutil").halt;
  9 +var url = require("../lib/cliutil").url;
  10 +var toHttpUrl = require("../lib/cliutil").toHttpUrl;
  11 +
  12 +var listClients = require("../lib/api").listClients;
  13 +
  14 +var consts = require("../lib/consts");
  15 +
  16 +
  17 +var DEFAULT_OPTIONS = { "url" : consts.DEFAULT_URL,
  18 + "json" : false
  19 + };
  20 +
  21 +
  22 +var options = Object.create(DEFAULT_OPTIONS);
  23 +
  24 +
  25 +function main () {
  26 + var args = process.argv.slice(2);
  27 + var arg;
  28 +
  29 +
  30 + while ((arg = args.shift())) {
  31 + switch (arg) {
  32 +
  33 + case "--usage":
  34 + return usage();
  35 +
  36 + case "--help":
  37 + return help();
  38 +
  39 + case "-v":
  40 + case "--version":
  41 + return version();
  42 +
  43 + case "--url":
  44 + case "--hostname":
  45 + case "--port":
  46 + case "--token":
  47 + case "--secure":
  48 + options.url = url(options.url, arg, args.shift());
  49 + break;
  50 +
  51 + case "--json":
  52 + options.json = true;
  53 + break;
  54 +
  55 + default:
  56 + return halt("bad argument - " + arg);
  57 + }
  58 + }
  59 +
  60 + options.url = toHttpUrl(options.url);
  61 +
  62 + listClients(options.url, function (err, result) {
  63 + if (err) {
  64 + halt(err.message);
  65 + }
  66 +
  67 + if (options.json) {
  68 + return console.log(JSON.stringify(result));
  69 + }
  70 +
  71 + console.log("total %s", result.length);
  72 +
  73 + result.forEach(function (c) {
  74 + console.log("#%s - %s - %s:%s",
  75 + c.id,
  76 + c.role.toUpperCase(),
  77 + c.remoteAddress,
  78 + c.remotePort
  79 + );
  80 + });
  81 + });
  82 +}
  83 +
  84 +
  85 +if (process.argv[1] == __filename) {
  86 + main();
  87 +}
93 bin/stage-collect
... ... @@ -0,0 +1,93 @@
  1 +#!/usr/bin/env node
  2 +
  3 +"use strict";
  4 +
  5 +var getReport = require("../lib/api").getReport;
  6 +
  7 +var version = require("../lib/cliutil").version;
  8 +var usage = require("../lib/cliutil").usage;
  9 +var help = require("../lib/cliutil").help;
  10 +var halt = require("../lib/cliutil").halt;
  11 +var url = require("../lib/cliutil").url;
  12 +var toHttpUrl = require("../lib/cliutil").toHttpUrl;
  13 +
  14 +var consts = require("../lib/consts");
  15 +
  16 +
  17 +var DEFAULT_OPTIONS = { "url" : consts.DEFAULT_URL,
  18 + "json" : false,
  19 + "jobid" : null
  20 + };
  21 +
  22 +
  23 +var options = Object.create(DEFAULT_OPTIONS);
  24 +
  25 +
  26 +function main () {
  27 + var args = process.argv.slice(2);
  28 + var arg;
  29 +
  30 + while ((arg = args.shift())) {
  31 + switch (arg) {
  32 +
  33 + case "-v":
  34 + case "--version":
  35 + return version();
  36 +
  37 + case "--usage":
  38 + return usage("<jobid>");
  39 +
  40 + case "--help":
  41 + return help();
  42 +
  43 + case "--url":
  44 + case "--hostname":
  45 + case "--port":
  46 + case "--token":
  47 + case "--secure":
  48 + options.url = url(options.url, arg, args.shift());
  49 + break;
  50 +
  51 + case "--json":
  52 + options.json = true;
  53 + break;
  54 +
  55 + default:
  56 +
  57 + if (arg[0] == "-") {
  58 + halt("unknown option " + arg);
  59 + }
  60 +
  61 + options.jobid = parseInt(arg, 10);
  62 + break;
  63 + }
  64 + }
  65 +
  66 + options.url = toHttpUrl(options.url);
  67 +
  68 + if (!options.jobid || isNaN(options.jobid)) {
  69 + halt("expected <jobid>");
  70 + }
  71 +
  72 + getReport(options.url, options.jobid, function (err, report) {
  73 + if (err) {
  74 + halt(err.message);
  75 + }
  76 +
  77 + if (options.json) {
  78 + return console.log(JSON.stringify(result));
  79 + }
  80 +
  81 + console.log(report);
  82 + // console.log("total %s", list.length);
  83 + //
  84 + // list.forEach(function (job) {
  85 + // console.log("#%s %s %s", job.id, job.name, job.state);
  86 + // });
  87 + });
  88 +}
  89 +
  90 +
  91 +if (process.argv[1] == __filename) {
  92 + main();
  93 +}
301 bin/stage-init-test
... ... @@ -0,0 +1,301 @@
  1 +#!/usr/bin/env node
  2 +
  3 +/***package
  4 +{
  5 + "author": "{{=author}}",
  6 + "name": "{{=name}}",
  7 + "version": "{{=version}}",
  8 + "dependencies": {
  9 + },
  10 + "scripts": {
  11 + "test": "node test.js",
  12 + "stagetest": "node test.js"
  13 + },
  14 + "engines": {
  15 + "node": ">=0.7.7"
  16 + }
  17 +}
  18 +*/
  19 +
  20 +
  21 +/***test
  22 +
  23 +// {{=name}}
  24 +// This test can be used as a template for your own test. The script is divided
  25 +// into two phases, compatible phase and setup phase. The compatible phase
  26 +// checks if Stage signals are supported.
  27 +//
  28 +// If so, we need to signal the parent process once that all setup
  29 +// routines is done.
  30 +//
  31 +// Generated by: {{=program}}, version {{=version}}
  32 +
  33 +"use strict";
  34 +
  35 +
  36 +function runTest () {
  37 + // Your test code goes here...
  38 + console.log("Test finished successfully!!");
  39 + process.exit(0);
  40 +}
  41 +
  42 +
  43 +function initStage () {
  44 + var parent = parseInt(process.env["STAGE_PARENT_PID"]);
  45 + var signal = process.env["STAGE_PARENT_SIGNAL"];
  46 +
  47 + // The PID of parent is set if we are executed as a Stage test
  48 + if (!parent || isNaN(parent)) {
  49 + return false;
  50 + }
  51 +
  52 + // Wait for parent to signal us. When it does, start the test
  53 + process.stdin.resume();
  54 + process.on(signal, runTest);
  55 +
  56 + // Stage was initialized for this test. Returning true indicates
  57 + // that we should send a signal to parent when we are ready to
  58 + // go.
  59 + return true;
  60 +}
  61 +
  62 +
  63 +function ready () {
  64 + var parent = parseInt(process.env["STAGE_PARENT_PID"]);
  65 + var signal = process.env["STAGE_PARENT_SIGNAL"];
  66 + process.kill(parent, signal);
  67 +}
  68 +
  69 +// Check if this script was called by a Stage process. If not, it was
  70 +// probably called by NPM or antoher testing suite.
  71 +if (initStage() == true) {
  72 +
  73 + // Do all your asynchronized calls here.....
  74 +
  75 + // Signal the parent process when you are ready to start the test.
  76 + ready();
  77 +} else {
  78 +
  79 + // You can choose to exit the process with an exit code at this
  80 + // time. This template is running the test even if not called by
  81 + // Stage.
  82 + runTest();
  83 +}
  84 +*/
  85 +
  86 +"use strict";
  87 +
  88 +var exec = require("child_process").exec;
  89 +
  90 +var readFileSync = require("fs").readFileSync;
  91 +var writeFileSync = require("fs").writeFileSync;
  92 +var existsSync = require("fs").existsSync;
  93 +var mkdirSync = require("fs").mkdirSync;
  94 +var statSync = require("fs").statSync;
  95 +
  96 +var resolve = require("path").resolve;
  97 +var basename = require("path").basename;
  98 +var dirname = require("path").dirname;
  99 +
  100 +var format = require("util").format;
  101 +
  102 +var version = require("../lib/cliutil").version;
  103 +var usage = require("../lib/cliutil").usage;
  104 +var help = require("../lib/cliutil").help;
  105 +var halt = require("../lib/cliutil").halt;
  106 +var url = require("../lib/cliutil").url;
  107 +
  108 +
  109 +var DEFAULT_OPTIONS = { "basepath" : process.cwd(),
  110 + "author" : process.env["STAGE_AUTHOR"] ||
  111 + process.env["USER"],
  112 + "version" : "1.0.0",
  113 + "name" : null,
  114 + "force" : false
  115 + };
  116 +
  117 +
  118 +var options = Object.create(DEFAULT_OPTIONS);
  119 +
  120 +
  121 +function main () {
  122 + var args = process.argv.slice(2);
  123 + var packagePath;
  124 + var testPath;
  125 + var arg;
  126 +
  127 + while ((arg = args.shift())) {
  128 + switch (arg) {
  129 +
  130 + case "-v":
  131 + case "--version":
  132 + return version();
  133 +
  134 + case "--usage":
  135 + return usage("<path>");
  136 +
  137 + case "--help":
  138 + return help();
  139 +
  140 + case "--force":
  141 + options.force = true;
  142 + break;
  143 +
  144 + case "--package-author":
  145 + options.author = args.shift();
  146 + break;
  147 +
  148 + case "--package-name":
  149 + options.name = args.shift();
  150 + break;
  151 +
  152 + case "--package-version":
  153 + options.version = args.shift();
  154 + break;
  155 +
  156 + default:
  157 +
  158 + if (arg[0] == "-") {
  159 + halt("unknown option " + arg);
  160 + }
  161 +
  162 + options.basepath = resolve(arg);
  163 + break;
  164 + }
  165 + }
  166 +
  167 +
  168 + if (!options.author) {
  169 + halt("Option --package-author must be set");
  170 + }
  171 +
  172 + packagePath = resolve(options.basepath, "package.json");
  173 + testPath = resolve(options.basepath, "test.js");
  174 +
  175 + if (existsSync(packagePath) && !options.force) {
  176 + halt("File '" + packagePath + "' already exists");
  177 + }
  178 +
  179 + if (existsSync(testPath) && !options.force) {
  180 + halt("File '" + packagePath + "' already exists");
  181 + }
  182 +
  183 + if (!options.name) {
  184 + options.name = basename(options.basepath);
  185 + }
  186 +
  187 + mkdirp(options.basepath);
  188 +
  189 + writePackageJson(packagePath);
  190 + writeTestJs(testPath);
  191 +
  192 + console.log("Test '%s@%s' was successfully initialized",
  193 + options.name,
  194 + options.version);
  195 +}
  196 +
  197 +
  198 +function writePackageJson (path) {
  199 + var content;
  200 + var context;
  201 +
  202 + content = getNamedSection("package");
  203 +
  204 + context = {
  205 + name : options.name,
  206 + version : options.version,
  207 + author : options.author
  208 + };
  209 +
  210 + writeFileSync(path, template(content, context));
  211 +}
  212 +
  213 +
  214 +function writeTestJs (path) {
  215 + var content;
  216 + var context;
  217 +
  218 + content = getNamedSection("test");
  219 +
  220 + context = {
  221 + name : options.name,
  222 + program : basename(process.argv[1]),
  223 + version : require("../package").version
  224 + };
  225 +
  226 + writeFileSync(path, template(content, context));
  227 +}
  228 +
  229 +
  230 +// Based on https://github.com/substack/node-mkdirp/blob/master/index.js
  231 +function mkdirp (path, mode, made) {
  232 + var stat;
  233 +
  234 + if (mode === undefined) {
  235 + mode = 0x1ff & (~process.umask());
  236 + }
  237 +
  238 + if (!made) {
  239 + made = null;
  240 + }
  241 +
  242 + path = resolve(path);
  243 +
  244 + try {
  245 + mkdirSync(path, mode);
  246 + made = made || path;
  247 + } catch (er) {
  248 + if (er.code == "ENOENT") {
  249 + made = mkdirp(dirname(path), mode, made);
  250 + mkdirp(path, mode, made);
  251 + } else {
  252 + try {
  253 + stat = statSync(path);
  254 + } catch (err) {
  255 + throw er;
  256 + }
  257 + if (stat.isDirectory() == false) {
  258 + throw er;
  259 + }
  260 + }
  261 + }
  262 +
  263 + return made;
  264 +}
  265 +
  266 +
  267 +function template (content, context) {
  268 + var re;
  269 + context = context || {};
  270 + for (var k in context) {
  271 + re = new RegExp("{{=" + k + "}}");
  272 + content = content.replace(re, context[k]);
  273 + }
  274 + return content;
  275 +}
  276 +
  277 +
  278 +function getNamedSection (name) {
  279 + var splitre = /(\/\*\*\*[^*]+\*\/)/;
  280 + var sectionre = /\/\*\*\*([a-z]+)\n([^*]+)\*\//gi;
  281 + var content;
  282 + var sections;
  283 + var section;
  284 + var match;
  285 +
  286 + content = readFileSync(__filename).toString();
  287 + sections = content.split(splitre);
  288 +
  289 + for (var i = 0; i < sections.length; i++) {
  290 + section = sections[i];
  291 + match = sectionre.exec(section);
  292 + if (match && match[1] == name) {
  293 + return match[2];
  294 + }
  295 + }
  296 +}
  297 +
  298 +
  299 +if (process.argv[1] == __filename) {
  300 + main();
  301 +}
162 bin/stage-install
... ... @@ -0,0 +1,162 @@
  1 +#!/usr/bin/env node
  2 +
  3 +"use strict";
  4 +
  5 +var statSync = require("fs").statSync;
  6 +var readFileSync = require("fs").readFileSync;
  7 +var existsSync = require("fs").existsSync;
  8 +
  9 +var parseUrl = require("url").parse;
  10 +
  11 +var resolve = require("path").resolve;
  12 +var join = require("path").join;
  13 +
  14 +var spawn = require("child_process").spawn;
  15 +
  16 +var version = require("../lib/cliutil").version;
  17 +var usage = require("../lib/cliutil").usage;
  18 +var help = require("../lib/cliutil").help;
  19 +var halt = require("../lib/cliutil").halt;
  20 +var url = require("../lib/cliutil").url;
  21 +var toHttpUrl = require("../lib/cliutil").toHttpUrl;
  22 +
  23 +var prepareRequest = require("../lib/api").prepareRequest;
  24 +
  25 +
  26 +var consts = require("../lib/consts");
  27 +
  28 +
  29 +var DEFAULT_OPTIONS = { "url" : consts.DEFAULT_URL,
  30 + "force" : false,
  31 + "target" : null
  32 + };
  33 +
  34 +var options = Object.create(DEFAULT_OPTIONS);
  35 +
  36 +
  37 +function main () {
  38 + var args = process.argv.slice(2);
  39 + var arg;
  40 + var tmpurl;
  41 + var pkg;
  42 +
  43 +
  44 + while ((arg = args.shift())) {
  45 + switch (arg) {
  46 +
  47 + case "-v":
  48 + case "--version":
  49 + return version();
  50 +
  51 + case "--usage":
  52 + return usage("<path>");
  53 +
  54 + case "--help":
  55 + return help();
  56 +
  57 + case "--url":
  58 + case "--hostname":
  59 + case "--port":
  60 + case "--token":
  61 + case "--secure":
  62 + options.url = url(options.url, arg, args.shift());
  63 + break;
  64 +
  65 + case "--force":
  66 + options.url = url(options.url, arg, "1");
  67 + break;
  68 +
  69 + default:
  70 +
  71 + if (arg[0] == "-") {
  72 + halt("unknown option " + arg);
  73 + }
  74 +
  75 + options.target = resolve(arg);
  76 + break;
  77 + }
  78 + }
  79 +
  80 + options.url = toHttpUrl(options.url);
  81 +
  82 + if (existsSync(options.target) == false ||
  83 + statSync(options.target).isDirectory() == false) {
  84 + halt("<path> must be a directory");
  85 + }
  86 +
  87 + pkg = readFileSync(resolve(options.target, "package.json"));
  88 + pkg = JSON.parse(pkg);
  89 +
  90 + if ("name" in pkg == false) {
  91 + halt("File '<path>/package.json' must contain the 'name' field");
  92 + }
  93 +
  94 + if ("version" in pkg == false) {
  95 + halt("File '<path>/package.json' must contain the 'version' field");
  96 + }
  97 +
  98 + if ("scripts" in pkg == false ||
  99 + ("stagetest" in pkg.scripts == false && "test" in pkg.scripts == false)) {
  100 + halt("Package <path> must contain either a 'stagetest' or a 'test' script");
  101 + }
  102 +
  103 + install(options.target, options.url, function (err, result) {
  104 + if (err) {
  105 + halt(err.message);
  106 + }
  107 +
  108 + console.log("Package '%s' was installed successfully", result.id);
  109 + });
  110 +}
  111 +
  112 +
  113 +function install (path, url, C) {
  114 + var child;
  115 + var opts;
  116 + var stderr;
  117 + var req;
  118 +
  119 + opts = {
  120 + method : "POST",
  121 + url : url,
  122 + path : "tests",
  123 + json : true
  124 + };
  125 +
  126 + req = prepareRequest(opts, function (err, result) {
  127 + child.kill("SIGKILL");
  128 + return C(err, result);
  129 + });
  130 +
  131 + req.setHeader("Content-Type", "application/octet-stream");
  132 + req.setHeader("Transfer-Encoding", "chunked");
  133 +
  134 + child = spawn("tar", ["-c", "."], { cwd: path });
  135 + child.stdout.pipe(req);
  136 +
  137 + stderr = [];
  138 +
  139 + child.stderr.on("data", function (chunk) {
  140 + stderr.push(chunk);
  141 + });
  142 +
  143 + child.on("error", function (err) {
  144 + req.abort();
  145 + return C(err);
  146 + });
  147 +
  148 + child.on("exit", function (code) {
  149 + var arg;
  150 +
  151 + if (code) {
  152 + arg = Buffer.concat(stderr).toString();
  153 + req.abort();
  154 + return C(new Error(arg));
  155 + }
  156 + });
  157 +}
  158 +
  159 +
  160 +if (process.argv[1] == __filename) {
  161 + main();
  162 +}
96 bin/stage-jobs
... ... @@ -0,0 +1,96 @@
  1 +#!/usr/bin/env node
  2 +
  3 +"use strict";
  4 +
  5 +var listJobs = require("../lib/api").listJobs;
  6 +
  7 +var version = require("../lib/cliutil").version;
  8 +var usage = require("../lib/cliutil").usage;
  9 +var help = require("../lib/cliutil").help;
  10 +var halt = require("../lib/cliutil").halt;
  11 +var url = require("../lib/cliutil").url;
  12 +var toHttpUrl = require("../lib/cliutil").toHttpUrl;
  13 +
  14 +var consts = require("../lib/consts");
  15 +
  16 +
  17 +var DEFAULT_OPTIONS = { "url" : consts.DEFAULT_URL,
  18 + "json" : false,
  19 + "monitor" : false
  20 + };
  21 +
  22 +
  23 +var options = Object.create(DEFAULT_OPTIONS);
  24 +
  25 +
  26 +function main () {
  27 + var args = process.argv.slice(2);
  28 + var arg;
  29 +
  30 + while ((arg = args.shift())) {
  31 + switch (arg) {
  32 +
  33 + case "-v":
  34 + case "--version":
  35 + return version();
  36 +
  37 + case "--usage":
  38 + return usage();
  39 +
  40 + case "--help":
  41 + return help();
  42 +
  43 + case "--url":
  44 + case "--hostname":
  45 + case "--port":
  46 + case "--token":
  47 + case "--secure":
  48 + options.url = url(options.url, arg, args.shift());
  49 + break;
  50 +
  51 + case "--monitor":
  52 + options.monitor = true;
  53 + break;
  54 +
  55 + case "--json":
  56 + options.json = true;
  57 + break;
  58 +
  59 + default:
  60 + return halt("bad argument - " + arg);
  61 + }
  62 + }
  63 +
  64 + options.url = toHttpUrl(options.url);
  65 +
  66 + listJobs(options.url, function (err, list) {
  67 + if (err) {
  68 + halt(err.message);
  69 + }
  70 +
  71 + if (options.json) {
  72 + return console.log(JSON.stringify(list));
  73 + }
  74 +
  75 + if (list.length == 0) {
  76 + console.log("No jobs are currently running");
  77 + return;
  78 + }
  79 +
  80 + console.log("total %s", list.length);
  81 +
  82 + list.forEach(function (job) {
  83 + console.log("#%s %s %s", job.id, job.name, job.state);
  84 + });
  85 + });
  86 +}
  87 +
  88 +
  89 +// function monitorjobs (url, C) {
  90 +//
  91 +// }
  92 +//
  93 +
  94 +if (process.argv[1] == __filename) {
  95 + main();
  96 +}
1,498 bin/stage-master
... ... @@ -0,0 +1,1498 @@
  1 +#!/usr/bin/env node
  2 +
  3 +"use strict";
  4 +
  5 +var ok = require("assert").ok;
  6 +var equal = require("assert").equal;
  7 +var notEqual = require("assert").notEqual;
  8 +
  9 +var exec = require("child_process").exec;
  10 +var spawn = require("child_process").spawn;
  11 +
  12 +var createDomain = require("domain").create;
  13 +
  14 +var EventEmitter = require("events").EventEmitter;
  15 +
  16 +var statSync = require("fs").statSync;
  17 +var stat = require("fs").stat;
  18 +var readdir = require("fs").readdir;
  19 +var readFile = require("fs").readFile;
  20 +var readFileSync = require("fs").readFileSync;
  21 +var writeFile = require("fs").writeFile;
  22 +var exists = require("fs").exists;
  23 +var existsSync = require("fs").existsSync;
  24 +var unlink = require("fs").unlink;
  25 +
  26 +var createServer = require("http").createServer;
  27 +var createSecureServer = require("https").createServer;
  28 +
  29 +var basename = require("path").basename;
  30 +var extname = require("path").extname;
  31 +var join = require("path").join;
  32 +var resolve = require("path").resolve;
  33 +
  34 +var parseUrl = require("url").parse;
  35 +
  36 +var inherits = require("util").inherits;
  37 +var format = require("util").format;
  38 +var log = require("util").log;
  39 +
  40 +var WebSocket = require("ws");
  41 +var WebSocketServer = require("ws").Server;
  42 +
  43 +var version = require("../lib/cliutil").version;
  44 +var usage = require("../lib/cliutil").usage;
  45 +var help = require("../lib/cliutil").help;
  46 +var halt = require("../lib/cliutil").halt;
  47 +var url = require("../lib/cliutil").url;
  48 +
  49 +var consts = require("../lib/consts");
  50 +
  51 +var JOBSTATE_NA = consts.JOBSTATE_NA;
  52 +var JOBSTATE_QUEUED = consts.JOBSTATE_QUEUED;
  53 +var JOBSTATE_INITIALIZED = consts.JOBSTATE_INITIALIZED;
  54 +var JOBSTATE_INSTALLING = consts.JOBSTATE_INSTALLING;
  55 +var JOBSTATE_SETUP = consts.JOBSTATE_SETUP;
  56 +var JOBSTATE_RUNNING = consts.JOBSTATE_RUNNING;
  57 +var JOBSTATE_STOPPING = consts.JOBSTATE_STOPPING;
  58 +var JOBSTATE_FINISHED = consts.JOBSTATE_FINISHED;
  59 +var JOBSTATE_KILLED = consts.JOBSTATE_KILLED;
  60 +
  61 +var SOP_INSTALL = consts.SOP_INSTALL;
  62 +var COP_INSTALL = consts.COP_INSTALL;
  63 +var SOP_SETUP = consts.SOP_SETUP;
  64 +var COP_SETUP = consts.COP_SETUP;
  65 +var SOP_START = consts.SOP_START;
  66 +var COP_START = consts.COP_START;
  67 +var COP_RESULT = consts.COP_RESULT;
  68 +var SOP_ABORT = consts.SOP_ABORT;
  69 +var COP_TESTERROR = consts.COP_TESTERROR;
  70 +var SOP_JOBSTATE = consts.SOP_JOBSTATE;
  71 +
  72 +var LOG_FATAL = consts.LOG_FATAL;
  73 +var LOG_WARN = consts.LOG_WARN;
  74 +var LOG_INFO = consts.LOG_INFO;
  75 +var LOG_VERBOSE = consts.LOG_VERBOSE;