Compare commits

...

3 Commits

Author SHA1 Message Date
791853fb2e cleanup + version bump...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2025-12-28 03:59:55 +03:00
4ecfaa1493 cleanup and fixes...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2025-12-28 03:59:04 +03:00
2aa354d30d cleanup... (needs testing!)
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
2025-12-28 03:28:12 +03:00
2 changed files with 110 additions and 211 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "ig-test", "name": "ig-test",
"version": "1.6.2", "version": "1.6.3",
"description": "experimental test runner....", "description": "experimental test runner....",
"main": "test.js", "main": "test.js",
"bin": { "bin": {

291
test.js
View File

@ -8,12 +8,11 @@
* *
* *
* TODO: * TODO:
* - a mor descriptive assertion fail... * - a more descriptive assertion fail...
* would be nice to: * would be nice to:
* - know what exactly failed and what was expected automatically * - know what exactly failed and what was expected automatically
* - ... * - ...
* - list found files... * - list found files...
* - flexible test chains with 0 or more modifiers...
* - might be a good idea to detect test module type and run only our * - might be a good idea to detect test module type and run only our
* ones... * ones...
* *
@ -60,7 +59,7 @@ module.IGNORE_TEST_FILES = ['node_modules/**']
// or // or
// $ VERBOSE=1 node test.js // $ VERBOSE=1 node test.js
// or set this manually after require('./test') but before calling // or set this manually after require('./test') but before calling
// the runner(..) or run(..) // the runTests(..) or run(..)
// NOTE: this may change in the future... // NOTE: this may change in the future...
module.VERBOSE = process ? module.VERBOSE = process ?
process.env.VERBOSE process.env.VERBOSE
@ -275,7 +274,6 @@ var mergeIter = function(iter){
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// //
// Merged(name, func) // Merged(name, func)
// -> merged // -> merged
@ -387,6 +385,8 @@ object.Constructor('Merged', {
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// Get tests from a test object...
//
var getTests = function(spec){ var getTests = function(spec){
var {setups, modifiers, tests, cases} = spec var {setups, modifiers, tests, cases} = spec
;[setups, modifiers, tests, cases] = ;[setups, modifiers, tests, cases] =
@ -402,12 +402,12 @@ var getTests = function(spec){
cases, cases,
} } } }
// XXX
// Parse test chain...
//
var parseChain = var parseChain =
module.parseChain = module.parseChain =
function(spec, chain){ function(chain){
var {setups, modifiers, tests, cases} = getTests(spec)
// parse chain... // parse chain...
chain = (chain == '*' || chain == null) ? chain = (chain == '*' || chain == null) ?
[] []
@ -433,6 +433,16 @@ function(spec, chain){
length, length,
} } } }
// Build test chain queue...
//
// NOTE: this might look too nested-looped but this needs to do several
// dimensions of cartesian products, so no way around this.
//
// XXX add support for partial patterns
// e.g.
// test-*:*-keyword:*x
// XXX log test errors -- unavailable tests, invalid chains, ... etc.
var buildQueue = var buildQueue =
module.buildQueue = module.buildQueue =
function(spec, chain, mod_chain_length=1){ function(spec, chain, mod_chain_length=1){
@ -494,7 +504,7 @@ function(spec, chain, mod_chain_length=1){
return [s, m, t] }) }) }) return [s, m, t] }) }) })
.flat(2) .flat(2)
: [], : [],
cases: chain.length <= 1 ? cases: chain.length == 1 ?
Object.keys(cases) Object.keys(cases)
.filter(function(s){ .filter(function(s){
return typeof(cases[s]) == 'function' return typeof(cases[s]) == 'function'
@ -503,11 +513,78 @@ function(spec, chain, mod_chain_length=1){
: [], : [],
} } } }
var runQueue =
module.runQueue =
function(queue){
}
// Test runner/combinator...
//
// runTests(spec)
// runTests(spec, '*')
// -> promise(stats)
//
// runTests(spec, 'case')
// runTests(spec, 'setup:test')
// runTests(spec, 'setup:mod:test')
// -> promise(stats)
//
//
// This will run
// test(modifier(setup))
// for each test in spec.tests
// for each modifier in spec.modifiers
// for each setup in spec.setups
// case()
// for each case in spec.cases
//
//
// XXX make Assert optional...
// XXX is this a good name???
var runTests =
module.runTests =
async function(spec, chain, stats, mod_chain_length=1, Assert=module.Assert){
var {setups, modifiers, tests, cases} = getTests(spec)
// setup stats...
stats = stats || {}
Object.assign(stats, {
tests: stats.tests || 0,
assertions: stats.assertions || 0,
failures: stats.failures || 0,
time: stats.time || 0,
})
var started = Date.now()
var queue = buildQueue(spec, chain, mod_chain_length)
// NOTE: we are not running these via .map(..) to keep things in
// sequence...
assert = Assert('[TEST]', stats, module.VERBOSE)
for(var [s, m, t] of queue.tests){
// run the test...
stats.tests += 1
var _assert = assert.push(
[s, ...m, t]
// do not print blank pass-through ('-')
// components...
.filter(function(e){
return e != '-' }) )
var d = await setups[s](_assert)
for(var mod of m){
d = await modifiers[mod](_assert, d) }
await tests[t](_assert, d) }
// cases...
assert = Assert('[CASE]', stats, module.VERBOSE)
for(var c of queue.cases){
stats.tests += 1
await cases[c](assert.push(c)) }
// runtime...
stats.time += Date.now() - started
return stats }
//---------------------------------------------------------------------
var TestSet = var TestSet =
module.TestSet = module.TestSet =
@ -528,117 +605,16 @@ object.Constructor('TestSet', {
__assert__: Assert, __assert__: Assert,
// XXX DIVERGED FROM runner(..) -- unify!!!
// ...essentially this looks like runner(..) with a different way to get assert...
// XXX need to be able to use external assert...
// - from context...
// - from arg...
// XXX nested assert(..) need to report nestedness correctly... // XXX nested assert(..) need to report nestedness correctly...
// XXX should/can this return a meaningfull result for it to be used __call__: async function(chain, stats, mod_chain_length=1){
// as a setup/mod??? var assert = this.__assert__
// XXX this is very similar to runner(..)...
__call__: async function(context, chain, stats){
var assert
// running nested... // running nested...
if(typeof(chain) == 'function'){ if(typeof(chain) == 'function'){
assert = chain assert = chain
chain = null chain = null
stats = stats stats = stats
|| assert.stats } || assert.stats }
// parse chain... return runTests(this, chain, stats, mod_chain_length, assert) },
//chain = this.parseChain(chain)
chain = (chain == '*' || chain == null) ?
[]
: chain
chain = chain instanceof Array ?
chain
: chain.split(/:/)
var chain_length = chain.length
var setup = chain.shift() || '*'
var test = chain.pop() || '*'
var mod = chain.pop() || '*'
mod = chain_length == 2 ?
'as-is'
: mod
// get the tests...
var {setups, modifiers, tests, cases} = this
;[setups, modifiers, tests, cases] =
[setups, modifiers, tests, cases]
.map(function(e){
return object.parentOf(Merged, e) ?
e.toObject()
: (e || {}) })
// stats...
stats = stats || {}
Object.assign(stats, {
tests: stats.tests || 0,
assertions: stats.assertions || 0,
failures: stats.failures || 0,
time: stats.time || 0,
})
var started = Date.now()
// tests...
// XXX revise nested assert...
var assert = assert
|| this.__assert__('[TEST]', stats, module.VERBOSE)
var queue =
chain_length != 1 ?
object.deepKeys(tests)
.filter(function(t, i, l){
return typeof(tests[t]) == 'function'
// skip blank tests if we have other tests unless
// explicitly specified...
&& ((t == '-'
&& test != t
&& l.length > 1) ?
false
: (test == '*'
|| test == t) ) })
.map(function(t){
// modifiers...
return object.deepKeys(modifiers)
.filter(function(m){
return typeof(modifiers[m]) == 'function'
&& (mod == '*' || mod == m) })
.map(function(m){
// setups...
return object.deepKeys(setups)
.filter(function(s){
return typeof(setups[s]) == 'function'
&& (setup == '*' || setup == s) })
.map(function(s){
return [s, m, t] }) }) })
.flat(2)
: []
for(var [s, m, t] of queue){
stats.tests += 1
var _assert = assert.push(
[s, m, t]
// do not print blank pass-through ('-')
// components...
.filter(function(e){ return e != '-' }) )
await tests[t](_assert,
await modifiers[m](_assert,
await setups[s](_assert))) }
// cases...
assert = assert
|| this.__assert__('[CASE]', stats, module.VERBOSE)
var queue =
chain_length <= 1 ?
Object.keys(cases)
.filter(function(s){
return typeof(cases[s]) == 'function'
&& (setup == '*' || setup == s) })
: []
for(var c of queue){
stats.tests += 1
cases[c](assert.push(c)) }
// runtime...
stats.time += Date.now() - started
return stats },
__init__: function(func){ __init__: function(func){
// test collections... // test collections...
@ -671,7 +647,6 @@ object.Constructor('TestSet', {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// XXX rename to runner...
module.BASE_TEST_SET = new TestSet() module.BASE_TEST_SET = new TestSet()
module.Setup = module.Setup =
@ -697,82 +672,6 @@ module.merge =
//---------------------------------------------------------------------
// Test runner/combinator...
//
// runner(spec)
// runner(spec, '*')
// -> promise(stats)
//
// runner(spec, 'case')
// runner(spec, 'setup:test')
// runner(spec, 'setup:mod:test')
// -> promise(stats)
//
//
// This will run
// test(modifier(setup))
// for each test in spec.tests
// for each modifier in spec.modifiers
// for each setup in spec.setups
// case()
// for each case in spec.cases
//
//
// NOTE: chaining more than one modifier is not yet supported (XXX)
//
// XXX chain N modifiers...
// XXX make Assert optional...
// XXX is this a good name???
//* XXX do we need a root runner???
// ...if not then need to cleanup run(..) to use TestSet / BASE_TEST_SET...
var runner =
module.runner =
async function(spec, chain, stats, mod_chain_length=1){
var {setups, modifiers, tests, cases} = getTests(spec)
// setup stats...
stats = stats || {}
Object.assign(stats, {
tests: stats.tests || 0,
assertions: stats.assertions || 0,
failures: stats.failures || 0,
time: stats.time || 0,
})
var started = Date.now()
var queue = buildQueue(spec, chain, mod_chain_length)
// NOTE: we are not running these via .map(..) to keep things in
// sequence...
var assert = Assert('[TEST]', stats, module.VERBOSE)
for(var [s, m, t] of queue.tests){
// run the test...
stats.tests += 1
var _assert = assert.push(
[s, ...m, t]
// do not print blank pass-through ('-')
// components...
.filter(function(e){
return e != '-' }) )
var d = await setups[s](_assert)
for(var mod of m){
d = await modifiers[mod](_assert, d) }
await tests[t](_assert, d) }
// cases...
var assert = Assert('[CASE]', stats, module.VERBOSE)
for(var c of queue.cases){
stats.tests += 1
await cases[c](assert.push(c)) }
// runtime...
stats.time += Date.now() - started
return stats }
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// CLI... // CLI...
// //
@ -1085,10 +984,10 @@ function(default_files, tests){
// run the tests... // run the tests...
if(chains.length > 0){ if(chains.length > 0){
for(var chain of chains){ for(var chain of chains){
await runner(tests, chain, stats, this.mod_chain_length) } await runTests(tests, chain, stats, this.mod_chain_length) }
//await module.BASE_TEST_SET(tests, chain, stats) } //await module.BASE_TEST_SET(tests, chain, stats) }
} else { } else {
await runner(tests, '*', stats, this.mod_chain_length) } await runTests(tests, '*', stats, this.mod_chain_length) }
//await module.BASE_TEST_SET(tests, '*', stats) } //await module.BASE_TEST_SET(tests, '*', stats) }
// XXX BUG for some reason we can get here BEFORE all the // XXX BUG for some reason we can get here BEFORE all the