unit test cleanup pass

This commit is contained in:
humbletim 2017-03-01 15:19:23 -05:00
parent 143b67e47d
commit fa0d3a1845
13 changed files with 206 additions and 143 deletions

View file

@ -1,3 +1,4 @@
/* eslint-env node */
var a = exports; var a = exports;
a.done = false; a.done = false;
var b = require('./b.js'); var b = require('./b.js');

View file

@ -1,3 +1,4 @@
/* eslint-env node */
var b = exports; var b = exports;
b.done = false; b.done = false;
var a = require('./a.js'); var a = require('./a.js');

View file

@ -1,3 +1,7 @@
/* eslint-env node */
/* global print */
/* eslint-disable comma-dangle */
print('main.js'); print('main.js');
var a = require('./a.js'), var a = require('./a.js'),
b = require('./b.js'); b = require('./b.js');

View file

@ -1,3 +1,4 @@
/* eslint-disable comma-dangle */
// test module method exception being thrown within main constructor // test module method exception being thrown within main constructor
(function() { (function() {
var apiMethod = Script.require('../exceptions/exceptionInFunction.js'); var apiMethod = Script.require('../exceptions/exceptionInFunction.js');
@ -9,4 +10,4 @@
print("entityConstructorAPIException::preload -- never seen --", uuid, Script.resolvePath('')); print("entityConstructorAPIException::preload -- never seen --", uuid, Script.resolvePath(''));
}, },
}; };
}) });

View file

@ -1,3 +1,5 @@
/* global module */
/* eslint-disable comma-dangle */
// test dual-purpose module and standalone Entity script // test dual-purpose module and standalone Entity script
function MyEntity(filename) { function MyEntity(filename) {
return { return {
@ -16,6 +18,6 @@ function MyEntity(filename) {
try { try {
module.exports = MyEntity; module.exports = MyEntity;
} catch(e) {} } catch (e) {} // eslint-disable-line no-empty
print('entityConstructorModule::MyEntity', typeof MyEntity); print('entityConstructorModule::MyEntity', typeof MyEntity);
(MyEntity) (MyEntity);

View file

@ -1,3 +1,4 @@
/* global module */
// test Entity constructor based on inherited constructor from a module // test Entity constructor based on inherited constructor from a module
function constructor() { function constructor() {
print("entityConstructorNested::constructor"); print("entityConstructorNested::constructor");
@ -7,7 +8,7 @@ function constructor() {
try { try {
module.exports = constructor; module.exports = constructor;
} catch(e) { } catch (e) {
constructor; constructor;
} }

View file

@ -1,9 +1,10 @@
/* global module */
// test Entity constructor based on nested, inherited module constructors // test Entity constructor based on nested, inherited module constructors
function constructor() { function constructor() {
print("entityConstructorNested2::constructor"); print("entityConstructorNested2::constructor");
// inherit from entityConstructorNested // inherit from entityConstructorNested
var Entity = Script.require('./entityConstructorNested.js'); var MyEntity = Script.require('./entityConstructorNested.js');
function SubEntity() {} function SubEntity() {}
SubEntity.prototype = new MyEntity('-- created from entityConstructorNested2 --'); SubEntity.prototype = new MyEntity('-- created from entityConstructorNested2 --');
@ -19,6 +20,6 @@ function constructor() {
try { try {
module.exports = constructor; module.exports = constructor;
} catch(e) { } catch (e) {
constructor; constructor;
} }

View file

@ -1,9 +1,10 @@
/* eslint-disable comma-dangle */
// test module-related exception from within "require" evaluation itself // test module-related exception from within "require" evaluation itself
(function() { (function() {
var mod = Script.require('../exceptions/exception.js'); var mod = Script.require('../exceptions/exception.js');
return { return {
preload: function(uuid) { preload: function(uuid) {
print("entityConstructorRequireException::preload (never happens)", uuid, Script.resolvePath('')); print("entityConstructorRequireException::preload (never happens)", uuid, Script.resolvePath(''), mod);
}, },
}; };
}) });

View file

@ -1,3 +1,4 @@
/* eslint-disable comma-dangle */
// test module method exception being thrown within preload // test module method exception being thrown within preload
(function() { (function() {
var apiMethod = Script.require('../exceptions/exceptionInFunction.js'); var apiMethod = Script.require('../exceptions/exceptionInFunction.js');
@ -9,4 +10,4 @@
print("entityPreloadAPIException::preload -- never seen --", uuid, Script.resolvePath('')); print("entityPreloadAPIException::preload -- never seen --", uuid, Script.resolvePath(''));
}, },
}; };
}) });

View file

@ -1,3 +1,4 @@
/* eslint-disable comma-dangle */
// test requiring a module from within preload // test requiring a module from within preload
(function constructor() { (function constructor() {
return { return {
@ -7,4 +8,4 @@
print("entityPreloadRequire::example::name", example.name); print("entityPreloadRequire::example::name", example.name);
}, },
}; };
}) });

View file

@ -1,3 +1,4 @@
/* eslint-env node */
module.exports = "n/a"; module.exports = "n/a";
throw new Error('exception on line 2'); throw new Error('exception on line 2');

View file

@ -1,3 +1,4 @@
/* eslint-env node */
// dummy lines to make sure exception line number is well below parent test script // dummy lines to make sure exception line number is well below parent test script
// //
// //
@ -30,8 +31,8 @@
function myfunc() { function myfunc() {
throw new Error('exception on line 32 in myfunc'); throw new Error('exception on line 32 in myfunc');
return "myfunc";
} }
module.exports = myfunc; module.exports = myfunc;
if (Script[module.filename] === 'throw') if (Script[module.filename] === 'throw') {
myfunc(); myfunc();
}

View file

@ -1,18 +1,65 @@
/* eslint-env jasmine */ /* eslint-env jasmine, node */
/* global print:true, Script:true, global:true, require:true */
/* eslint-disable comma-dangle */
var isNode = instrumentTestrunner(),
runInterfaceTests = !isNode,
runNetworkTests = true;
var isNode = instrument_testrunner(); // describe wrappers (note: `xdescribe` indicates a disabled or "pending" jasmine test)
var INTERFACE = { describe: runInterfaceTests ? describe : xdescribe },
NETWORK = { describe: runNetworkTests ? describe : xdescribe };
var NETWORK_describe = xdescribe,
INTERFACE_describe = !isNode ? describe : xdescribe,
NODE_describe = isNode ? describe : xdescribe;
print("DESCRIBING");
describe('require', function() { describe('require', function() {
describe('resolve', function() { describe('resolve', function() {
it('should resolve relative filenames', function() { it('should resolve relative filenames', function() {
var expected = Script.resolvePath('./moduleTests/example.json'); var expected = Script.resolvePath('./moduleTests/example.json');
expect(require.resolve('./moduleTests/example.json')).toEqual(expected); expect(require.resolve('./moduleTests/example.json')).toEqual(expected);
}); });
describe('exceptions', function() {
it('should reject blank "" module identifiers', function() {
expect(function() {
require.resolve('');
}).toThrowError(/Cannot find/);
});
it('should reject excessive identifier sizes', function() {
expect(function() {
require.resolve(new Array(8193).toString());
}).toThrowError(/Cannot find/);
});
it('should reject implicitly-relative filenames', function() {
expect(function() {
var mod = require.resolve('example.js');
mod.exists;
}).toThrowError(/Cannot find/);
});
it('should reject non-existent filenames', function() {
expect(function() {
require.resolve('./404error.js');
}).toThrowError(/Cannot find/);
});
it('should reject identifiers resolving to a directory', function() {
expect(function() {
var mod = require.resolve('.');
mod.exists;
// console.warn('resolved(.)', mod);
}).toThrowError(/Cannot find/);
expect(function() {
var mod = require.resolve('..');
mod.exists;
// console.warn('resolved(..)', mod);
}).toThrowError(/Cannot find/);
expect(function() {
var mod = require.resolve('../');
mod.exists;
// console.warn('resolved(../)', mod);
}).toThrowError(/Cannot find/);
});
(isNode ? xit : it)('should reject non-system, extensionless identifiers', function() {
expect(function() {
require.resolve('./example');
}).toThrowError(/Cannot find/);
});
});
}); });
describe('JSON', function() { describe('JSON', function() {
@ -20,12 +67,12 @@ describe('require', function() {
var example = require('./moduleTests/example.json'); var example = require('./moduleTests/example.json');
expect(example.name).toEqual('Example JSON Module'); expect(example.name).toEqual('Example JSON Module');
}); });
INTERFACE_describe('inteface', function() { INTERFACE.describe('interface', function() {
NETWORK_describe('network', function() { NETWORK.describe('network', function() {
//xit('should import #content-type=application/json modules', function() { // xit('should import #content-type=application/json modules', function() {
// var results = require('https://jsonip.com#content-type=application/json'); // var results = require('https://jsonip.com#content-type=application/json');
// expect(results.ip).toMatch(/^[.0-9]+$/); // expect(results.ip).toMatch(/^[.0-9]+$/);
//}); // });
it('should import content-type: application/json modules', function() { it('should import content-type: application/json modules', function() {
var scope = { 'content-type': 'application/json' }; var scope = { 'content-type': 'application/json' };
var results = require.call(scope, 'https://jsonip.com'); var results = require.call(scope, 'https://jsonip.com');
@ -36,66 +83,32 @@ describe('require', function() {
}); });
INTERFACE_describe('system', function() { INTERFACE.describe('system', function() {
it('require(id)', function() { it('require("vec3")', function() {
expect(require('vec3')).toEqual(jasmine.any(Function)); expect(require('vec3')).toEqual(jasmine.any(Function));
}); });
it('require(id).function', function() { it('require("vec3").method', function() {
expect(require('vec3')().isValid).toEqual(jasmine.any(Function)); expect(require('vec3')().isValid).toEqual(jasmine.any(Function));
}); });
}); it('require("vec3") as constructor', function() {
var vec3 = require('vec3');
describe('exceptions', function() { var v = vec3(1.1, 2.2, 3.3);
it('should reject blank "" module identifiers', function() { expect(v).toEqual(jasmine.any(Object));
expect(function() { expect(v.isValid).toEqual(jasmine.any(Function));
require.resolve(''); expect(v.isValid()).toBe(true);
}).toThrowError(/Cannot find/); expect(v.toString()).toEqual('[Vec3 (1.100,2.200,3.300)]');
}); });
it('should reject excessive identifier sizes', function() {
expect(function() {
require.resolve(new Array(8193).toString());
}).toThrowError(/Cannot find/);
});
it('should reject implicitly-relative filenames', function() {
expect(function() {
var mod = require.resolve('example.js');
}).toThrowError(/Cannot find/);
});
it('should reject non-existent filenames', function() {
expect(function() {
var mod = require.resolve('./404error.js');
}).toThrowError(/Cannot find/);
});
it('should reject identifiers resolving to a directory', function() {
expect(function() {
var mod = require.resolve('.');
//console.warn('resolved(.)', mod);
}).toThrowError(/Cannot find/);
expect(function() {
var mod = require.resolve('..');
//console.warn('resolved(..)', mod);
}).toThrowError(/Cannot find/);
expect(function() {
var mod = require.resolve('../');
//console.warn('resolved(../)', mod);
}).toThrowError(/Cannot find/);
});
if (typeof MODE !== 'undefined' && MODE !== 'node') {
it('should reject non-system, extensionless identifiers', function() {
expect(function() {
require.resolve('./example');
}).toThrowError(/Cannot find/);
});
}
}); });
describe('cache', function() { describe('cache', function() {
it('should cache modules by resolved module id', function() { it('should cache modules by resolved module id', function() {
var value = new Date; var value = new Date;
var example = require('./moduleTests/example.json'); var example = require('./moduleTests/example.json');
// earmark the module object with a unique value
example['.test'] = value; example['.test'] = value;
var example2 = require('../../tests/unit_tests/moduleTests/example.json'); var example2 = require('../../tests/unit_tests/moduleTests/example.json');
expect(example2).toBe(example); expect(example2).toBe(example);
// verify earmark is still the same after a second require()
expect(example2['.test']).toBe(example['.test']); expect(example2['.test']).toBe(example['.test']);
}); });
it('should reload cached modules set to null', function() { it('should reload cached modules set to null', function() {
@ -104,6 +117,7 @@ describe('require', function() {
example['.test'] = value; example['.test'] = value;
require.cache[require.resolve('../../tests/unit_tests/moduleTests/example.json')] = null; require.cache[require.resolve('../../tests/unit_tests/moduleTests/example.json')] = null;
var example2 = require('../../tests/unit_tests/moduleTests/example.json'); var example2 = require('../../tests/unit_tests/moduleTests/example.json');
// verify the earmark is *not* the same as before
expect(example2).not.toBe(example); expect(example2).not.toBe(example);
expect(example2['.test']).not.toBe(example['.test']); expect(example2['.test']).not.toBe(example['.test']);
}); });
@ -113,6 +127,7 @@ describe('require', function() {
example['.test'] = value; example['.test'] = value;
delete require.cache[require.resolve('../../tests/unit_tests/moduleTests/example.json')]; delete require.cache[require.resolve('../../tests/unit_tests/moduleTests/example.json')];
var example2 = require('../../tests/unit_tests/moduleTests/example.json'); var example2 = require('../../tests/unit_tests/moduleTests/example.json');
// verify the earmark is *not* the same as before
expect(example2).not.toBe(example); expect(example2).not.toBe(example);
expect(example2['.test']).not.toBe(example['.test']); expect(example2['.test']).not.toBe(example['.test']);
}); });
@ -120,30 +135,29 @@ describe('require', function() {
describe('cyclic dependencies', function() { describe('cyclic dependencies', function() {
describe('should allow lazy-ref cyclic module resolution', function() { describe('should allow lazy-ref cyclic module resolution', function() {
const MODULE_PATH = './moduleTests/cycles/main.js';
var main; var main;
beforeEach(function() { beforeEach(function() {
try { this._print = print; } catch(e) {} // eslint-disable-next-line
// for this test print is no-op'd so it doesn't disrupt the reporter output try { this._print = print; } catch (e) {}
//console = typeof console === 'object' ? console : { log: function() {} }; // during these tests print() is no-op'd so that it doesn't disrupt the reporter output
print = function() {}; print = function() {};
Script.resetModuleCache(); Script.resetModuleCache();
}); });
afterEach(function() { afterEach(function() {
print = this._print; print = this._print;
}); });
it('main requirable', function() { it('main is requirable', function() {
main = require(MODULE_PATH); main = require('./moduleTests/cycles/main.js');
expect(main).toEqual(jasmine.any(Object)); expect(main).toEqual(jasmine.any(Object));
}); });
it('main with both a and b', function() { it('transient a and b done values', function() {
expect(main.a['b.done?']).toBe(true); expect(main.a['b.done?']).toBe(true);
expect(main.b['a.done?']).toBe(false); expect(main.b['a.done?']).toBe(false);
}); });
it('a.done?', function() { it('ultimate a.done?', function() {
expect(main['a.done?']).toBe(true); expect(main['a.done?']).toBe(true);
}); });
it('b.done?', function() { it('ultimate b.done?', function() {
expect(main['b.done?']).toBe(true); expect(main['b.done?']).toBe(true);
}); });
}); });
@ -166,8 +180,8 @@ describe('require', function() {
}).toThrowError(/could not resolve|Cannot find.*foobar:/); }).toThrowError(/could not resolve|Cannot find.*foobar:/);
}); });
NETWORK_describe('network', function() { NETWORK.describe('network', function() {
// note: with retries these tests can take up to 60 seconds each to timeout // note: depending on retries these tests can take up to 60 seconds each to timeout
var timeout = 75 * 1000; var timeout = 75 * 1000;
it('should throw catchable host errors', function() { it('should throw catchable host errors', function() {
expect(function() { expect(function() {
@ -183,7 +197,7 @@ describe('require', function() {
}); });
}); });
INTERFACE_describe('entity', function() { INTERFACE.describe('entity', function() {
var sampleScripts = [ var sampleScripts = [
'entityConstructorAPIException.js', 'entityConstructorAPIException.js',
'entityConstructorModule.js', 'entityConstructorModule.js',
@ -192,72 +206,98 @@ describe('require', function() {
'entityConstructorRequireException.js', 'entityConstructorRequireException.js',
'entityPreloadAPIError.js', 'entityPreloadAPIError.js',
'entityPreloadRequire.js', 'entityPreloadRequire.js',
].filter(Boolean).map(function(id) { return Script.require.resolve('./moduleTests/entity/'+id); }); ].filter(Boolean).map(function(id) {
return Script.require.resolve('./moduleTests/entity/'+id);
});
var uuids = []; var uuids = [];
function cleanup() {
for(var i=0; i < sampleScripts.length; i++) { uuids.splice(0,uuids.length).forEach(function(uuid) {
(function(i) { Entities.deleteEntity(uuid);
var script = sampleScripts[ i % sampleScripts.length ]; });
var shortname = '['+i+'] ' + script.split('/').pop(); }
var position = MyAvatar.position; afterAll(cleanup);
position.y -= i/2; // extra sanity check to avoid lingering entities
it(shortname, function(done) { Script.scriptEnding.connect(cleanup);
var uuid = Entities.addEntity({
text: shortname, for (var i=0; i < sampleScripts.length; i++) {
description: Script.resolvePath('').split('/').pop(), maketest(i);
type: 'Text', }
position: position,
rotation: MyAvatar.orientation, function maketest(i) {
script: script, var script = sampleScripts[ i % sampleScripts.length ];
scriptTimestamp: +new Date, var shortname = '['+i+'] ' + script.split('/').pop();
lifetime: 20, var position = MyAvatar.position;
lineHeight: 1/8, position.y -= i/2;
dimensions: { x: 2, y: .5, z: .01 }, // define a unique jasmine test for the current entity script
backgroundColor: { red: 0, green: 0, blue: 0 }, it(shortname, function(done) {
color: { red: 0xff, green: 0xff, blue: 0xff }, var uuid = Entities.addEntity({
}, !Entities.serversExist() || !Entities.canRezTmp()); text: shortname,
uuids.push(uuid); description: Script.resolvePath('').split('/').pop(),
var ii = Script.setInterval(function() { type: 'Text',
Entities.queryPropertyMetadata(uuid, "script", function(err, result) { position: position,
if (err) { rotation: MyAvatar.orientation,
throw new Error(err); script: script,
} scriptTimestamp: +new Date,
if (result.success) { lifetime: 20,
clearInterval(ii); lineHeight: 1/8,
if (/Exception/.test(script)) dimensions: { x: 2, y: 0.5, z: 0.01 },
expect(result.status).toMatch(/^error_(loading|running)_script$/); backgroundColor: { red: 0, green: 0, blue: 0 },
else color: { red: 0xff, green: 0xff, blue: 0xff },
expect(result.status).toEqual("running"); }, !Entities.serversExist() || !Entities.canRezTmp());
done(); uuids.push(uuid);
} else { function stopChecking() {
print('!result.success', JSON.stringify(result)); if (ii) {
} Script.clearInterval(ii);
}); ii = 0;
}, 100); }
Script.setTimeout(function() { }
Script.clearInterval(ii); var ii = Script.setInterval(function() {
}, 4900); Entities.queryPropertyMetadata(uuid, "script", function(err, result) {
}, 5000 /* timeout */); if (err) {
})(i); stopChecking();
throw new Error(err);
}
if (result.success) {
stopChecking();
if (/Exception/.test(script)) {
expect(result.status).toMatch(/^error_(loading|running)_script$/);
} else {
expect(result.status).toEqual("running");
}
Entities.deleteEntity(uuid);
done();
} else {
print('!result.success', JSON.stringify(result));
}
});
}, 100);
Script.setTimeout(stopChecking, 4900);
}, 5000 /* jasmine async timeout */);
} }
Script.scriptEnding.connect(function() {
uuids.forEach(function(uuid) { Entities.deleteEntity(uuid); });
});
}); });
}); });
// support for isomorphic Node.js / Interface unit testing
// note: run `npm install` from unit_tests/ and then `node moduleUnitTests.js`
function run() {} function run() {}
function instrument_testrunner() { function instrumentTestrunner() {
var isNode = typeof process === 'object' && process.title === 'node'; var isNode = typeof process === 'object' && process.title === 'node';
if (typeof describe === 'function') {
// already running within a test runner; assume jasmine is ready-to-go
return isNode;
}
if (isNode) { if (isNode) {
// for consistency this still uses the same local jasmine.js library /* eslint-disable no-console */
// Node.js test mode
// to keep things consistent Node.js uses the local jasmine.js library (instead of an npm version)
var jasmineRequire = require('../../libraries/jasmine/jasmine.js'); var jasmineRequire = require('../../libraries/jasmine/jasmine.js');
var jasmine = jasmineRequire.core(jasmineRequire); var jasmine = jasmineRequire.core(jasmineRequire);
var env = jasmine.getEnv(); var env = jasmine.getEnv();
var jasmineInterface = jasmineRequire.interface(jasmine, env); var jasmineInterface = jasmineRequire.interface(jasmine, env);
for (var p in jasmineInterface) for (var p in jasmineInterface) {
global[p] = jasmineInterface[p]; global[p] = jasmineInterface[p];
}
env.addReporter(new (require('jasmine-console-reporter'))); env.addReporter(new (require('jasmine-console-reporter')));
// testing mocks // testing mocks
Script = { Script = {
@ -270,39 +310,45 @@ function instrument_testrunner() {
// this attempts to accurately emulate how Script.resolvePath works // this attempts to accurately emulate how Script.resolvePath works
var trace = {}; Error.captureStackTrace(trace); var trace = {}; Error.captureStackTrace(trace);
var base = trace.stack.split('\n')[2].replace(/^.*[(]|[)].*$/g,'').replace(/:[0-9]+:[0-9]+.*$/,''); var base = trace.stack.split('\n')[2].replace(/^.*[(]|[)].*$/g,'').replace(/:[0-9]+:[0-9]+.*$/,'');
if (!id) if (!id) {
return base; return base;
}
var rel = base.replace(/[^\/]+$/, id); var rel = base.replace(/[^\/]+$/, id);
console.info('rel', rel); console.info('rel', rel);
return require.resolve(rel); return require.resolve(rel);
}, },
require: function(mod) { require: function(mod) {
return require(Script.require.resolve(mod)); return require(Script.require.resolve(mod));
} },
}; };
Script.require.cache = require.cache; Script.require.cache = require.cache;
Script.require.resolve = function(mod) { Script.require.resolve = function(mod) {
if (mod === '.' || /^\.\.($|\/)/.test(mod)) if (mod === '.' || /^\.\.($|\/)/.test(mod)) {
throw new Error("Cannot find module '"+mod+"' (is dir)"); throw new Error("Cannot find module '"+mod+"' (is dir)");
}
var path = require.resolve(mod); var path = require.resolve(mod);
//console.info('node-require-reoslved', mod, path); // console.info('node-require-reoslved', mod, path);
try { try {
if (require('fs').lstatSync(path).isDirectory()) { if (require('fs').lstatSync(path).isDirectory()) {
throw new Error("Cannot find module '"+path+"' (is directory)"); throw new Error("Cannot find module '"+path+"' (is directory)");
} }
//console.info('!path', path); // console.info('!path', path);
} catch(e) { console.info(e) } } catch (e) {
console.error(e);
}
return path; return path;
}; };
print = console.info.bind(console, '[print]'); print = console.info.bind(console, '[print]');
/* eslint-enable no-console */
} else { } else {
// Interface test mode
global = this; global = this;
// Interface Test mode
Script.require('../../../system/libraries/utils.js'); Script.require('../../../system/libraries/utils.js');
this.jasmineRequire = Script.require('../../libraries/jasmine/jasmine.js'); this.jasmineRequire = Script.require('../../libraries/jasmine/jasmine.js');
Script.require('../../libraries/jasmine/hifi-boot.js') Script.require('../../libraries/jasmine/hifi-boot.js');
require = Script.require; require = Script.require;
// polyfill console // polyfill console
/* global console:true */
console = { console = {
log: print, log: print,
info: print.bind(this, '[info]'), info: print.bind(this, '[info]'),
@ -311,6 +357,7 @@ function instrument_testrunner() {
debug: print.bind(this, '[debug]'), debug: print.bind(this, '[debug]'),
}; };
} }
// eslint-disable-next-line
run = function() { global.jasmine.getEnv().execute(); }; run = function() { global.jasmine.getEnv().execute(); };
return isNode; return isNode;
} }