Mike Gerwitz

Activist for User Freedom

aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Gerwitz <mike.gerwitz@rtspecialty.com>2018-02-19 15:21:16 -0500
committerMike Gerwitz <mike.gerwitz@rtspecialty.com>2018-02-19 15:21:16 -0500
commit79cc96e02671e5b976c534a216f11ff287c07598 (patch)
tree9d260e2a0c410c92aee697868d4fe764b9c227c5
parent7f26db41b211694530acb5c3cd881c14e508b7df (diff)
parent4b1a72db810341eaf2c3282f0d38b12e5e4da18b (diff)
downloadtame-79cc96e02671e5b976c534a216f11ff287c07598.tar.gz
tame-79cc96e02671e5b976c534a216f11ff287c07598.tar.bz2
tame-79cc96e02671e5b976c534a216f11ff287c07598.zip
Merge branch 'progtest'
-rw-r--r--.gitignore4
-rw-r--r--doc/.gitignore3
-rw-r--r--progtest/.gitignore6
-rw-r--r--progtest/Makefile40
-rw-r--r--progtest/README.md5
-rw-r--r--progtest/bin/runner.js36
-rwxr-xr-xprogtest/build-aux/gen-index58
-rw-r--r--progtest/package.json16
-rw-r--r--progtest/src/TestCase.js103
-rw-r--r--progtest/src/TestRunner.js189
-rw-r--r--progtest/src/env.js73
-rw-r--r--progtest/src/reader/ConstResolver.js110
-rw-r--r--progtest/src/reader/DateResolver.js61
-rw-r--r--progtest/src/reader/TestReader.js40
-rw-r--r--progtest/src/reader/YamlTestReader.js79
-rw-r--r--progtest/src/reporter/ConsoleTestReporter.js183
-rw-r--r--progtest/src/reporter/NullTestReporter.js79
-rw-r--r--progtest/src/reporter/TestReporter.js68
-rw-r--r--progtest/test/TestCaseTest.js99
-rw-r--r--progtest/test/TestRunnerTest.js148
-rw-r--r--progtest/test/reader/ConstResolverTest.js104
-rw-r--r--progtest/test/reader/DateResolverTest.js85
-rw-r--r--progtest/test/reader/YamlTestReaderTest.js55
-rw-r--r--progtest/test/reporter/ConsoleTestReporterTest.js177
-rw-r--r--src/current/compiler/fragments.xsl6
-rw-r--r--src/current/compiler/js.xsl78
-rw-r--r--src/current/include/entry-form.xsl1
-rw-r--r--src/current/scripts/entry-form.js92
l---------src/current/scripts/tame-progtest.js1
-rw-r--r--src/current/summary.css8
30 files changed, 1980 insertions, 27 deletions
diff --git a/.gitignore b/.gitignore
index 5fab262..db0b056 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,8 +7,8 @@
*.info
# autotools- and configure-generated
-Makefile.in
-Makefile
+/Makefile.in
+/Makefile
/aclocal.m4
/*.cache/
/configure
diff --git a/doc/.gitignore b/doc/.gitignore
index 3f9b696..14d345e 100644
--- a/doc/.gitignore
+++ b/doc/.gitignore
@@ -17,3 +17,6 @@ version.texi
*.txt
*.html
+/Makefile.in
+/Makefile
+
diff --git a/progtest/.gitignore b/progtest/.gitignore
new file mode 100644
index 0000000..b387800
--- /dev/null
+++ b/progtest/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+
+# output
+/tame-progtest.js
+index.js
+
diff --git a/progtest/Makefile b/progtest/Makefile
new file mode 100644
index 0000000..2b01a10
--- /dev/null
+++ b/progtest/Makefile
@@ -0,0 +1,40 @@
+# tame-progtest Makefile
+#
+# Copyright (C) 2018 R-T Specialty, LLC.
+#
+# This file is part of TAME.
+#
+# TAME is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+.PHONY: check test modindex browserify FORCE
+
+namespaces=$(shell find src/ -type d)
+nsindex=$(addsuffix /index.js, $(namespaces))
+
+test: check
+check:
+ PATH="$(PATH):$(CURDIR)/node_modules/mocha/bin" \
+ mocha --harmony_destructuring --recursive test/
+
+modindex: $(nsindex)
+%/index.js: FORCE
+ $(CURDIR)/build-aux/gen-index "$*" > "$@"
+
+browserify: tame-progtest.js
+tame-progtest.js: FORCE
+ $(CURDIR)/node_modules/.bin/browserify \
+ --debug \
+ -r $(CURDIR)/src/index.js:progtest \
+ $(CURDIR)/src/index.js \
+ -o "$@"
diff --git a/progtest/README.md b/progtest/README.md
new file mode 100644
index 0000000..a18ba1b
--- /dev/null
+++ b/progtest/README.md
@@ -0,0 +1,5 @@
+# Program Testing
+A /program/ is a top-level package (either marked as with `@program="true"`,
+or with a root `rater` node). This system provides a means of writing and
+running test cases.
+
diff --git a/progtest/bin/runner.js b/progtest/bin/runner.js
new file mode 100644
index 0000000..8895992
--- /dev/null
+++ b/progtest/bin/runner.js
@@ -0,0 +1,36 @@
+/**
+ * Test case runner script
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const program = require( process.argv[ 2 ] );
+const filename = process.argv[ 3 ];
+
+const fs = require( 'fs' );
+
+const case_yaml = fs.readFileSync( filename, 'utf8' );
+
+const runner = require( '../src/env' ).console(
+ program, process.stdout
+);
+
+runner( case_yaml )
+ .catch( e => console.error( e ) );
diff --git a/progtest/build-aux/gen-index b/progtest/build-aux/gen-index
new file mode 100755
index 0000000..5cdb350
--- /dev/null
+++ b/progtest/build-aux/gen-index
@@ -0,0 +1,58 @@
+#!/bin/bash
+# Generates index.js from sources in destination directory
+#
+# Copyright (C) 2014 R-T Specialty, LLC.
+#
+# This file is part of liza.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+##
+
+shopt -s extglob nullglob
+
+destpath="${1?Destination path required}"
+
+cat <<EOH
+/*** GENERATED BY gen-index; DO NOT MODIFY ***/
+
+module.exports = {
+EOH
+
+declare -i i
+
+# generate require for each module
+for module in "$destpath"/!(index).js; do
+ modname="$( basename "$module" .js )"
+
+ # humor ECMAScript 3 for now
+ if ((i++)); then
+ echo ,
+ fi
+
+ echo -n " get '$modname'() { return require( './$modname' ); }"
+done
+
+# include index.js for any sub-directories (namespace)
+for dir in $( find "$destpath" -maxdepth 1 -mindepth 1 -type d ); do
+ ns=$( basename "$dir" )
+
+ # humor ECMAScript 3 for now
+ if ((i++)); then
+ echo ,
+ fi
+
+ echo -n " get '$ns'() { return require('./$ns'); }"
+done
+
+echo -e "\n};"
diff --git a/progtest/package.json b/progtest/package.json
new file mode 100644
index 0000000..97c70f1
--- /dev/null
+++ b/progtest/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "tame-progtest",
+ "description": "TAME Program testing",
+ "version": "0.0.0",
+ "author": "R-T Specialty, LLC",
+
+ "dependencies": {
+ "easejs": "0.2.9",
+ "js-yaml": "3.10.0"
+ },
+ "devDependencies": {
+ "browserify": "16.10.0"
+ },
+
+ "license": "GPL-3.0+"
+}
diff --git a/progtest/src/TestCase.js b/progtest/src/TestCase.js
new file mode 100644
index 0000000..5ea39a3
--- /dev/null
+++ b/progtest/src/TestCase.js
@@ -0,0 +1,103 @@
+/**
+ * Test case
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { Class } = require( 'easejs' );
+
+
+module.exports = Class( 'TestCase',
+{
+ /**
+ * Test case data
+ *
+ * @type {Object} test case data
+ */
+ 'private _caseData': {},
+
+ get description()
+ {
+ return this._caseData.description || "";
+ },
+
+ get data()
+ {
+ return this._caseData.data || {};
+ },
+
+ get expect()
+ {
+ return this._caseData.expect || {};
+ },
+
+
+ constructor( case_data )
+ {
+ this._caseData = case_data;
+ },
+
+
+ 'public mapEachValue'( callback )
+ {
+ const [ new_data, new_expect ] = [ this.data, this.expect ].map( src =>
+ {
+ const new_src = {};
+
+ Object.keys( src ).forEach(
+ key => new_src[ key ] = this._visitDeep(
+ src[ key ],
+ callback
+ )
+ );
+
+ return new_src;
+ } );
+
+ return module.exports( {
+ description: this.description,
+ data: new_data,
+ expect: new_expect,
+ } );
+ },
+
+
+ /**
+ * Recursively resolve constants
+ *
+ * Only scalars and arrays are supported
+ *
+ * @param {number|Array} input scalar or array of inputs
+ * @param {Object} consts constant mapping
+ *
+ * @return {number|Array} resolved value(s)
+ */
+ 'private _visitDeep'( val, callback )
+ {
+ if ( Array.isArray( val ) )
+ {
+ return val.map(
+ x => this._visitDeep( x, callback )
+ );
+ }
+
+ return callback( val );
+ }
+} );
diff --git a/progtest/src/TestRunner.js b/progtest/src/TestRunner.js
new file mode 100644
index 0000000..504c1a1
--- /dev/null
+++ b/progtest/src/TestRunner.js
@@ -0,0 +1,189 @@
+/**
+ * Test case runner
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { Class } = require( 'easejs' );
+
+
+/**
+ * Run test cases and report results
+ */
+module.exports = Class( 'TestRunner',
+{
+ /**
+ * SUT
+ *
+ * @type {Program}
+ */
+ 'private _program': null,
+
+ /**
+ * Test reporter
+ *
+ * @type {TestReporter}
+ */
+ 'private _reporter': null,
+
+
+ /**
+ * Initialize runner for program PROGRAM
+ *
+ * @param {TestReporter} reporter test reporter
+ * @param {Program} program SUT
+ */
+ constructor( reporter, program )
+ {
+ // primitive check to guess whether this might be a program
+ if ( typeof program.rater !== 'function' )
+ {
+ throw TypeError( "program#rater is not a function" );
+ }
+
+ this._reporter = reporter;
+ this._program = program;
+ },
+
+
+ /**
+ * Run set of test cases
+ *
+ * @param {Array<TestCase>} dfns array of TestCases
+ *
+ * @return {Promise} promise to complete test cases, yielding results
+ */
+ 'public runTests'( dfns )
+ {
+ const total = dfns.length;
+
+ this._reporter.preRun( total );
+
+ return this._runAsync( dfns ).then(
+ results => this._reporter.done( results )
+ );
+ },
+
+
+ /**
+ * Run all tests asynchronously
+ *
+ * TODO: This significantly slows down the runner! The better option
+ * would be to go back to sync and put it in a Web Worker in the client,
+ * which would also async updating of the UI.
+ *
+ * @param {Array<TestCase>} dfns test case definitions
+ *
+ * @return {Promise} promise to complete test cases, yielding results
+ */
+ 'private _runAsync'( dfns )
+ {
+ const total = dfns.length;
+
+ return new Promise( ( resolve, reject ) =>
+ {
+ const results = [];
+
+ const runNext = () =>
+ {
+ if ( dfns.length === 0 )
+ {
+ resolve( results );
+ return;
+ }
+
+ const dfn = dfns.shift();
+ const result = this._runTest( dfn, results.length, total );
+
+ results.push( result );
+
+ setTimeout( runNext, 0 );
+ };
+
+ runNext();
+ } );
+ },
+
+
+ /**
+ * Run individual test case
+ *
+ * @param {Object<TestCase>} _ source test case
+ * @param {number} test_i test index
+ * @param {number} total total number of tests
+ *
+ * @return {Object<desc,i,total,failures>} test results
+ */
+ 'private _runTest'( { description: desc, data, expect }, test_i, total )
+ {
+ // no input map---#rate uses params directly
+ const result = this._program.rater( data ).vars;
+
+ const cmp = Object.keys( expect ).map(
+ field => [
+ field,
+ this._deepCompare( expect[ field ], result[ field ] )
+ ]
+ );
+
+ const failures = cmp.filter( ( [ , ok ] ) => !ok )
+ .map( ( [ field ] ) => ( {
+ field: field,
+ expect: expect[ field ],
+ result: result[ field ],
+ } ) );
+
+ const succeeded = cmp.length - failures.length;
+
+ const result_data = {
+ desc: desc,
+ i: test_i,
+ total: cmp.length,
+ failures: failures,
+ };
+
+ this._reporter.testCaseResult( result_data, total );
+
+ return result_data;
+ },
+
+
+ /**
+ * Recursively compare values (scalar, array)
+ *
+ * @param {number|Array<number>} x first value
+ * @param {number|Array<number>} y second value
+ *
+ * @return {boolean} whether X deeply equals Y
+ */
+ 'private _deepCompare'( x, y )
+ {
+ // vector/matrix/etc
+ if ( Array.isArray( x ) )
+ {
+ return Array.isArray( y )
+ && ( x.length === y.length )
+ && x.every( ( xval, i ) => xval === y[ i ] );
+ }
+
+ // scalar
+ return x === y;
+ },
+} );
diff --git a/progtest/src/env.js b/progtest/src/env.js
new file mode 100644
index 0000000..7e85854
--- /dev/null
+++ b/progtest/src/env.js
@@ -0,0 +1,73 @@
+/**
+ * Environment-specific runner initialization
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const yaml_reader = require( 'js-yaml' );
+
+const {
+ TestCase,
+ TestRunner,
+
+ reader: {
+ ConstResolver,
+ DateResolver,
+ YamlTestReader
+ },
+
+ reporter: {
+ ConsoleTestReporter
+ },
+} = require( '../src' );
+
+
+module.exports = {
+ console: ( program, stdout ) =>
+ {
+ const runner = TestRunner(
+ ConsoleTestReporter( stdout ),
+ program
+ );
+
+ const reader = YamlTestReader
+ .use( DateResolver )
+ .use( ConstResolver( program ) )
+ ( yaml_reader, TestCase );
+
+ // XXX: work around issue with consts not being initialized ahead of
+ // time (initialized during actual rating...!)
+ program.rater( {} );
+
+ return yaml => new Promise( ( resolve, reject ) =>
+ {
+ try
+ {
+ const cases = reader.loadCases( yaml );
+
+ resolve( runner.runTests( cases ) );
+ }
+ catch ( e )
+ {
+ reject( e );
+ }
+ } );
+ },
+};
diff --git a/progtest/src/reader/ConstResolver.js b/progtest/src/reader/ConstResolver.js
new file mode 100644
index 0000000..b8861f6
--- /dev/null
+++ b/progtest/src/reader/ConstResolver.js
@@ -0,0 +1,110 @@
+/**
+ * Constant resolver for test case reader
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+
+const { Trait } = require( 'easejs' );
+const TestReader = require( './TestReader' );
+
+
+/**
+ * Resolve program constants by replacing them with their numeric value
+ *
+ * The result is a loaded YAML file that contains only numeric input. All
+ * non-numeric input is interpreted as a constant.
+ *
+ * Any non-numeric value is considered to be a constant, so it is important
+ * to perform all other data transformations before applying constant
+ * resolution.
+ */
+module.exports = Trait( 'ConstResolver' )
+ .implement( TestReader )
+ .extend(
+{
+ /**
+ * Program from which to load constants
+ *
+ * @type {Program}
+ */
+ 'private _program': null,
+
+
+ /**
+ * Initialize with program from which to load constants
+ *
+ * @param {Program} program source program
+ */
+ __mixin( program )
+ {
+ this._program = program;
+ },
+
+
+ /**
+ * Load test cases and resolve constants
+ *
+ * @param {*} src data source
+ *
+ * @return {Array<TestCase>} array of test cases
+ */
+ 'abstract override public loadCases'( yaml )
+ {
+ const data = this.__super( yaml );
+ const { consts } = this._program.rater;
+
+ return data.map(
+ testcase => testcase.mapEachValue(
+ value => this._resolve( value, consts )
+ )
+ );
+ },
+
+
+ /**
+ * Resolve constant
+ *
+ * If the constant is not known (via CONSTS), an error is thrown.
+ *
+ * @param {number|Array} input scalar or array of inputs
+ * @param {Object} consts constant mapping
+ *
+ * @throws {Error} if constant is unknown in CONSTS
+ *
+ * @return {number|Array} resolved value(s)
+ */
+ 'private _resolve'( input, consts )
+ {
+ // already a number, return as-is
+ if ( !isNaN( +input ) )
+ {
+ return input;
+ }
+
+ const result = consts[ input ];
+ if ( result === undefined )
+ {
+ throw Error( `unknown constant: ${input}` );
+ }
+
+ return result;
+ }
+} );
diff --git a/progtest/src/reader/DateResolver.js b/progtest/src/reader/DateResolver.js
new file mode 100644
index 0000000..319254e
--- /dev/null
+++ b/progtest/src/reader/DateResolver.js
@@ -0,0 +1,61 @@
+/**
+ * Date resolver for test case reader
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+
+const { Trait } = require( 'easejs' );
+const TestReader = require( './TestReader' );
+
+
+/**
+ * Resolve dates of the format MM/DD/YYYY to Unix timestamps
+ *
+ * This allows easily readable dates to be included in test cases without
+ * having to worry about Unix timestamps. For higher precision, Unix
+ * timestamps must be used.
+ */
+module.exports = Trait( 'DateResolver' )
+ .implement( TestReader )
+ .extend(
+{
+ /**
+ * Load test cases and resolve dates
+ *
+ * Dates will be replaced with Unix timestamps.
+ *
+ * @param {*} src data source
+ *
+ * @return {Array<TestCase>} array of test cases
+ */
+ 'abstract override public loadCases'( src )
+ {
+ const data = this.__super( src );
+
+ return data.map(
+ testcase => testcase.mapEachValue(
+ value => ( /^\d{2}\/\d{2}\/\d{4}$/.test( value ) )
+ ? ( ( new Date( value ) ).getTime() / 1000 )
+ : value
+ )
+ );
+ }
+} );
diff --git a/progtest/src/reader/TestReader.js b/progtest/src/reader/TestReader.js
new file mode 100644
index 0000000..717b12c
--- /dev/null
+++ b/progtest/src/reader/TestReader.js
@@ -0,0 +1,40 @@
+/**
+ * Test case reader
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { Interface } = require( 'easejs' );
+
+
+module.exports = Interface( 'TestReader',
+{
+ /**
+ * Load test cases from an implementation-defined data source SRC
+ *
+ * The produced object will be an array of cases, each containing a
+ * `description`, `data`, and `expect`.
+ *
+ * @param {*} src data source
+ *
+ * @return {Array<TestCase>} array of test cases
+ */
+ 'public loadCases': [ 'src' ],
+} );
diff --git a/progtest/src/reader/YamlTestReader.js b/progtest/src/reader/YamlTestReader.js
new file mode 100644
index 0000000..a28c1f3
--- /dev/null
+++ b/progtest/src/reader/YamlTestReader.js
@@ -0,0 +1,79 @@
+/**
+ * YAML test case reader
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { Class } = require( 'easejs' );
+const TestReader = require( './TestReader' );
+
+
+module.exports = Class( 'YamlTestReader' )
+ .implement( TestReader )
+ .extend(
+{
+ /**
+ * YAML parser
+ *
+ * @type {Object}
+ */
+ 'private _yamlParser': null,
+
+ /**
+ * TestCase constructor
+ *
+ * @type {function(Object)}
+ */
+ 'private _createTestCase': null,
+
+
+ /**
+ * Initialize with YAML parser
+ *
+ * The parser must conform to the API of `js-yaml`.
+ *
+ * @param {Object} yaml_parser YAML parser
+ * @param {function(Object)} test_case_ctor TestCase constructor
+ */
+ constructor( yaml_parser, test_case_ctor )
+ {
+ this._yamlParser = yaml_parser;
+ this._createTestCase = test_case_ctor;
+ },
+
+
+ /**
+ * Load test cases from a YAML string
+ *
+ * The produced object will be an array of cases, each containing a
+ * `description`, `data`, and `expect`.
+ *
+ * @param {string} src source YAML
+ *
+ * @return {Array<TestCase>} array of test cases
+ */
+ 'virtual public loadCases'( yaml )
+ {
+ const data = this._yamlParser.safeLoad( yaml )
+ .map( this._createTestCase );
+
+ return data;
+ },
+} );
diff --git a/progtest/src/reporter/ConsoleTestReporter.js b/progtest/src/reporter/ConsoleTestReporter.js
new file mode 100644
index 0000000..2a16e03
--- /dev/null
+++ b/progtest/src/reporter/ConsoleTestReporter.js
@@ -0,0 +1,183 @@
+/**
+ * Console test reporter
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { Class } = require( 'easejs' );
+
+
+/**
+ * Real-time reporting of test cases to the console
+ *
+ * Test cases will be output in a block of dots (success) or 'F's (failure),
+ * in a style similar to PHPUnit. If failures occur, they will be output to
+ * in more detail after all tests have run.
+ */
+module.exports = Class( 'ConsoleTestReporter',
+{
+ /**
+ * Standard out
+ *
+ * @type {Object} standard out
+ */
+ 'private _stdout': null,
+
+
+ /**
+ * Initialize reporter with target console
+ *
+ * STDOUT must follow Node.js' API.
+ *
+ * @param {Object} stdout standard out
+ */
+ constructor( stdout )
+ {
+ this._stdout = stdout;
+ },
+
+
+ /**
+ * Invoked before tests are run
+ *
+ * The only information provided here is the number of test cases to be
+ * run, which can be used to produce a progress indicator.
+ *
+ * @param {number} total number of test cases
+ *
+ * @return {undefined}
+ */
+ 'public preRun'( total )
+ {
+ // this reporter does nothing with this method
+ },
+
+
+ /**
+ * Invoked for each test case immediately after it has been run
+ *
+ * For the format of RESULT, see TestRunner.
+ *
+ * @param {Object} result test case result
+ *
+ * @return {undefined}
+ */
+ 'public testCaseResult'( result, total )
+ {
+ const { i, failures } = result;
+
+ const ind = ( failures.length === 0 )
+ ? '.'
+ : 'F';
+
+ const sep = ( i % 50 === 49 )
+ ? ` ${i+1}/${total}\n`
+ : '';
+
+ this._stdout.write( ind + sep );
+ },
+
+
+ /**
+ * Invoked after all test cases have been run
+ *
+ * RESULTS is an array containing each result that was previously
+ * reported to `#testCaseResult`.
+ *
+ * A final line will be output, preceded by an empty line, summarizing
+ * the number of tests, assertions, and failures for each.
+ *
+ * @param {Array<Object>} results all test results
+ *
+ * @return {undefined}
+ */
+ 'public done'( results )
+ {
+ this._outputFailureReport( results );
+ this._outputSummary( results );
+ },
+
+
+ /**
+ * For each failure, output each expected and resulting value
+ *
+ * Failures are prefixed with a 1-indexed number.
+ *
+ * @param {Object} result test case result}
+ *
+ * @return {undefined}
+ */
+ 'private _outputFailureReport'( results )
+ {
+ const report = results
+ .filter( ( { failures } ) => failures.length > 0 )
+ .map( this._reportTestFailure.bind( this ) )
+ .join( '\n' )
+
+ this._stdout.write( "\n\n" + report );
+ },
+
+
+ /**
+ * Generate report for test case failure
+ *
+ * @param {Object<i,desc,failures>} _ test case result data
+ *
+ * @return {string} report
+ */
+ 'private _reportTestFailure'( { i, desc, failures } )
+ {
+ return `[#${i+1}] ${desc}\n` +
+ failures.map( ( { field, expect, result } ) =>
+ ` ${field}:\n` +
+ ` expected: ` + JSON.stringify( expect ) + `\n` +
+ ` result: ` + JSON.stringify( result ) + `\n`
+ ).join( '' );
+ },
+
+
+ /**
+ * Output a line, preceded by an empty line, summarizing the number of
+ * tests, assertions, and failures for each
+ *
+ * @param {Array<Object>} results all test results
+ *
+ * @return {undefined}
+ */
+ 'private _outputSummary'( results )
+ {
+ const [ failed, afailed, acount ] = results.reduce(
+ ( [ failed, afailed, acount ], { failures, total } ) =>
+ [
+ ( failed + +( failures.length > 0 ) ),
+ ( afailed + failures.length ),
+ ( acount + total )
+ ],
+ [ 0, 0, 0 ]
+ );
+
+ const test_total = results.length;
+
+ this._stdout.write(
+ `\n${test_total} tests, ${failed} failed (` +
+ `${acount} assertions, ${afailed} failures)`
+ );
+ },
+} );
diff --git a/progtest/src/reporter/NullTestReporter.js b/progtest/src/reporter/NullTestReporter.js
new file mode 100644
index 0000000..d29fb05
--- /dev/null
+++ b/progtest/src/reporter/NullTestReporter.js
@@ -0,0 +1,79 @@
+/**
+ * Reporter that does nothing
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { Class } = require( 'easejs' );
+
+
+/**
+ * Reporter that does nothing
+ *
+ * This is useful if you want a fully background process.
+ */
+module.exports = Class( 'NullTestReporter',
+{
+ /**
+ * Invoked before tests are run
+ *
+ * The only information provided here is the number of test cases to be
+ * run, which can be used to produce a progress indicator.
+ *
+ * @param {number} total number of test cases
+ *
+ * @return {undefined}
+ */
+ 'public preRun'( total )
+ {
+ // this reporter does nothing with this method
+ },
+
+
+ /**
+ * Invoked for each test case immediately after it has been run
+ *
+ * For the format of RESULT, see TestRunner.
+ *
+ * @param {Object} result test case result
+ *
+ * @return {undefined}
+ */
+ 'public testCaseResult'( result, total )
+ {
+ // this reporter does nothing with this method
+ },
+
+
+ /**
+ * Invoked after all test cases have been run
+ *
+ * RESULTS is an array containing each result that was previously
+ * reported to `#testCaseResult`.
+ *
+ * @param {Array<Object>} results all test results
+ *
+ * @return {undefined}
+ */
+ 'public done'( results )
+ {
+ // this reporter does nothing with this method
+ },
+} );
diff --git a/progtest/src/reporter/TestReporter.js b/progtest/src/reporter/TestReporter.js
new file mode 100644
index 0000000..ee7ce8a
--- /dev/null
+++ b/progtest/src/reporter/TestReporter.js
@@ -0,0 +1,68 @@
+/**
+ * Test reporter
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { Interface } = require( 'easejs' );
+
+
+/**
+ * Real-time reporting of test cases
+ */
+module.exports = Interface( 'TestReporter',
+{
+ /**
+ * Invoked before tests are run
+ *
+ * The only information provided here is the number of test cases to be
+ * run, which can be used to produce a progress indicator.
+ *
+ * @param {number} total number of test cases
+ *
+ * @return {undefined}
+ */
+ 'public preRun': [ 'total' ],
+
+
+ /**
+ * Invoked for each test case immediately after it has been run
+ *
+ * For the format of RESULT, see TestRunner.
+ *
+ * @param {Object} result test case result
+ *
+ * @return {undefined}
+ */
+ 'public testCaseResult': [ 'result' ],
+
+
+ /**
+ * Invoked after all test cases have been run
+ *
+ * RESULTS is an array containing each result that was previously
+ * reported to `#testCaseResult`.
+ *
+ * @param {Array<Object>} results all test results
+ *
+ * @return {undefined}
+ */
+ 'public done': [ 'results' ],
+} );
diff --git a/progtest/test/TestCaseTest.js b/progtest/test/TestCaseTest.js
new file mode 100644
index 0000000..95b46d6
--- /dev/null
+++ b/progtest/test/TestCaseTest.js
@@ -0,0 +1,99 @@
+/**
+ * Tests TestCase
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { expect } = require( 'chai' );
+const Sut = require( '../src/TestCase' );
+
+
+describe( "TestCase", () =>
+{
+ it( "allows retrieving raw data", () =>
+ {
+ const data = {
+ description: "Foo bar",
+ data: { foo: [ 5 ] },
+ expect: { bar: [ 1 ] },
+ };
+
+ const sut = Sut( data );
+
+ expect( sut.description ).to.equal( data.description );
+ expect( sut.data ).to.deep.equal( data.data );
+ expect( sut.expect ).to.deep.equal( data.expect );
+ } );
+
+
+ it( "provides sane defaults for missing data", () =>
+ {
+ const sut = Sut( {} );
+
+ expect( sut.description ).to.equal( "" );
+ expect( sut.data ).to.deep.equal( {} );
+ expect( sut.expect ).to.deep.equal( {} );
+ } );
+
+
+ describe( "#mapEachValue", () =>
+ {
+ it( "visits each 'data' and 'expect' value", () =>
+ {
+ // tests scalar, vector, matrix; mixed with non-constants
+ const testcase = {
+ description: 'test desc',
+
+ data: {
+ foo: 'bar',
+ bar: [ 'baz', 'quux' ],
+ baz: [ [ 'quuux', 'foox' ], [ 'moo', 'cow' ] ],
+ },
+ expect: {
+ quux: 'out',
+ quuux: [ 'of', 'names' ],
+ },
+ };
+
+ const expected = {
+ data: {
+ foo: 'OKbar',
+ bar: [ 'OKbaz', 'OKquux' ],
+ baz: [ [ 'OKquuux', 'OKfoox' ], [ 'OKmoo', 'OKcow' ] ],
+ },
+ expect: {
+ quux: 'OKout',
+ quuux: [ 'OKof', 'OKnames' ],
+ },
+ };
+
+ const result = Sut( testcase ).mapEachValue( val => `OK${val}` );
+
+ // derived from the original
+ expect( result.description ).to.equal( testcase.description );
+ expect( result.data ).to.deep.equal( expected.data );
+ expect( result.expect ).to.deep.equal( expected.expect );
+
+ // but not the original (should return a new object)
+ expect( result.data ).to.not.equal( testcase.data );
+ expect( result.expect ).to.not.equal( testcase.expect );
+ } );
+ } );
+} );
diff --git a/progtest/test/TestRunnerTest.js b/progtest/test/TestRunnerTest.js
new file mode 100644
index 0000000..eff50eb
--- /dev/null
+++ b/progtest/test/TestRunnerTest.js
@@ -0,0 +1,148 @@
+/**
+ * Tests TestReader
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { expect } = require( 'chai' );
+const { Class } = require( 'easejs' );
+const Sut = require( '../src/TestRunner' );
+const TestReporter = require( '../src/reporter/TestReporter' );
+const NullTestReporter = require( '../src/reporter/NullTestReporter' );
+
+
+describe( "TestRunner", () =>
+{
+ it( "runs each test against given program", () =>
+ {
+ const given = [];
+
+ const program = {
+ rater( data )
+ {
+ return rate_results[ given.push( data ) - 1 ];
+ }
+ };
+
+ const test_cases = [
+ {
+ description: "first",
+ data: { a: 1 },
+ expect: { foo: 1 },
+ },
+ {
+ description: "second",
+ data: { a: 2 },
+ expect: {
+ foo: [ 1, 2 ],
+ bar: [ 3, 1 ],
+ baz: [ 4, 2 ],
+ },
+ },
+ ];
+
+ const rate_results = [
+ // no failures
+ { vars: { foo: 1 } },
+
+ // bar, baz failures
+ { vars: {
+ foo: [ 1, 2 ],
+ bar: [ 3, 4 ],
+ baz: [ 4, 5 ],
+ } },
+ ];
+
+ const expect_failures = [
+ [],
+ [
+ {
+ field: 'bar',
+ expect: test_cases[ 1 ].expect.bar,
+ result: rate_results[ 1 ].vars.bar,
+ },
+ {
+ field: 'baz',
+ expect: test_cases[ 1 ].expect.baz,
+ result: rate_results[ 1 ].vars.baz,
+ },
+ ]
+ ];
+
+ Sut( NullTestReporter(), program )
+ .runTests( test_cases )
+ .then( results =>
+ {
+ test_cases.forEach( ( test_case, i ) =>
+ {
+ const result = results[ i ];
+
+ expect( result.desc ).to.equal( test_case.description );
+ expect( result.i ).to.equal( i );
+ expect( result.total ).to.equal(
+ Object.keys( test_case.expect ).length
+ );
+ expect( result.failures ).to.deep.equal(
+ expect_failures[ i ]
+ );
+ } );
+ } );
+ } );
+
+
+ it( "invokes reporter before, during, and after test cases", done =>
+ {
+ let pre = false;
+ let results = [];
+
+ const program = { rater: () => ( { vars: {} } ) };
+
+ const mock_reporter = Class.implement( TestReporter ).extend(
+ {
+ preRun( total )
+ {
+ expect( total ).to.equal( 2 );
+ pre = true;
+ },
+
+ testCaseResult( result, total )
+ {
+ expect( pre ).to.equal( true );
+ expect( total ).to.equal( 2 );
+
+ results.push( result );
+ },
+
+ done( given_results )
+ {
+ expect( pre ).to.equal( true );
+ expect( results ).to.deep.equal( given_results );
+
+ done();
+ },
+ } )();
+
+ // see done() above
+ Sut( mock_reporter, program ).runTests( [
+ { description: '', data: {}, expect: {} },
+ { description: '', data: {}, expect: {} },
+ ] );
+ } );
+} );
diff --git a/progtest/test/reader/ConstResolverTest.js b/progtest/test/reader/ConstResolverTest.js
new file mode 100644
index 0000000..2060f2a
--- /dev/null
+++ b/progtest/test/reader/ConstResolverTest.js
@@ -0,0 +1,104 @@
+/**
+ * Tests ConstResolver
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { expect } = require( 'chai' );
+const { Class } = require( 'easejs' );
+const TestCase = require( '../../src/TestCase' );
+const TestReader = require( '../../src/reader/TestReader' );
+const Sut = require( '../../src/reader/ConstResolver' );
+
+const StubTestReader = Class.implement( TestReader ).extend(
+{
+ constructor( parsed_data )
+ {
+ this.parsedData = parsed_data;
+ },
+
+ 'virtual public loadCases'( _ )
+ {
+ return this.parsedData;
+ }
+} );
+
+
+describe( "ConstResolver", () =>
+{
+ [ 'data', 'expect' ].forEach( field =>
+ {
+ it( `replaces known ${field} constants from program`, () =>
+ {
+ const program = {
+ rater: {
+ consts: { FOO: 1, BAR: 2 },
+ },
+ };
+
+ // tests scalar, vector, matrix; mixed with non-constants
+ const parsed_data = [
+ TestCase( { [field]: { foo: 'FOO', bar: 4 } } ),
+ TestCase(
+ { [field]: {
+ foo: [ 'FOO', 'BAR', 5 ],
+ bar: [ [ 'FOO', 3 ], [ 'FOO', 'BAR' ] ],
+ } }
+ ),
+ ];
+
+ const { FOO, BAR } = program.rater.consts;
+
+ const expected = [
+ TestCase( { [field]: { foo: FOO, bar: 4 } } ),
+ TestCase(
+ { [field]: {
+ foo: [ FOO, BAR, 5 ],
+ bar: [ [ FOO, 3 ], [ FOO, BAR ] ],
+ } }
+ ),
+ ];
+
+ // anything just to proxy
+ const given_yaml = 'fooml';
+
+ const result = StubTestReader
+ .use( Sut( program ) )( parsed_data )
+ .loadCases( given_yaml );
+
+ result.forEach(
+ ( tcase, i ) => expect( tcase[ field ] )
+ .to.deep.equal( expected[ i ][ field ] )
+ );
+ } );
+
+
+ it( `throws error on unknown $field constant`, () =>
+ {
+ const program = { rater: { consts: {} } };
+ const parsed_data = [ TestCase( { [field]: { foo: 'UNKNOWN' } } ) ];
+
+ expect(
+ () => StubTestReader.use( Sut( program ) )( parsed_data )
+ .loadCases( '' )
+ ).to.throw( Error, 'UNKNOWN' );
+ } );
+ } );
+} );
diff --git a/progtest/test/reader/DateResolverTest.js b/progtest/test/reader/DateResolverTest.js
new file mode 100644
index 0000000..4b2225d
--- /dev/null
+++ b/progtest/test/reader/DateResolverTest.js
@@ -0,0 +1,85 @@
+/**
+ * Tests DateResolver
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { expect } = require( 'chai' );
+const { Class } = require( 'easejs' );
+const TestCase = require( '../../src/TestCase' );
+const TestReader = require( '../../src/reader/TestReader' );
+const Sut = require( '../../src/reader/DateResolver' );
+
+const MockTestReader = Class.implement( TestReader ).extend(
+{
+ constructor( parsed_data, expected_load )
+ {
+ this.parsedData = parsed_data;
+ this.expectedLoad = expected_load;
+ },
+
+ 'virtual public loadCases'( given )
+ {
+ expect( given ).to.equal( this.expectedLoad );
+ return this.parsedData;
+ }
+} );
+
+
+describe( "DateResolver", () =>
+{
+ [ 'data', 'expect' ].forEach( field =>
+ {
+ it( `converts ${field} dates into Unix timestamps`, () =>
+ {
+ const date = '10/25/1989';
+ const time = ( new Date( date ) ).getTime() / 1000;
+
+ // tests scalar, vector, matrix; mixed with non-constants
+ const parsed_data = [
+ TestCase(
+ { [field]: {
+ foo: [ 5, 'NOTADATE', date ],
+ } }
+ ),
+ ];
+
+ const expected = [
+ TestCase(
+ { [field]: {
+ foo: [ 5, 'NOTADATE', time ],
+ } }
+ ),
+ ];
+
+ // anything just to proxy
+ const given_yaml = 'fooml';
+
+ const result = MockTestReader
+ .use( Sut )( parsed_data, given_yaml )
+ .loadCases( given_yaml );
+
+ result.forEach(
+ ( tcase, i ) => expect( tcase[ field ] )
+ .to.deep.equal( expected[ i ][ field ] )
+ );
+ } );
+ } );
+} );
diff --git a/progtest/test/reader/YamlTestReaderTest.js b/progtest/test/reader/YamlTestReaderTest.js
new file mode 100644
index 0000000..9fb26fd
--- /dev/null
+++ b/progtest/test/reader/YamlTestReaderTest.js
@@ -0,0 +1,55 @@
+/**
+ * Tests TestReader
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { expect } = require( 'chai' );
+const Sut = require( '../../src/reader/YamlTestReader' );
+
+
+describe( "YamlTestReader", () =>
+{
+ it( "parses given yaml", () =>
+ {
+ const yaml = "foo: bar";
+
+ const parsed = [
+ {
+ description: "first desc",
+ data: { "foo": "bar" },
+ expect: { "bar": "baz" },
+ },
+ ];
+
+ const case_ctor = ( data ) => ( { ok: data } );
+
+ const mock_parser = {
+ safeLoad( given )
+ {
+ expect( given ).to.equal( yaml );
+ return parsed;
+ }
+ };
+
+ expect( Sut( mock_parser, case_ctor ).loadCases( yaml ) )
+ .to.deep.equal( [ { ok: parsed[0] } ] );
+ } );
+} );
diff --git a/progtest/test/reporter/ConsoleTestReporterTest.js b/progtest/test/reporter/ConsoleTestReporterTest.js
new file mode 100644
index 0000000..07e3e52
--- /dev/null
+++ b/progtest/test/reporter/ConsoleTestReporterTest.js
@@ -0,0 +1,177 @@
+/**
+ * Tests ConsoleTestReporter
+ *
+ * Copyright (C) 2018 R-T Specialty, LLC.
+ *
+ * This file is part of TAME.
+ *
+ * TAME is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+"use strict";
+
+const { expect } = require( 'chai' );
+const Sut = require( '../../src/reporter/ConsoleTestReporter' );
+
+
+describe( "ConsoleTestReporter", () =>
+{
+ describe( "#testCaseResult", () =>
+ {
+ it( "outputs indicator for each test case", () =>
+ {
+ let output = '';
+
+ const stdout = { write: str => output += str };
+ const sut = Sut( stdout );
+
+ [
+ { i: 0, failures: [] },
+ { i: 1, failures: [] },
+ { i: 2, failures: [ {} ] },
+ { i: 3, failures: [ {}, {} ] },
+ { i: 4, failures: [] },
+ ].forEach(
+ result => sut.testCaseResult( result, 5 )
+ );
+
+ expect( output ).to.equal( '..FF.' );
+ } );
+
+
+ it( "outputs line break with count after 40 cases", () =>
+ {
+ let output = '';
+
+ const stdout = { write: str => output += str };
+ const sut = Sut( stdout );
+
+ const results = ( new Array( 130 ) ).join( '.' ).split( '.' )
+ .map( ( _, i ) => ( { i: i, failures: [] } ) );
+
+ results.forEach(
+ result => sut.testCaseResult( result, 130 )
+ );
+
+ expect( output ).to.equal(
+ ( new Array( 51 ) ).join( '.' ) + ' 50/130\n' +
+ ( new Array( 51 ) ).join( '.' ) + ' 100/130\n' +
+ ( new Array( 31 ) ).join( '.' )
+ );
+ } );
+ } );
+
+
+ describe( "done", () =>
+ {
+ it( "outputs report of failures to stdout", () =>
+ {
+ let output = '';
+
+ const stdout = { write: str => output += str };
+
+ const results = [
+ { i: 0, total: 1, desc: "test 0", failures: [] },
+ { i: 1, total: 2, desc: "test 1", failures: [] },
+
+ {
+ i: 2,
+ total: 3,
+ desc: "test 2",
+ failures: [
+ {
+ field: "foo",
+ expect: [ 1 ],
+ result: [ 2, 3 ]
+ },
+ ],
+ },
+ {
+ i: 3,
+ total: 4,
+ desc: "test 3",
+ failures: [
+ {
+ field: "bar",
+ expect: 2,
+ result: 3,
+ },
+ {
+ field: "baz",
+ expect: [ [ 4 ] ],
+ result: [ 5 ],
+ }
+ ],
+ },
+ ];
+
+ const stringified = results.map(
+ result => result.failures.map(
+ failure => ( {
+ expect: JSON.stringify( failure.expect ),
+ result: JSON.stringify( failure.result ),
+ } )
+ )
+ );
+
+ Sut( stdout ).done( results );
+
+ const fail_output = output.match( /\n\n\[#3\](.|\n)*\n\n/ )[0];
+
+ // 1-indexed output
+ expect( fail_output ).to.equal(
+ `\n\n` +
+ `[#3] test 2\n` +
+ ` foo:\n` +
+ ` expected: ` + stringified[ 2 ][ 0 ].expect + `\n` +
+ ` result: ` + stringified[ 2 ][ 0 ].result + `\n` +
+ `\n` +
+ `[#4] test 3\n` +
+ ` bar:\n` +
+ ` expected: ` + stringified[ 3 ][ 0 ].expect + `\n` +
+ ` result: ` + stringified[ 3 ][ 0 ].result + `\n` +
+ ` baz:\n` +
+ ` expected: ` + stringified[ 3 ][ 1 ].expect + `\n` +
+ ` result: ` + stringified[ 3 ][ 1 ].result + `\n\n`
+ );
+ } );
+
+
+ it( "outputs summary on last line of stdout", () =>
+ {
+ let output = '';
+
+ const stdout = { write: str => output += str };
+ const sut = Sut( stdout, {} );
+
+ Sut( stdout ).done( [
+ { i: 0, total: 1, failures: [] },
+ { i: 1, total: 2, failures: [] },
+ { i: 2, total: 3, failures: [ {} ] },
+ { i: 3, total: 4, failures: [ {}, {} ] },
+ { i: 4, total: 5, failures: [] },
+ ] );
+
+ const lines = output.split( '\n' );
+
+ // preceded by empty line
+ expect( lines[ lines.length - 2 ] ).to.equal( "" );
+
+ // last line
+ expect( lines[ lines.length - 1 ] ).to.equal(
+ `5 tests, 2 failed (15 assertions, 3 failures)`
+ );
+ } );
+ } );
+} );
diff --git a/src/current/compiler/fragments.xsl b/src/current/compiler/fragments.xsl
index 23dcae9..8fee167 100644
--- a/src/current/compiler/fragments.xsl
+++ b/src/current/compiler/fragments.xsl
@@ -128,7 +128,11 @@
<xsl:variable name="pkg" as="element( lv:package )"
select="root(.)" />
- <xsl:apply-templates select="$pkg/lv:const[ @name=$name ]" mode="compile" />
+ <xsl:apply-templates mode="compile"
+ select="$pkg/lv:const[ @name=$name ],
+ $pkg/lv:typedef//lv:item[ @name=$name ]">
+ <xsl:with-param name="as-const" select="true()" />
+ </xsl:apply-templates>
</xsl:template>
<xsl:template match="preproc:sym[ @type='tpl' ]" mode="preproc:compile-fragments" priority="5">
diff --git a/src/current/compiler/js.xsl b/src/current/compiler/js.xsl
index 6674140..0d9b6f4 100644
--- a/src/current/compiler/js.xsl
+++ b/src/current/compiler/js.xsl
@@ -65,18 +65,11 @@
<!-- to store debug information for equations (we have to put this out here
so that functions also have access to it...yes, it's stateful, yes it's
bullshit, but oh well) -->
- <text>var debug = {};</text>
<text>var consts = {};</text>
+ <text>var debug = {};</text>
<text>var params = {};</text>
<text>var types = {};</text>
<text>var meta = {};</text>
- <!--
- <value-of select="$compiler:nl" />
- <apply-templates
- select="root(.)//lv:const[ .//lv:item or preproc:iou ]"
- mode="compile" />
- -->
-
</template>
@@ -162,7 +155,6 @@
<text>premium: ( Math.round( args.___yield * 100 ) / 100 ), </text>
<text>classes: classes, </text>
<text>vars: args, </text>
- <text>consts: consts, </text>
<text>reqParams: req_params, </text>
<text>debug: debug </text>
<text>}; </text>
@@ -174,6 +166,7 @@
<text>'; </text>
<text>rater.meta = meta;</text>
+ <text>rater.consts = consts;</text>
<!-- provide classification -> yields mapping -->
<value-of select="$compiler:nl" />
@@ -280,6 +273,23 @@
<value-of select="@default" />
<text>',</text>
+ <text>depth: </text>
+ <!-- TODO: this logic is duplicated multiple places -->
+ <choose>
+ <when test="@set = 'vector'">
+ <sequence select="1" />
+ </when>
+
+ <when test="@set = 'matrix'">
+ <sequence select="2" />
+ </when>
+
+ <otherwise>
+ <sequence select="0" />
+ </otherwise>
+ </choose>
+ <text>,</text>
+
<text>required: </text>
<!-- param is required if the attribute is present, not non-empty -->
<value-of select="string( not( boolean( @default ) ) )" />
@@ -383,6 +393,9 @@
@return property representing a specific value
-->
<template match="lv:enum/lv:item" mode="compile">
+ <param name="as-const" as="xs:boolean"
+ select="false()" />
+
<!-- determine enumerated value -->
<variable name="value">
<choose>
@@ -398,9 +411,10 @@
</variable>
<!-- we are only interest in its value; its constant is an internal value -->
- <text>'</text>
- <value-of select="$value" />
- <text>': true</text>
+ <sequence select="if ( $as-const ) then
+ concat( 'consts[''', @name, '''] = ', $value, ';' )
+ else
+ concat( '''', $value, ''': true' )" />
</template>
@@ -412,7 +426,7 @@
@return JS object assignment for constant set values
-->
-<template mode="compile" priority="1"
+<template mode="compile" priority="2"
match="lv:const[ element() or @values ]">
<text>consts['</text>
<value-of select="@name" />
@@ -449,6 +463,19 @@
<!--
+ Falls back to scalar constants
+-->
+<template mode="compile" priority="1"
+ match="lv:const">
+ <text>consts['</text>
+ <value-of select="@name" />
+ <text>'] = </text>
+ <value-of select="@value" />
+ <text>;</text>
+</template>
+
+
+<!--
Produce sequence of sets
Sets are used to group together items in a matrix. A set can be
@@ -1867,31 +1894,34 @@
{
for ( var param in params )
{
- var val = params[ param ]['default'];
- if ( !val )
- {
- continue;
- }
+ var param_data = params[ param ];
+ var val = param_data['default'] || 0;
+ var depth = param_data.depth || 0;
- args[ param ] = set_defaults( args[ param ], val );
+ args[ param ] = set_defaults( args[ param ], val, +depth );
}
}
- function set_defaults( input, value )
+ function set_defaults( input, value, depth )
{
// scalar
- if ( !( typeof input === 'object' ) )
+ if ( depth === 0 )
{
return ( input === '' || input === undefined ) ? value : input;
}
- // otherwise, assume array
- var i = input.length;
+ input = input || [];
+
+ // vector or matrix
+ var i = input.length || 1;
var ret = [];
+ var value = ( depth === 2 ) ? [ value ] : value;
+
while ( i-- ) {
- ret[i] = ( input[i] === '' ) ? value : input[i];
+ ret[i] = ( input[i] === '' || input[i] === undefined ) ? value : input[i];
}
+
return ret;
}
]]>
diff --git a/src/current/include/entry-form.xsl b/src/current/include/entry-form.xsl
index 8d2cc2c..804c173 100644
--- a/src/current/include/entry-form.xsl
+++ b/src/current/include/entry-form.xsl
@@ -132,6 +132,7 @@
</xsl:for-each-group>
</form>
+ <script type="text/javascript" src="{$fw-path}/rater/scripts/tame-progtest.js"></script>
<script type="text/javascript" src="{$fw-path}/rater/scripts/entry-form.js"></script>
</xsl:template>
diff --git a/src/current/scripts/entry-form.js b/src/current/scripts/entry-form.js
index 616e100..73a55e2 100644
--- a/src/current/scripts/entry-form.js
+++ b/src/current/scripts/entry-form.js
@@ -1501,8 +1501,48 @@ var client = ( function()
loadQuote( qid, qdata_host );
} );
+ const yamlconsole = dom.createElement( 'textarea' );
+ yamlconsole.style.display = 'none';
+ yamlconsole.id = 'yamlconsole';
+
+ const yamlbrowse = dom.createElement( 'input' );
+ yamlbrowse.type = 'file';
+ yamlbrowse.style.display = 'none';
+ yamlbrowse.accept = '.yml, .yaml';
+ yamlbrowse.multiple = 'multiple';
+ yamlbrowse.addEventListener( 'change', e =>
+ {
+ yamlconsole.style.display = '';
+ yamlconsole.textContent = '';
+
+ if ( yamlbrowse.files.length === 0 )
+ {
+ return;
+ }
+
+ runYamlTestCases(
+ Array.prototype.slice.call( yamlbrowse.files, 0 ),
+ createYamlRunner( yamlconsole )
+ );
+
+ yamlbrowse.value = '';
+
+ return false;
+ } );
+
+ const yamlcases = dom.createElement( 'button' );
+ yamlcases.innerHTML = 'Load YAML Test Cases';
+ yamlcases.addEventListener( 'click', e =>
+ {
+ yamlbrowse.click();
+ return false;
+ } );
+
dialog.appendChild( retest );
dialog.appendChild( loadquote );
+ dialog.appendChild( yamlcases );
+ dialog.appendChild( yamlbrowse );
+ dialog.appendChild( yamlconsole );
dialog.appendChild( getPriorTable() );
dom.body.appendChild( dialog );
@@ -1517,6 +1557,58 @@ var client = ( function()
};
+ /**
+ * Create YAML test case runner
+ *
+ * @param {HTMLElement} yamlconsole element to contain runner output
+ *
+ * @return {function(string)} runner
+ */
+ const createYamlRunner = yamlconsole => require( 'progtest' )
+ .env.console(
+ { rater: window.rater },
+ {
+ write( str )
+ {
+ yamlconsole.textContent += str;
+ }
+ }
+ );
+
+
+ /**
+ * Run test cases in each YAML file FILES
+ *
+ * @param {Array<File>} files YAML files
+ * @param {function(string)} runner test case runner
+ *
+ * @return {undefined}
+ */
+ const runYamlTestCases = function( files, runner )
+ {
+ if ( files.length === 0 )
+ {
+ return;
+ }
+
+ const testfile = files.shift();
+ const reader = new FileReader();
+
+ reader.onload = ev =>
+ {
+ const yaml = ev.target.result;
+
+ runner( yaml )
+ .catch( e => alert( e.message ) );
+
+ // run for remaining files
+ runYamlTestCases( files, runner );
+ };
+
+ reader.readAsBinaryString( testfile );
+ };
+
+
var getPriorTable = function()
{
var table = dom.createElement( 'table' ),
diff --git a/src/current/scripts/tame-progtest.js b/src/current/scripts/tame-progtest.js
new file mode 120000
index 0000000..c2cb3ef
--- /dev/null
+++ b/src/current/scripts/tame-progtest.js
@@ -0,0 +1 @@
+../../../progtest/tame-progtest.js \ No newline at end of file
diff --git a/src/current/summary.css b/src/current/summary.css
index 64d3948..6b97113 100644
--- a/src/current/summary.css
+++ b/src/current/summary.css
@@ -1020,3 +1020,11 @@ body:not(.prior) #voi-container td.prior
background-color: #ffc0c0;
border-color: #c00000;
}
+
+#yamlconsole
+{
+ display: block;
+ width: 95%;
+ height: 40ex;
+ margin: 2ex 0px;
+}