Source: org/terraswarm/accessor/accessors/web/hosts/common/test/testCommon.js

// Test code for functions to be shared among accessor hosts.
//
// Copyright (c) 2015-2017 The Regents of the University of California.
// All rights reserved.
//
// Permission is hereby granted, without written agreement and without
// license or royalty fees, to use, copy, modify, and distribute this
// software and its documentation for any purpose, provided that the above
// copyright notice and the following two paragraphs appear in all copies
// of this software.
//
// IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
// FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
// ENHANCEMENTS, OR MODIFICATIONS.
//

// Simple script to test the common host. To run it, in this directory,
// just do:
//     node testCommon.js

// The file system module 'fs', is not available under Duktape, CapeCode or Nashorn.
//if (typeof Duktape !== 'object') {
var hasFsModule = true;

if (typeof Duktape === 'object' || (typeof Packages !== 'undefined' && typeof Packages.java.util.Vector === 'function')) {
    hasFsModule = false;
}

if (hasFsModule) {
    // Require the filesystem module to read the accessor source code.
    var fs = require('fs');
}

// Provide the required getAccessorCode function that every host needs.
// Search for a .js file in a search path.
getAccessorCode = function (name) {
    // We need this so that we can run the test from ant using mocha.
    // See test/mocha/testCommon.js.

    // The path elements should end with a slash.
    var searchPath = ['./', '../', '../../', '../../../', '../../../../', '../../../../../', 'web/', 'org/terraswarm/accessor/accessors/web/'];

    var pathName, src;
    
    // For node, add _dirname.  If this is not done, then if this test
    // is run before node/tests/mocha/testNodeAuto.js, then
    // testNodeAuto.js will fail.
    if (typeof __dirname !== 'undefined') {
        searchPath.splice(0, 0, __dirname + '/');
    }
    for (i = 0; i < searchPath.length; i++) {
        // Append a '.js' to the name, if needed.
        if (name.indexOf('.js') !== name.length - 3) {
            name += '.js';
        }
        try {
            if (hasFsModule) {
                pathName = searchPath[i] + name;
                //console.log("testCommon.js: pathName: " + pathName);
                if (fs.statSync(pathName).isFile()) {
                    return fs.readFileSync(pathName, 'utf8');
                }
            } else {
                pathName = searchPath[i] + name;
                // print("testCommon.js: pathName: " + pathName);

                if (typeof Duktape === 'object') {
                    src = FileIo.readfile(pathName);
                    // FIXME: jshint warns "Invalid typeof value 'buffer'", but this might be ok for Duktape2.x
                    if (typeof src === 'buffer') {
                        // print("testCommon.js: returning contents of " + pathName);
                        return src;
                    }
                } else {
                    // Nashorn
                    var FileReader = java.io.FileReader,
                        BufferedReader = java.io.BufferedReader,
                        buffered;
                    src = '';

                    try {
                        buffered = new BufferedReader(new FileReader(pathName));
                        while ((line = buffered.readLine()) !== null) {
                            src += line + '\n';
                        }
                    } finally {
                        if (typeof buffered !== 'undefined') {
                            buffered.close(); // close the stream so there's no file locks
                        }
                    }
                    // print("testCommon.js: returning contents of " + pathName);
                    return src;
                }
            }
        } catch (e) {
            //print("getAccessorCode(" + name + "):" + e);
        }
    }
    throw ('Failed to find ' + name + ". Looked in " + searchPath);
};

/** Get a resource.
 *  Below are the types of resources that are handled
 *  all other resources will cause an error.
 *  This is a copy from nodeHose.js so that this test file can run.
 *
 *  * $KEYSTORE is replaced with $HOME/.ptKeystore
 *
 *  @param uri A specification for the resource.
 */
getResource = function (uri) {

    // We might want the Node host (and in fact all hosts) to allow access to
    // resources that are given with relative paths. By default, these would
    // get resolved relative to the location of the file defining the swarmlet.
    // This might even work in the Browser host with the same source
    // policy.

    if (uri.startsWith('$KEYSTORE') === true) {
        var home = process.env.HOME;
        if (home === undefined) {
            throw new Error('Could not get $HOME from the environment to expand ' + uri);
        } else {
            var path = require('path');
            uri = uri.replace('$KEYSTORE', home + path.sep + '.ptKeystore');
            code = fs.readFileSync(uri, 'utf8');
            return code;
        }
    }
    throw new Error('getResouce(' + uri + ', ' + timeout + ') only supports $KEYSTORE, not ' +
        uri);
}


// Read the accessor source code.
var code = getAccessorCode('test/TestAccessor');

// Require the accessor module to turn the source code into an accessor instance.
var commonHost = require('../commonHost.js');

// Create an accessor instance.
var instance = new commonHost.Accessor('TestAccessor', code);

// Invoke the initialize function.
instance.initialize();

// Examine the instance in JSON format.
console.log('Instance of TestAccessor: %j\nTests:', instance);

function test(testName, expression, expectedValue) {
    if (expression != expectedValue) {

        // Don't print a stack trace here, it is confusing.  Instead,
        // throw a new Error(), which will have the stack

        // // Print a stack trace.
        // var e = new Error('dummy');
        // var stack = e.stack.replace(/^[^\(]+?[\n$]/gm, '')
        //     .replace(/^\s+at\s+/gm, '')
        //     .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@')
        //     .split('\n');
        // console.log("==== testCommon.js: test Failed: \"" + testName + "\"\n\tExpression     \"" + expression + "\" is !=\n\tExpectedValue: \"" + expectedValue + "\"");
        // console.log("\tThe stack was:");
        // console.log(stack);

        // Use throw new Error() here so that we get a stack
        throw new Error('*** Test failed: ' + testName +
            '. Expected: ' + expectedValue +
            ', but got: ' + expression);
    } else {
        console.log('Test passed: ' + testName);
    }
}

// Check this.getParameter() with default value.
test('TestAccessor: getParameter', instance.getParameter('p'), 42);

// Check this.setParameter() and getParameter.
instance.setParameter('p', 12);
test('TestAccessor: setParameter', instance.getParameter('p'), 12);

// Check this.get().
test('TestAccessor: get', instance.get('numeric'), 0);

// Check this.get() with no input yet provided.
test('TestAccessor: get with undefined', instance.get('untyped'), null);

// Check this.get() with no input yet provided but type being boolean.
test('TestAccessor: get with undefined', instance.get('boolean'), null);

// Check provideInput().
instance.provideInput('boolean', true);
test('TestAccessor: provideInput()', instance.get('boolean'), true);

// Check inputHandlers, send, and latestOutput.
instance.react();
test('TestAccessor: react, send, and latestOutput', instance.latestOutput('negation'), false);

// Check composite accessors with manual and automatic scheduling.

// Have to provide an implementation of this.instantiate(), which in this case will only
// instantiate accessors founds in the accessors repo directory.
var code = getAccessorCode('test/TestComposite');
var a = new commonHost.Accessor('TestComposite', code, getAccessorCode);
a.initialize();

// Check assigned priorities.
test('TestComposite: priority number of destination is higher than source',
    a.containedAccessors[0].priority < a.containedAccessors[1].priority,
    true);

a.provideInput('input', 10);
a.containedAccessors[0].react();
a.containedAccessors[1].react();
test('TestComposite: composite accessor with manual scheduling',
    a.latestOutput('output'), 50);

a.initialize();
a.provideInput('input', 5);
a.react();
test('TestComposite: composite accessor with automatic scheduling',
    a.latestOutput('output'), 25);

// Note that the following two tests will run concurrently (!)

// Test spontaneous accessor.
var b = commonHost.instantiateAccessor('TestSpontaneous', 'test/TestSpontaneous',
    getAccessorCode);

b.initialize();
b.setTimeoutDeterministic(function () {
    test('TestSpontaneous: Test that spontaneous accessor produces 0 after 1 second',
        b.latestOutput('output'), 0);
}, 1500);
b.setTimeoutDeterministic(function () {
    test('TestSpontaneous: Test that spontaneous accessor produces 1 after 2 seconds',
        b.latestOutput('output'), 1);
    b.wrapup();
}, 2500);

// Test composite spontaneous accessor.
var c = commonHost.instantiateAccessor(
    'TestCompositeSpontaneous', 'test/TestCompositeSpontaneous', getAccessorCode);
c.initialize();
c.setTimeoutDeterministic(function () {
    test('TestCompositeSpontaneous: Test that composite spontaneous accessor produces 0 after 1 second',
        c.latestOutput('output'), 0);
}, 1500);
c.setTimeoutDeterministic(function () {
    test('TestCompositeSpontaneous: Test that composite spontaneous accessor produces 4 after 2 seconds',
        c.latestOutput('output'), 4);
    c.wrapup();
}, 2500);

// Test this.extend().
var d = commonHost.instantiateAccessor(
    'TestInheritance', 'test/TestInheritance', getAccessorCode);
d.initialize();
d.provideInput('untyped', 'foo');
d.react();
test('TestInheritance: inheritance, function overriding, and variable visibility',
    d.latestOutput('jsonOfUntyped'), 'hello');

// Test this.implement().
var e = commonHost.instantiateAccessor(
    'TestImplement', 'test/TestImplement', getAccessorCode);
e.initialize();
e.provideInput('numeric', 42);
e.react();
test('TestImplement: implementing an interface',
    e.latestOutput('numericPlusP'), 84);

// Test access to exported fields of base classes an proper scoping of initialize().
var f = commonHost.instantiateAccessor(
    'TestDerivedC', 'test/TestDerivedC', getAccessorCode);
f.initialize();
f.provideInput('in1', '42');
f.react();
test('TestDerivedC: access to base class exports properties',
    f.latestOutput('out1'), 2);

// Test two-level inheritance.
var g = commonHost.instantiateAccessor(
    'TestDerivedAgainA', 'test/TestDerivedAgainA', getAccessorCode);
g.initialize();
g.provideInput('in1', 42);
g.react();
test('TestDerivedAgainA: two-level inheritance, out1', g.latestOutput('out1'), 2);
test('TestDerivedAgainA-2: two-level inheritance, out2', g.latestOutput('out2'), 2);