├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── bin └── equinox ├── docs ├── architecture.md ├── install.md └── testing.md ├── examples ├── failing.js └── simple.js ├── index.js ├── lib ├── db.js ├── loader.js ├── reporters │ ├── dot.js │ ├── spec.js │ └── tap.js ├── runners │ └── async.js └── util.js ├── package.json ├── stubs ├── run.js ├── setup.js └── teardown.js └── test ├── index.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | - '0.12' 5 | - '0.10' 6 | sudo: false 7 | cache: 8 | directories: 9 | - node_modules 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Equinox change log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## 1.1.0 7 | * update documentation quite a bit 8 | * move repo 9 | * add environment variables for database connection 10 | * add `equinox` namespace 11 | * add deprecation notices for `console` 12 | 13 | ## 1.0.4 14 | * quick release to fix some typos 15 | * fix `funcname` to better create the function name 16 | 17 | ## 1.0.1 18 | * add tap reporter 19 | 20 | ## 1.0.0 21 | * first release 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Contributions welcome! 4 | 5 | **Before spending lots of time on something, ask for feedback on your idea first!** 6 | 7 | Please search issues and pull requests before adding something new to avoid duplicating efforts and conversations. 8 | 9 | This project welcomes non-code contributions, too! The following types of contributions are welcome: 10 | 11 | - **Ideas**: participate in an issue thread or start your own to have your voice heard. 12 | - **Writing**: contribute your expertise in an area by helping expand the included content. 13 | - **Copy editing**: fix typos, clarify language, and generally improve the quality of the content. 14 | - **Formatting**: help keep content easy to read with consistent formatting. 15 | 16 | ## Code Style 17 | 18 | This repository uses [`eslint`][eslint-url] to maintain code style and consistency and avoid style arguments. `npm test` runs `eslint` so you don't have to! 19 | 20 | 21 | ## Rules 22 | 23 | There are a few basic ground rules for collaborators: 24 | 25 | 1. **No `--force` pushes** or modifying the Git history in any way. 26 | 1. **Non-master branches** ought to be used for ongoing work. 27 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull request** to solicit feedback from other contributors. 28 | 1. Internal pull requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 29 | 1. Contributors should attempt to adhere to the prevailing code style. 30 | 31 | ## Releases 32 | 33 | Declaring formal releases remains the prerogative of the project maintainer. 34 | 35 | ## Changes to this arrangement 36 | 37 | This is an experiment and feedback is welcome! This document may also be subject to pull requests or changes by contributors where you believe you have something valuable to add or change. 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # [MIT License](https://spdx.org/licenses/MIT) 2 | 3 | Copyright (c) 2016 Jerry Sievert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Equinox 2 | 3 | Testing for Postgres 4 | 5 | Write tests, and let `equinox` do the rest for you! 6 | 7 | ## Install 8 | 9 | `equinox` requires [PLV8](https://github.com/plv8/plv8) to be installed before 10 | running tests. PLV8 adds Javascript as an extension in Postgres. There are 11 | operating system specific directions available in [install.md](docs/install.md). 12 | 13 | ``` 14 | npm install equinox 15 | ``` 16 | 17 | ## Usage 18 | 19 | Run all tests against a database named `database`, with the default `dot` 20 | reporter. 21 | 22 | ``` 23 | $ equinox -d database --files examples/*.js 24 | ``` 25 | 26 | ### Options 27 | 28 | There are several options available for running tests: 29 | 30 | Parameter|Description|Default 31 | ---|---|--- 32 | files|Files to load and execute| 33 | database|Database to run in| 34 | host|Hostname to connect to|localhost 35 | user|Database user to connect with| 36 | password|Database password to connect with| 37 | reporter|Test reporter to use (dot, spec, tap)|dot 38 | 39 | In addition to these options, it is possible to set `environment` variables 40 | that define the database connection. These will be overridden by any command-line 41 | arguments: 42 | 43 | * `PGDATABASE` 44 | * `PGHOST` 45 | * `PGUSER` 46 | * `PGPASSWORD` 47 | 48 | ### Example Tests 49 | 50 | Tests are simple to write and start using immediately. 51 | 52 | ```js 53 | var tests = [ 54 | { 55 | 'should equal 1': function ( ) { 56 | var result = plv8.execute('SELECT 1 AS num'); 57 | assert.equal(result.length, 1, "length should be 1"); 58 | assert.equal(result[0].num, 1, "num should equal 1"); 59 | } 60 | } 61 | ]; 62 | ``` 63 | 64 | ## Contributing 65 | 66 | Contributions welcome! Please read the [contributing guidelines](CONTRIBUTING.md) first. 67 | 68 | ## License 69 | 70 | [MIT](LICENSE.md) 71 | -------------------------------------------------------------------------------- /bin/equinox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var yargs = require('yargs') 4 | .usage('Equinox.\nUsage: $0') 5 | .demand('database') 6 | .alias('d', 'database') 7 | .describe('database', 'Database to run in') 8 | .default('host', 'localhost') 9 | .alias('h', 'host') 10 | .describe('host', 'Hostname') 11 | .alias('u', 'user') 12 | .describe('user', 'User') 13 | .alias('p', 'password') 14 | .describe('password', 'Password') 15 | .alias('port', 'port') 16 | .describe('port', 'Port') 17 | .array('files') 18 | .describe('files', 'Files to load and execute') 19 | .describe('reporter', 'Test reporter') 20 | .alias('r', 'reporter') 21 | .default('reporter', 'dot') 22 | ; 23 | 24 | var argv = yargs.argv; 25 | var db = require(__dirname + '/../lib/db'); 26 | var loader = require(__dirname + '/../lib/loader'); 27 | var runner = require('../lib/runners/async').runner; 28 | var dot = require('../lib/reporters/dot').reporter; 29 | var spec = require('../lib/reporters/spec').reporter; 30 | var tap = require('../lib/reporters/tap').reporter; 31 | 32 | var reporter = dot; 33 | 34 | if (argv.reporter === 'spec') { 35 | reporter = spec; 36 | } else if (argv.reporter === 'tap') { 37 | reporter = tap; 38 | } 39 | 40 | var fs = require('fs'); 41 | 42 | if (argv.database === true) { 43 | yargs.showHelp(); 44 | process.exit(0); 45 | } 46 | 47 | var files = [ ]; 48 | 49 | // read any file arguments 50 | if (argv.files) { 51 | files = files.concat(argv.files); 52 | } 53 | 54 | // look for any environment variables that could fill in any blanks, command-line 55 | // overrides anything in an environment variable 56 | if (argv.host === undefined) { 57 | argv.host = process.env.PGHOST; 58 | } 59 | 60 | if (argv.user === undefined) { 61 | argv.user = process.env.PGUSER; 62 | } 63 | 64 | if (argv.password === undefined) { 65 | argv.password = process.env.PGPASS; 66 | } 67 | 68 | if (argv.database === undefined) { 69 | argv.database = process.env.PGDATABASE; 70 | } 71 | 72 | // try to connect to the database 73 | db.connect (argv.database, argv.host, argv.user, argv.password, argv.port, function (err) { 74 | if (err) { 75 | console.log("ERROR: Unable to connect to database (" + err.toString() + ")"); 76 | return; 77 | } 78 | 79 | db.query('CREATE SCHEMA IF NOT EXISTS equinox', function (err) { 80 | if (err) { 81 | console.log("ERROR: " + err); 82 | process.exit(1); 83 | } 84 | 85 | loader.load(files, function (err) { 86 | if (err) { 87 | console.log("ERROR: " + err); 88 | process.exit(1); 89 | } 90 | 91 | // execute tests here 92 | runner(files, function (err, results) { 93 | reporter(results); 94 | 95 | // drop the schema` 96 | db.query('DROP SCHEMA equinox CASCADE', function (err, res) { 97 | if (err) { 98 | console.log("ERROR: " + err); 99 | process.exit(1); 100 | } 101 | 102 | process.exit(0); 103 | }); 104 | }); 105 | }); 106 | }); 107 | }); 108 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | Equinox is built on top of *Node.js* and *PLV8*. The script that does the 4 | instrumentation and reporting is written to run in *Node.js*, while the 5 | tests themselves are written in Javascript, and run inside of *PLV8*. 6 | 7 | ## Inside the Database 8 | 9 | Equinox creates a *schema* named *equinox* in your database when it runs. 10 | It then loads all of your tests into that schema by name as *Postgres* 11 | functions. These functions include all of the information needed to setup, 12 | execute, and teardown your tests, there is no need for you to add anything 13 | but the test code. 14 | 15 | ### Schema 16 | 17 | The tests rely on a schema existing named *equinox*. Don't worry, this is 18 | created automatically for you when you run tests: 19 | 20 | ``` 21 | CREATE SCHEMA IF NOT EXISTS equinox 22 | ``` 23 | 24 | When the tests are complete, it destroys the schema: 25 | 26 | ``` 27 | DROP SCHEMA equinox CASCADE 28 | ``` 29 | 30 | ### Tests 31 | 32 | Tests are added to the database in the *equinox* schema, and executed after all 33 | are added. In the event of a failure to add, the schema will be destroyed, and 34 | the error will be reported. 35 | 36 | Once all tests are added, they are executed in order, and the results are 37 | reported via the `reporter` of your choice: 38 | 39 | * spec 40 | * dot 41 | * tap 42 | 43 | ## Inside Node.js 44 | 45 | *Node.js* is used to instrument the tests. It creates the *equinix schema*, 46 | adds the tests by name, executes them in *postgres*, and returns the results 47 | in the format requested. 48 | 49 | The tests are run using the `pg` module, and running _SELECT_ against each 50 | function. The results are tallied, and reported. 51 | 52 | ## Inside Postgres 53 | 54 | As functions are added to *postgres*, they are created as _functions_, and 55 | instrumented with additional functionality. 56 | 57 | ``` 58 | CREATE OR REPLACE FUNCTION _modulename_ RETURNS JSON AS $$ 59 | // instrumentation 60 | // [...] 61 | 62 | your_code_here() 63 | 64 | // return results of tests 65 | return test_results; 66 | $$ LANGUAGE plv8; 67 | ``` 68 | 69 | These tests are then run in the order that they are loaded, executing any 70 | `setup()` or `teardown()` as needed. 71 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # Installing 2 | 3 | Equinox requires [Node.js](https://nodejs.org/) in order to instrument the 4 | database and execute tests. 5 | 6 | ``` 7 | $ npm install equinox 8 | ``` 9 | 10 | This brings `equinox` into your development environment. If you are using 11 | `node.js` for development and testing, you can add `equinox` to your tests 12 | in `package.json`: 13 | 14 | ``` 15 | "scripts": { 16 | "test": "equinox -d test --files ./test/tests.js -r tap" 17 | }, 18 | ``` 19 | 20 | If you are using an alternate development environment, you can install 21 | `equinox` globally using `npm`: 22 | 23 | ``` 24 | $ npm install -g equinox 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Equinox is an opinionated testing framework. 4 | 5 | Tests that rely on each other should exist in one `file` - at the start of each 6 | `file` being executed, a new `schema` is created in the database, and all tests 7 | are executed within this `schema`. 8 | 9 | All tests are implemented in Javascript, and ascribe to the `PLV8` functions 10 | for database interaction. 11 | 12 | ## API 13 | 14 | There is an API available for testing. This falls into how the [architecture](architecure.md) sets up the tests. 15 | 16 | ## Tests 17 | 18 | Tests are defined in a variable named `tests`. It is defined as an `array` of 19 | `objects`, that contain code and assertions in the form of `assert()`: 20 | 21 | ``` 22 | var tests = [ 23 | { 24 | 'should equal 1': function ( ) { 25 | var result = plv8.execute('SELECT 1 AS num'); 26 | assert.equal(result.length, 1, "length should be 1"); 27 | assert.equal(result[0].num, 1, "num should equal 1"); 28 | } 29 | } 30 | ]; 31 | ``` 32 | 33 | ## Test Setup 34 | 35 | Tests have the option of a `setup()` method. If a `setup()` method exists, 36 | it is executed before all tests are run. 37 | 38 | ```js 39 | function setup ( ) { 40 | plv8.execute('CREATE TABLE logs (data JSONB)'); 41 | } 42 | ``` 43 | 44 | ### Per Test 45 | 46 | Each test also has the ability to have a `setup()` method: 47 | 48 | ``` 49 | var tests = [ 50 | { 51 | setup: function ( ) { 52 | plv8.execute('INSERT INTO logs (data) VALUES ('{ "foo": "bar" }')'); 53 | }, 54 | 'should return a result': function ( ) { 55 | var result = plv8.execute('SELECT data FROM logs'); 56 | assert.equal(result.length, 1, "length should be 1"); 57 | assert.equal(result[0].data.foo, 'bar', "data should equal bar"); 58 | } 59 | } 60 | ]; 61 | ``` 62 | 63 | ## Test Teardown 64 | 65 | Tests have the option of a `teardown()` method. If a `teardown()` method exists, 66 | it is executed before all tests are run. 67 | 68 | ```js 69 | function teardown ( ) { 70 | plv8.execute('DROP TABLE logs'); 71 | } 72 | ``` 73 | 74 | ### Per Test 75 | 76 | Each test also has the ability to have a `teardown()` method: 77 | 78 | ``` 79 | var tests = [ 80 | { 81 | teardown: function ( ) { 82 | plv8.execute('DELETE FROM logs'); 83 | }, 84 | 'should return a result': function ( ) { 85 | var result = plv8.execute('SELECT data FROM logs'); 86 | assert.equal(result.length, 1, "length should be 1"); 87 | assert.equal(result[0].data.foo, 'bar', "data should equal bar"); 88 | } 89 | } 90 | ]; 91 | ``` 92 | -------------------------------------------------------------------------------- /examples/failing.js: -------------------------------------------------------------------------------- 1 | var tests = [ 2 | { 3 | 'should equal 1': function ( ) { 4 | var result = plv8.execute('SELECT 2 AS num'); 5 | console.log('woo'); 6 | assert.equal(result.length, 1, "length should be 1"); 7 | assert.equal(result[0].num, 1, "num should equal 1"); 8 | } 9 | } 10 | ]; 11 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | var tests = [ 2 | { 3 | 'should equal 1': function ( ) { 4 | var result = plv8.execute('SELECT 1 AS num'); 5 | assert.equal(result.length, 1, "length should be 1"); 6 | assert.equal(result[0].num, 1, "num should equal 1"); 7 | } 8 | } 9 | ]; 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var db = require('./lib/db'); 2 | var loader = require('./lib/loader'); 3 | var util = require('./lib/util'); 4 | var runner = require('./lib/runners/async'); 5 | 6 | exports.db = db; 7 | exports.loader = loader; 8 | exports.util = util; 9 | exports.runner = runner; 10 | -------------------------------------------------------------------------------- /lib/db.js: -------------------------------------------------------------------------------- 1 | var pg = require('pg'); 2 | 3 | var pg_client; 4 | 5 | function connect (database, hostname, username, password, port, callback) { 6 | var conString = 'postgres://'; 7 | 8 | if (username) { 9 | conString += username; 10 | if (password) { 11 | conString += ':'; 12 | conString += password; 13 | } 14 | conString += '@'; 15 | } 16 | 17 | if (hostname) { 18 | conString += hostname; 19 | if (port) { 20 | conString += ':'; 21 | conString += port; 22 | } 23 | conString += '/'; 24 | } 25 | 26 | conString += database; 27 | 28 | pg.connect(conString, function (err, client, done) { 29 | if (err) { 30 | return callback(err); 31 | } 32 | 33 | pg_client = client; 34 | 35 | callback(null, client); 36 | }); 37 | } 38 | 39 | function query (sql, params, callback) { 40 | if (pg_client === undefined) { 41 | return callback("not connected to the database. did you try connect()?"); 42 | } 43 | 44 | pg_client.query(sql, params, callback); 45 | } 46 | 47 | exports.connect = connect; 48 | exports.query = query; 49 | -------------------------------------------------------------------------------- /lib/loader.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var db = require('./db'); 3 | var util = require('./util'); 4 | 5 | var setup = fs.readFileSync(__dirname + '/../stubs/setup.js', 'utf8'); 6 | var run = fs.readFileSync(__dirname + '/../stubs/run.js', 'utf8'); 7 | var teardown = fs.readFileSync(__dirname + '/../stubs/teardown.js', 'utf8'); 8 | 9 | function _apply (filename, content, callback) { 10 | var funcname = util.create_funcname(filename); 11 | var sql = "CREATE OR REPLACE FUNCTION equinox." + funcname + "( ) RETURNS JSON AS $$\n"; 12 | 13 | sql += setup; 14 | sql += content; 15 | sql += "\n"; 16 | sql += "var __filename = \"" + filename + "\";\n"; 17 | sql += run; 18 | sql += teardown; 19 | 20 | sql += "\n$$ LANGUAGE plv8"; 21 | 22 | db.query(sql, [ ], callback); 23 | } 24 | 25 | function load (files, callback) { 26 | var count = 0; 27 | var contents = [ ]; 28 | var i; 29 | var sql; 30 | 31 | if (files.length === 0) { 32 | return callback("No files specified"); 33 | } 34 | 35 | for (i = 0; i < files.length; i++) { 36 | try { 37 | sql = fs.readFileSync(process.cwd() + '/' + files[i], 'utf8'); 38 | contents.push(sql); 39 | } catch (err) { 40 | return callback("Unable to read file " + files[i]); 41 | } 42 | } 43 | 44 | for (i = 0; i < contents.length; i++) { 45 | _apply(files[i], contents[i], function (err) { 46 | if (err) { 47 | console.log("ERROR: " + err); 48 | } 49 | 50 | count++; 51 | if (count === contents.length) { 52 | return callback(); 53 | } 54 | }); 55 | } 56 | } 57 | 58 | exports.load = load; 59 | -------------------------------------------------------------------------------- /lib/reporters/dot.js: -------------------------------------------------------------------------------- 1 | var color = require("ansi-color").set; 2 | var util = require('../util'); 3 | 4 | var out = ''; 5 | var failures = [ ]; 6 | var pass_count = 0; 7 | var fail_count = 0; 8 | 9 | function dotReporter (results, testname) { 10 | var passed = util.all_passed(results, testname); 11 | 12 | if (passed) { 13 | out += color('.', "green"); 14 | } else { 15 | out += color('✗', "red"); 16 | } 17 | } 18 | 19 | function fail (results) { 20 | var failed = util.all_failed(results); 21 | 22 | fail_count += failed.length; 23 | 24 | failures = failures.concat(failed); 25 | } 26 | 27 | function failureReporter ( ) { 28 | for (var i = 0; i < failures.length; i++) { 29 | var line = '\n' + failures[i].filename + ' : ' + failures[i].current_test + "\n"; 30 | 31 | line += (" " + failures[i].message + "\n expected " + 32 | failures[i].expected + ", got " + failures[i].actual + 33 | " (" + failures[i].operator + ")\n"); 34 | 35 | console.log(color(line, 'red')); 36 | } 37 | } 38 | 39 | function reporter (results) { 40 | var test_results = util.transform(results); 41 | 42 | var keys = Object.keys(test_results); 43 | 44 | for (var i = 0; i < keys.length; i++) { 45 | var innerKeys = Object.keys(test_results[keys[i]]); 46 | 47 | for (var j = 0; j < innerKeys.length; j++) { 48 | dotReporter(test_results[keys[i]][innerKeys[j]], innerKeys[j]); 49 | fail(test_results[keys[i]][innerKeys[j]], innerKeys[j]); 50 | } 51 | } 52 | 53 | console.log(out); 54 | 55 | failureReporter( ); 56 | } 57 | 58 | exports.reporter = reporter; 59 | -------------------------------------------------------------------------------- /lib/reporters/spec.js: -------------------------------------------------------------------------------- 1 | var color = require("ansi-color").set; 2 | 3 | function specReporter (name, results) { 4 | console.log("\n" + name + ":\n"); 5 | 6 | var current_test; 7 | 8 | for (var i = 0; i < results.length; i++) { 9 | if (current_test !== results[i].current_test) { 10 | current_test = results[i].current_test; 11 | 12 | console.log(" " + results[i].current_test); 13 | } 14 | 15 | if (results[i].status === 'pass') { 16 | console.log(" " + color("✓", "green") + " " + results[i].message); 17 | } else { 18 | console.log(" " + color("✗", "red") + " " + results[i].message); 19 | console.log(" » " + color("expected " + results[i].expected + ",", "yellow")); 20 | console.log(" " + color("got " + results[i].actual + " (" + results[i].operator + ")", "yellow")); 21 | } 22 | } 23 | } 24 | 25 | function reporter (results) { 26 | var keys = Object.keys(results); 27 | 28 | for (var i = 0; i < keys.length; i++) { 29 | specReporter(keys[i], results[keys[i]]); 30 | } 31 | } 32 | 33 | exports.reporter = reporter; 34 | -------------------------------------------------------------------------------- /lib/reporters/tap.js: -------------------------------------------------------------------------------- 1 | var pass = 0; 2 | var fail = 0; 3 | var count = 0; 4 | 5 | function tapReporter (name, results) { 6 | console.log("# " + name); 7 | 8 | for (var i = 0; i < results.length; i++) { 9 | count++; 10 | 11 | if (results[i].status === 'pass') { 12 | pass++; 13 | console.log("ok " + count + " " + results[i].message); 14 | } else { 15 | fail++; 16 | console.log("not ok " + count + " " + results[i].message); 17 | console.log(" ---"); 18 | console.log(" operator: " + results[i].operator); 19 | console.log(" expected: " + results[i].expected); 20 | console.log(" actual: " + results[i].actual); 21 | console.log(" ..."); 22 | } 23 | } 24 | } 25 | 26 | function reporter (results) { 27 | console.log("TAP version 13"); 28 | 29 | var keys = Object.keys(results); 30 | 31 | for (var i = 0; i < keys.length; i++) { 32 | tapReporter(keys[i], results[keys[i]]); 33 | } 34 | 35 | console.log("1.." + count); 36 | console.log("# tests " + count); 37 | 38 | if (pass) { 39 | console.log("# pass " + pass); 40 | } 41 | 42 | if (fail) { 43 | console.log("# fail " + fail); 44 | } 45 | } 46 | 47 | exports.reporter = reporter; 48 | -------------------------------------------------------------------------------- /lib/runners/async.js: -------------------------------------------------------------------------------- 1 | var db = require('../db'); 2 | var util = require('../util'); 3 | 4 | var current = 0; 5 | var results = { }; 6 | var count; 7 | 8 | function _execute_query (filename, callback) { 9 | var funcname = util.create_funcname(filename); 10 | 11 | db.query("SELECT equinox." + funcname + "() AS result", function (err, data) { 12 | if (err) { 13 | results[filename] = { "error": err }; 14 | } else { 15 | if (data.rowCount === 1) { 16 | results[filename] = data.rows[0].result; 17 | } else { 18 | results[filename] = { "error": "Expected one row returned, got " + data.rowsCount }; 19 | } 20 | } 21 | 22 | current++; 23 | 24 | if (current === count) { 25 | callback(null, results); 26 | } 27 | }); 28 | 29 | } 30 | 31 | function async_runner (files, callback) { 32 | if (files.length === 0) { 33 | return callback({ "error": "Expected at least one test to run" }); 34 | } 35 | db.query("BEGIN"); 36 | 37 | count = files.length; 38 | 39 | for (var i = 0; i < count; i++) { 40 | _execute_query(files[i], callback); 41 | } 42 | 43 | db.query("ROLLBACK"); 44 | } 45 | 46 | exports.runner = async_runner; 47 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | function create_funcname (filename) { 4 | var funcname = filename.replace(/\//g, "_"); 5 | funcname = funcname.replace(/\./g, "_"); 6 | funcname = funcname.replace(/\\/g, "_"); 7 | 8 | return funcname; 9 | } 10 | 11 | function transform (results) { 12 | var test_results = { }; 13 | 14 | var keys = Object.keys(results); 15 | 16 | for (var i = 0; i < keys.length; i++) { 17 | if (test_results[keys[i]] === undefined) { 18 | test_results[keys[i]] = { }; 19 | } 20 | 21 | for (var j = 0; j < results[keys[i]].length; j++) { 22 | if (test_results[keys[i]][results[keys[i]][j].current_test] === undefined) { 23 | test_results[keys[i]][results[keys[i]][j].current_test] = [ ]; 24 | } 25 | 26 | test_results[keys[i]][results[keys[i]][j].current_test].push(results[keys[i]][j]); 27 | } 28 | } 29 | 30 | return test_results; 31 | } 32 | 33 | function all_passed (results, current_test) { 34 | var filtered = results.filter(function (item) { 35 | if (item.current_test === current_test) { 36 | return true; 37 | } 38 | 39 | return false; 40 | }); 41 | 42 | var actual = filtered.filter(function (item) { 43 | if (item.status === 'pass') { 44 | return true; 45 | } 46 | 47 | return false; 48 | }); 49 | 50 | return actual.length === filtered.length; 51 | } 52 | 53 | function all_failed (results) { 54 | var filtered = results.filter(function (item) { 55 | if (item.status === 'pass') { 56 | return false; 57 | } 58 | 59 | return true; 60 | }); 61 | 62 | return filtered; 63 | } 64 | 65 | exports.create_funcname = create_funcname; 66 | exports.transform = transform; 67 | exports.all_passed = all_passed; 68 | exports.all_failed = all_failed; 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "equinox", 3 | "description": "Testing for Postgres", 4 | "version": "1.1.0", 5 | "author": "Jerry Sievert ", 6 | "dependencies": { 7 | "ansi-color": "^0.2.1", 8 | "pg": "^4.5.1", 9 | "yargs": "^4.3.1" 10 | }, 11 | "bin": { 12 | "equinox": "./bin/equinox" 13 | }, 14 | "keywords": [ 15 | "plv8", 16 | "postgres", 17 | "test", 18 | "testing", 19 | "tests" 20 | ], 21 | "license": "MIT", 22 | "main": "index.js", 23 | "scripts": { 24 | "test": "eslint lib/*.js lib/*/*.js index.js bin/equinox && tape test/*.js | tap-nyan" 25 | }, 26 | "devDependencies": { 27 | "eslint": "^2.5.3", 28 | "tap-nyan": "0.0.2", 29 | "tap-spec": "^4.1.1", 30 | "tape": "^4.5.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /stubs/run.js: -------------------------------------------------------------------------------- 1 | // iterate through the tests, running each 2 | for (var i = 0; i < tests.length; i++) { 3 | var test = tests[i]; 4 | var keys = Object.keys(test).filter(function (item) { 5 | if (item === "setup" || item === "teardown") { 6 | return false; 7 | } 8 | 9 | return true; 10 | }); 11 | 12 | // run the setup 13 | if (test.setup && typeof test.setup === 'function') { 14 | test.setup(); 15 | } 16 | 17 | // run the tests 18 | for (var j = 0; j < keys.length; j++) { 19 | if (typeof test[keys[j]] === 'function') { 20 | current_test = keys[j]; 21 | try { 22 | test[keys[j]](); 23 | } catch (err) { 24 | fail(undefined, undefined, undefined, undefined, err); 25 | } 26 | } else { 27 | current_test = null; 28 | } 29 | } 30 | 31 | // run the teardown 32 | if (test.teardown && typeof test.teardown === 'function') { 33 | test.teardown(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /stubs/setup.js: -------------------------------------------------------------------------------- 1 | // test statuses stored here 2 | var test_statuses = [ ]; 3 | var current_test; 4 | 5 | // assertion failure 6 | function fail (actual, expected, message, operator, error) { 7 | test_statuses.push({ 8 | actual: actual, 9 | expected: expected, 10 | message: message, 11 | operator: operator, 12 | status: "fail", 13 | error: error, 14 | current_test: current_test, 15 | filename: __filename 16 | }); 17 | } 18 | 19 | // assertion ok 20 | function ok (actual, expected, message) { 21 | test_statuses.push({ 22 | actual: actual, 23 | expected: expected, 24 | status: "pass", 25 | message: message, 26 | current_test: current_test, 27 | filename: __filename 28 | }); 29 | } 30 | 31 | // assert methods 32 | var assert = { 33 | equal: function (actual, expected, message) { 34 | if (actual !== expected) { 35 | fail(actual, expected, message, "=="); 36 | } else { 37 | ok(actual, expected, message); 38 | } 39 | } 40 | }; 41 | 42 | // initial setup 43 | var tests = [ ]; 44 | 45 | // equinox namespace 46 | equinox = { 47 | log: function ( ) { 48 | var args = Array.prototype.slice.call(arguments); 49 | plv8.elog(NOTICE, args.join(' ')); 50 | }, 51 | warn: function ( ) { 52 | var args = Array.prototype.slice.call(arguments); 53 | plv8.elog(WARNING, args.join(' ')); 54 | }, 55 | error: function ( ) { 56 | var args = Array.prototype.slice.call(arguments); 57 | plv8.elog(ERROR, args.join(' ')); 58 | } 59 | }; 60 | 61 | // console methods - deprecated as of 1.1.0 62 | console = { 63 | log: function ( ) { 64 | equinox.warn('DEPRICATION WARNING: console.log() has been deprecated, use equinox.log instead.'); 65 | equinox.log.call(arguments); 66 | }, 67 | warn: function ( ) { 68 | equinox.warn('DEPRICATION WARNING: console.warn() has been deprecated, use equinox.log instead.'); 69 | equinox.warn.call(arguments); 70 | }, 71 | error: function ( ) { 72 | equinox.warn('DEPRICATION WARNING: console.error() has been deprecated, use equinox.log instead.'); 73 | equinox.error.call(arguments); 74 | } 75 | }; 76 | 77 | // check for setup method 78 | if (typeof test_setup === 'function') { 79 | test_setup(); 80 | } 81 | -------------------------------------------------------------------------------- /stubs/teardown.js: -------------------------------------------------------------------------------- 1 | // check for teardown method 2 | if (typeof test_teardown === 'function') { 3 | test_teardown(); 4 | } 5 | 6 | return test_statuses; 7 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerrySievert/equinox/80e9de3fbc82935cfc98efcf9c981c4004b65e4f/test/index.js -------------------------------------------------------------------------------- /test/util.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var util = require('../lib/util'); 3 | 4 | var failing = [ { actual: 1, 5 | expected: 1, 6 | status: 'pass', 7 | message: 'length should be 1', 8 | current_test: 'should equal 1' }, 9 | { actual: 2, 10 | expected: 1, 11 | message: 'num should equal 1', 12 | operator: '==', 13 | status: 'fail', 14 | current_test: 'should equal 1' } ]; 15 | 16 | var passing = [ { actual: 1, 17 | expected: 1, 18 | status: 'pass', 19 | message: 'length should be 1', 20 | current_test: 'should equal 1' }, 21 | { actual: 1, 22 | expected: 1, 23 | message: 'num should equal 1', 24 | operator: '==', 25 | status: 'pass', 26 | current_test: 'should equal 1' } ]; 27 | 28 | test('all_passed() passes when correct', function (t) { 29 | t.plan(1); 30 | 31 | var status = util.all_passed(passing, 'should equal 1'); 32 | 33 | t.equal(status, true, 'all_passed() returns true'); 34 | }); 35 | 36 | test('all_passed() fails when not correct', function (t) { 37 | t.plan(1); 38 | 39 | var status = util.all_passed(failing, 'should equal 1'); 40 | 41 | t.equal(status, false, 'all_passed() returns false'); 42 | }); 43 | 44 | test('create_funcname() creates the correct function names', function (t) { 45 | t.plan(2); 46 | 47 | t.equal(util.create_funcname('foo.js'), 'foo_js', 'dots are replaced'); 48 | t.equal(util.create_funcname('foo/bar.js'), 'foo_bar_js', 'slashes are replaced'); 49 | }); 50 | 51 | test('all_failed() returns the correct failed entries', function (t) { 52 | t.plan(1); 53 | 54 | var failed = util.all_failed(failing); 55 | 56 | t.equal(failed.length, 1, 'a single failure found'); 57 | }); 58 | --------------------------------------------------------------------------------