Skip to content
Browse files

Implemented build process, added documentation.

  • Loading branch information...
1 parent 42bd6e6 commit fb82834c58f1cb012b8488f8d39193f78df68634 @mde committed Jul 17, 2010
Showing with 369 additions and 30 deletions.
  1. +33 −0 Makefile
  2. 0 README
  3. +143 −0 README.markdown
  4. +16 −3 lib/browser/adapter.js
  5. +2 −2 lib/browser/index.html
  6. +1 −1 lib/browser/runner.js
  7. +28 −14 lib/logan.js
  8. +6 −3 lib/node/adapter.js
  9. +4 −1 lib/node/runner.js
  10. +27 −6 lib/runner.js
  11. +66 −0 scripts/Jakefile.js
  12. +21 −0 scripts/jake
  13. +22 −0 scripts/logan
View
33 Makefile
@@ -0,0 +1,33 @@
+#
+# Logan client-/server-side JavaScript test runner
+# Copyright 2112 Matthew Eernisse (mde@fleegix.org)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+.PHONY: all build install clean uninstall test
+
+all: build
+
+build:
+ @mkdir -p ./dist; cp -r lib/* dist; echo 'Logan built.'
+
+install:
+ @./scripts/jake -f `pwd`/scripts/Jakefile default
+
+clean:
+ @rm -fr dist
+
+uninstall:
+ @rm -fr dist; rm -fr ~/.node_libraries/logan; rm -f /usr/local/bin/logan; echo 'Logan uninstalled.'
+
View
0 README
No changes.
View
143 README.markdown
@@ -0,0 +1,143 @@
+## Logan: a minimal test runner for both server- and client-side JavaScript
+- - -
+
+### Installing
+
+Prerequisites: Logan requires Node.js. (<http://nodejs.org/>)
+
+Get Logan:
+
+ git clone git://github.com/mde/logan.git
+
+Build Logan:
+
+ cd logan && make && sudo make install
+
+### Basic usage
+
+ logan [browser|node|racer]
+
+### Description
+
+ Logan is a minimal test runner for both client- and
+ server-side JavaScript. It uses the Node.js assert library.
+
+ Logan supports the following envrironments:
+ * Browsers (via HTTP global-eval/script-append)
+ * Node.js
+ * TheRubyRacer
+
+### Test syntax
+
+A Logan test file looks like this:
+
+ var testNamespace = new function () {
+ this.testFoo = function () {
+ assert.ok(true);
+ };
+
+ this.testBar = function () {
+ assert.equal(1, 1);
+ };
+
+ }();
+
+ logan.run(testNamespace);
+
+Pretty simple.
+
+### Sharing client- and server-side JavaScript code and tests
+
+For Logan's testing purposes, "server-side" means in Node.js,
+which uses CommonJS modules. "Client-side" includes the
+browsers, and TheRubyRacer, where there is no facility for
+loading code modules from within the runtime.
+
+There are a few simple rules to follow when creating a module
+to use both in client and server:
+
+1. Name your namespace object in side the module file the same
+name as the variable you'll be setting it to when you load it
+via CommonJS require.
+
+2. Only export your namespace in the CommonJS environment.
+
+For example, in the module file, foo.js:
+
+
+ if (typeof foo != 'undefined') { foo = {}; }
+
+ foo.bar = new function () {
+ this.a = 1;
+ this.b = function () {};
+ }();
+
+ if (typeof module != 'undefined') { module.exports = foo.bar; }
+
+
+Or, if you are exporting a constructor:
+
+ if (typeof foo != 'undefined') { foo = {}; }
+
+ foo.Baz = function () {
+ this.a = 2;
+ this.b = [];
+ };
+
+ if (typeof exports != 'undefined') { exports.Baz = foo.Baz; }
+
+3. When you call require in your source code, use the same
+name as in the module.
+
+4. Create any top-level namespace objects non-destructively.
+
+In your tests, if you're creating top-level namespaces to hang
+your required objects on, use something to create them that
+checks for their existence before creating.
+
+Logan includes a utility function, `logan.namespace`, to help
+with this. In your own app's code, you'll either have to use
+a utility function for doing this, or simply check with a
+`typeof` check for `undefined`.
+
+Here's how to load and use the previous two examples:
+
+ logan.namespace('foo');
+ foo.bar = require('./path/to/foo');
+ foo.Bar = require('./path/to/bar').Bar;
+
+ var fooTests = new function () {
+ this.testFooBarAIsTruthy = function () {
+ assert.ok(foo.bar.a);
+ };
+
+ this.testFooBazAIsNumber = function () {
+ var barInstance = new foo.Bar();
+ assert.equal(typeof barInstance.a, 'number');
+ };
+
+ }();
+
+ logan.run(fooTests);
+
+The reason for these special requirements is the difference
+between the way that code loads on the client and the way it
+loads via CommonJS.
+
+In the client environment (including TheRubyRacer), there
+is no module-level scope, and the mechanism for loading code
+doesn't return any value, so namespace creation has to happen
+inside the module file, and has to be careful not to rewrite
+anything other modules might have done.
+
+Logan makes this work with CommonJS `require` syntax in your
+tests by doing source-code transformation -- it uses the
+require statements to know what code to load, and rewrites
+the statements so they don't stomp on the results of the
+eval operation.
+
+### Author
+
+Matthew Eernisse, mde@fleegix.org
+
+
View
19 lib/browser/adapter.js
@@ -1,8 +1,20 @@
-var BrowserAdapter = function () {};
+var BrowserAdapter = function (logan) {
+ this.logan = logan;
+};
BrowserAdapter.prototype = new function () {
+ this.files = [];
+ this.register = function (files) {
+ this.files = files;
+ var file;
+ while (file = this.files.shift()) {
+ this.loadTestFile(file);
+ }
+ this.logan.reportFinal();
+ };
+
this.loadTestFile = function (file) {
this.dirname = file.replace(/\/([^\/]+)\.js$/, '');
var content = this.getFile(file);
@@ -101,8 +113,9 @@ BrowserAdapter.prototype = new function () {
$('results').innerHTML += '<div class="' + cl + '">' + str + '</div>';
};
- this.reportFinal = function (total, successes) {
- var results = 'Tests run: ' + total + ' total, ' + successes + ' successes';
+ this.reportFinal = function (total, successes, failures) {
+ var results = 'Tests run: ' + total + ' total, ' +
+ successes + ' successes, ' + failures + ' failures';
$('header').innerHTML += results;
};
View
4 lib/browser/index.html
@@ -8,9 +8,9 @@
<script type="text/javascript" src="deps/fleegix.js"></script>
<script type="text/javascript" src="deps/assert.js"></script>
<script type="text/javascript">
- logan.adapter = new BrowserAdapter();
+ logan.adapter = new BrowserAdapter(logan);
window.onload = function () {
- logan.register(@@testfiles@@);
+ logan.adapter.register(@@testfiles@@);
};
</script>
<style type="text/css">
View
2 lib/browser/runner.js
@@ -53,7 +53,7 @@ exports.BrowserRunner = function (runner) {
}
});
server.listen(3030);
- sys.puts('Logan running on localhost port 3030');
+ sys.puts('Logan ready to run tests on localhost port 3030');
};
View
42 lib/logan.js
@@ -1,21 +1,11 @@
-
if (typeof logan == 'undefined') { logan = {}; }
+
var loganBase = new function () {
- this.files = [];
this.dirname = null;
this.filename = null;
this.total = 0;
this.successes = 0;
- this.register = function (files) {
- this.files = files;
- var file;
- while (file = this.files.shift()) {
- this.adapter.loadTestFile(file);
- }
- this.reportFinal(this.total, this.successes);
- };
-
this.run = function (namespace) {
for (var p in namespace) {
if (/^test/.test(p) && typeof namespace[p] == 'function') {
@@ -26,7 +16,6 @@ var loganBase = new function () {
this.successes++;
}
catch (e) {
- //console.log(e);
var msg = e.message || '';
msg = msg ? '(' + msg + ')' : '';
this.reportTest('Failure: ' + p + ' ' + msg);
@@ -43,8 +32,33 @@ var loganBase = new function () {
return this.adapter.reportTest(str, success);
};
- this.reportFinal = function (total, successes) {
- return this.adapter.reportFinal(total, successes);
+ this.reportFinal = function () {
+ var failures = this.total - this.successes;
+ return this.adapter.reportFinal(this.total, this.successes, failures);
+ };
+
+ this.namespace = function (namespaceString, explicitContext) {
+ var spaces = namespaceString.split('.')
+ , ctxt = explicitContext
+ , key;
+ if (!ctxt) {
+ if (typeof window != 'undefined') {
+ ctxt = window;
+ }
+ if (typeof global != 'undefined') {
+ ctxt = global;
+ }
+ }
+ if (!ctxt) {
+ throw new Error('No context available for creating a namespace object.');
+ }
+ for (var i = 0, ii = spaces.length; i < ii; i++) {
+ key = spaces[i];
+ if (!ctxt[key]) {
+ ctxt[key] = {};
+ }
+ ctxt = ctxt[key];
+ }
};
}();
View
9 lib/node/adapter.js
@@ -1,15 +1,18 @@
var sys = require('sys');
-var NodeAdapter = function () {};
+var NodeAdapter = function (logan) {
+ this.logan = logan;
+};
NodeAdapter.prototype = new function () {
this.reportTest = function (str, success) {
var cl = success ? 'success' : 'failure';
sys.puts(str);
};
- this.reportFinal = function (total, successes) {
- var results = 'Tests run: ' + total + ' total, ' + successes + ' successes';
+ this.reportFinal = function (total, successes, failures) {
+ var results = 'Tests run: ' + total + ' total, ' +
+ successes + ' successes, ' + failures + ' failures';
sys.puts(results);
};
View
5 lib/node/runner.js
@@ -1,3 +1,5 @@
+var sys = require('sys');
+
/**
* The runner for Node.js -- this is ridiculously easy. Just load up
* the tests and run them.
@@ -13,11 +15,12 @@ exports.NodeRunner = function (runner) {
// requires from the test source files in the other environments
global.logan = logan;
global.assert = require('assert');
+ sys.puts(list);
// Require each of the test files -- this will run the tests
for (var i = 0, ii = list.length; i < ii; i++) {
require(process.cwd() + list[i].replace(/^\./, '').replace(/\.js$/, ''));
}
- logan.reportFinal(logan.total, logan.successes);
+ logan.reportFinal();
};
};
View
33 lib/runner.js
@@ -4,6 +4,20 @@ var http = require('http')
, ch = require('child_process')
, args = process.argv.slice(2);
+var die = function (str) {
+ sys.puts(str);
+ process.exit();
+}
+
+var usage = ''
+ + 'Logan client-/server-side JavaScript test runner\n'
+ + '{Usage}: logan [browser|node|racer]'
+ + '';
+
+if (!args.length) {
+ die(usage);
+}
+
var runner = new function () {
// List of available environments to run tests in
var _envMap = {browser: true, node: true, racer: true,};
@@ -28,8 +42,13 @@ var runner = new function () {
if (stderr) {
throw new Error(stderr);
}
- _this.testList = _this.getTestList(stdout);
- _this.run();
+ _this.testList = _this.getTestList(stdout);
+ if (_this.testList.length) {
+ _this.run();
+ }
+ else {
+ sys.puts('No test directories in this directory, no tests to run.');
+ }
});
};
@@ -38,10 +57,12 @@ var runner = new function () {
*/
this.getTestList = function (stdout) {
var files = []
- , paths = stdout.replace(/\s$/, ''); // Trim trailing newline
- paths = paths.split('\n');
- for (var i = 0; i < paths.length; i++) {
- files.push(paths[i]);
+ , paths = stdout.replace(/^\s|\s$/, ''); // Trim
+ if (stdout.length) {
+ paths = paths.split('\n');
+ for (var i = 0; i < paths.length; i++) {
+ files.push(paths[i]);
+ }
}
return files;
};
View
66 scripts/Jakefile.js
@@ -0,0 +1,66 @@
+/*
+ * Logan client-/server-side JavaScript test runner
+ * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+*/
+
+var sys = require('sys');
+var child_process = require('child_process');
+var fs = require('fs');
+
+desc('Installs the Logan client-/server-side JavaScript test runner.');
+task('default', [], function () {
+ var uid = process.env.SUDO_UID;
+ var gid = process.env.SUDO_GID;
+ var cmds = [
+ 'mkdir -p ~/.node_libraries/logan'
+ , 'cp -R ./dist/* ~/.node_libraries/logan'
+ , 'chown -R ' + uid + ':' + gid + ' ~/.node_libraries'
+ , 'cp scripts/logan /usr/local/bin/'
+ ];
+ runCmds(cmds, function () {
+ sys.puts('Logan installed.');
+ });
+});
+
+var runCmds = function (arr, callback, printStdout) {
+ var run = function (cmd) {
+ child_process.exec(cmd, function (err, stdout, stderr) {
+ if (err) {
+ sys.puts('Error: ' + JSON.stringify(err));
+ }
+ else if (stderr) {
+ sys.puts('Error: ' + stderr);
+ }
+ else {
+ if (printStdout) {
+ sys.puts(stdout);
+ }
+ if (arr.length) {
+ var next = arr.shift();
+ run(next);
+ }
+ else {
+ if (callback) {
+ callback();
+ }
+ }
+ }
+ });
+ };
+ run(arr.shift());
+};
+
+
View
21 scripts/jake
@@ -0,0 +1,21 @@
+#
+# Node-Jake JavaScript build tool
+# Copyright 2112 Matthew Eernisse (mde@fleegix.org)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#!/bin/bash
+
+node ~/.node_libraries/node-jake/jake.js `pwd` $@
+
View
22 scripts/logan
@@ -0,0 +1,22 @@
+#
+# Logan client-/server-side JavaScript test runner
+# Copyright 2112 Matthew Eernisse (mde@fleegix.org)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#!/bin/bash
+
+node ~/.node_libraries/logan/runner.js $@
+
+

0 comments on commit fb82834

Please sign in to comment.
Something went wrong with that request. Please try again.