377 lines · 16.2 KB · JavaScript
RawDownload
/* eslint-env jasmine, node *//* global print:true, Script:true, global:true, require:true *//* eslint-disable comma-dangle */var isNode = instrumentTestrunner(),    runInterfaceTests = !isNode,    runNetworkTests = true; // describe wrappers (note: `xdescribe` indicates a disabled or "pending" jasmine test)var INTERFACE = { describe: runInterfaceTests ? describe : xdescribe },    NETWORK = { describe: runNetworkTests ? describe : xdescribe }; describe('require', function() {    describe('resolve', function() {        it('should resolve relative filenames', function() {            var expected = Script.resolvePath('./moduleTests/example.json');            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 unanchored, existing filenames with advice', function() {                expect(function() {                    var mod = require.resolve('moduleTests/example.json');                    mod.exists;                }).toThrowError(/use '.\/moduleTests\/example\.json'/);            });            it('should reject unanchored, non-existing filenames', function() {                expect(function() {                    var mod = require.resolve('asdfssdf/example.json');                    mod.exists;                }).toThrowError(/Cannot find.*system module not found/);            });            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() {        it('should import .json modules', function() {            var example = require('./moduleTests/example.json');            expect(example.name).toEqual('Example JSON Module');        });        // noet: support for loading JSON via content type workarounds reverted        // (leaving these tests intact in case ever revisited later)        // INTERFACE.describe('interface', function() {        //     NETWORK.describe('network', function() {        //         xit('should import #content-type=application/json modules', function() {        //             var results = require('https://jsonip.com#content-type=application/json');        //             expect(results.ip).toMatch(/^[.0-9]+$/);        //         });        //         xit('should import content-type: application/json modules', function() {        //             var scope = { 'content-type': 'application/json' };        //             var results = require.call(scope, 'https://jsonip.com');        //             expect(results.ip).toMatch(/^[.0-9]+$/);        //         });        //     });        // });     });     INTERFACE.describe('system', function() {        it('require("vec3")', function() {            expect(require('vec3')).toEqual(jasmine.any(Function));        });        it('require("vec3").method', function() {            expect(require('vec3')().isValid).toEqual(jasmine.any(Function));        });        it('require("vec3") as constructor', function() {            var vec3 = require('vec3');            var v = vec3(1.1, 2.2, 3.3);            expect(v).toEqual(jasmine.any(Object));            expect(v.isValid).toEqual(jasmine.any(Function));            expect(v.isValid()).toBe(true);            expect(v.toString()).toEqual('[Vec3 (1.100,2.200,3.300)]');        });    });     describe('cache', function() {        it('should cache modules by resolved module id', function() {            var value = new Date;            var example = require('./moduleTests/example.json');            // earmark the module object with a unique value            example['.test'] = value;            var example2 = require('../../tests/unit_tests/moduleTests/example.json');            expect(example2).toBe(example);            // verify earmark is still the same after a second require()            expect(example2['.test']).toBe(example['.test']);        });        it('should reload cached modules set to null', function() {            var value = new Date;            var example = require('./moduleTests/example.json');            example['.test'] = value;            require.cache[require.resolve('../../tests/unit_tests/moduleTests/example.json')] = null;            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['.test']).not.toBe(example['.test']);        });        it('should reload when module property is deleted', function() {            var value = new Date;            var example = require('./moduleTests/example.json');            example['.test'] = value;            delete require.cache[require.resolve('../../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['.test']).not.toBe(example['.test']);        });    });     describe('cyclic dependencies', function() {        describe('should allow lazy-ref cyclic module resolution', function() {            var main;            beforeEach(function() {                // eslint-disable-next-line                try { this._print = print; } catch (e) {}                // during these tests print() is no-op'd so that it doesn't disrupt the reporter output                print = function() {};                Script.resetModuleCache();            });            afterEach(function() {                print = this._print;            });            it('main is requirable', function() {                main = require('./moduleTests/cycles/main.js');                expect(main).toEqual(jasmine.any(Object));            });            it('transient a and b done values', function() {                expect(main.a['b.done?']).toBe(true);                expect(main.b['a.done?']).toBe(false);            });            it('ultimate a.done?', function() {                expect(main['a.done?']).toBe(true);            });            it('ultimate b.done?', function() {                expect(main['b.done?']).toBe(true);            });        });    });     describe('JS', function() {        it('should throw catchable local file errors', function() {            expect(function() {                require('file:///dev/null/non-existent-file.js');            }).toThrowError(/path not found|Cannot find.*non-existent-file/);        });        it('should throw catchable invalid id errors', function() {            expect(function() {                require(new Array(4096 * 2).toString());            }).toThrowError(/invalid.*size|Cannot find.*,{30}/);        });        it('should throw catchable unresolved id errors', function() {            expect(function() {                require('foobar:/baz.js');            }).toThrowError(/could not resolve|Cannot find.*foobar:/);        });         NETWORK.describe('network', function() {            // note: depending on retries these tests can take up to 60 seconds each to timeout            var timeout = 75 * 1000;            it('should throw catchable host errors', function() {                expect(function() {                    var mod = require('http://non.existent.highfidelity.io/moduleUnitTest.js');                    print("mod", Object.keys(mod));                }).toThrowError(/error retrieving script .ServerUnavailable.|Cannot find.*non.existent/);            }, timeout);            it('should throw catchable network timeouts', function() {                expect(function() {                    require('http://ping.highfidelity.io:1024');                }).toThrowError(/error retrieving script .Timeout.|Cannot find.*ping.highfidelity/);            }, timeout);        });    });     INTERFACE.describe('entity', function() {        var sampleScripts = [            'entityConstructorAPIException.js',            'entityConstructorModule.js',            'entityConstructorNested2.js',            'entityConstructorNested.js',            'entityConstructorRequireException.js',            'entityPreloadAPIError.js',            'entityPreloadRequire.js',        ].filter(Boolean).map(function(id) {            return Script.require.resolve('./moduleTests/entity/'+id);        });         var uuids = [];        function cleanup() {            uuids.splice(0,uuids.length).forEach(function(uuid) {                Entities.deleteEntity(uuid);            });        }        afterAll(cleanup);        // extra sanity check to avoid lingering entities        Script.scriptEnding.connect(cleanup);         for (var i=0; i < sampleScripts.length; i++) {            maketest(i);        }         function maketest(i) {            var script = sampleScripts[ i % sampleScripts.length ];            var shortname = '['+i+'] ' + script.split('/').pop();            var position = MyAvatar.position;            position.y -= i/2;            // define a unique jasmine test for the current entity script            it(shortname, function(done) {                var uuid = Entities.addEntity({                    text: shortname,                    description: Script.resolvePath('').split('/').pop(),                    type: 'Text',                    position: position,                    rotation: MyAvatar.orientation,                    script: script,                    scriptTimestamp: +new Date,                    lifetime: 20,                    lineHeight: 1/8,                    dimensions: { x: 2, y: 0.5, z: 0.01 },                    backgroundColor: { red: 0, green: 0, blue: 0 },                    color: { red: 0xff, green: 0xff, blue: 0xff },                }, !Entities.serversExist() || !Entities.canRezTmp());                uuids.push(uuid);                function stopChecking() {                    if (ii) {                        Script.clearInterval(ii);                        ii = 0;                    }                }                var ii = Script.setInterval(function() {                    Entities.queryPropertyMetadata(uuid, "script", function(err, result) {                        if (err) {                            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 */);        }    });}); // support for isomorphic Node.js / Interface unit testing// note: run `npm install` from unit_tests/ and then `node moduleUnitTests.js`function run() {}function instrumentTestrunner() {    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) {        /* 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 jasmine = jasmineRequire.core(jasmineRequire);        var env = jasmine.getEnv();        var jasmineInterface = jasmineRequire.interface(jasmine, env);        for (var p in jasmineInterface) {            global[p] = jasmineInterface[p];        }        env.addReporter(new (require('jasmine-console-reporter')));        // testing mocks        Script = {            resetModuleCache: function() {                module.require.cache = {};            },            setTimeout: setTimeout,            clearTimeout: clearTimeout,            resolvePath: function(id) {                // this attempts to accurately emulate how Script.resolvePath works                var trace = {}; Error.captureStackTrace(trace);                var base = trace.stack.split('\n')[2].replace(/^.*[(]|[)].*$/g,'').replace(/:[0-9]+:[0-9]+.*$/,'');                if (!id) {                    return base;                }                var rel = base.replace(/[^\/]+$/, id);                return require.resolve(rel);            },            require: function(mod) {                return require(Script.require.resolve(mod));            },        };        Script.require.cache = require.cache;        Script.require.resolve = function(mod) {            if (mod === '.' || /^\.\.($|\/)/.test(mod)) {                throw new Error("Cannot find module (is dir)");            }            var path = require.resolve(mod);            // console.info('node-require-reoslved', mod, path);            try {                if (require('fs').lstatSync(path).isDirectory()) {                    throw new Error("Cannot find module (is directory)");                }                // console.info('!path', path);            } catch (e) {                console.error(e);            }            return path;        };        print = console.info.bind(console, '[print]');        /* eslint-enable no-console */    } else {        // Interface test mode        global = this;        Script.require('../../../system/libraries/utils.js');        this.jasmineRequire = Script.require('../../libraries/jasmine/jasmine.js');        Script.require('../../libraries/jasmine/hifi-boot.js');        require = Script.require;        // polyfill console        /* global console:true */        console = {            log: print,            info: print.bind(this, '[info]'),            warn: print.bind(this, '[warn]'),            error: print.bind(this, '[error]'),            debug: print.bind(this, '[debug]'),        };    }    // eslint-disable-next-line    run = function() { global.jasmine.getEnv().execute(); };    return isNode;}run();