/*globals process __filename __dirname ARGV ENV Ct spade $util $fs $path OptionParser exit */ // NOTE: Import modules this way because later will will reset rootdir require('ivory'); var OptionParser = require('optparse/optparse').OptionParser; var UTIL = require('core-test/utils'); /* We start with any paths you pass or the current working directory and walk up the tree looking for a package. Then we try to load and run any tests in the paths that you named. */ var cwd = $path.cwd(); // .......................................................... // PROCESS ARGS // // really simple arguments processing for now var SWITCHES = [ ['-v', '--verbose', 'Show tests with verbose mode instead of summary'], ['-c', '--color', 'Show tests in color'], ['-q', '--quiet', 'Disable extra info. Opposite of verbose'], ['-C', '--no-color', 'Disable color. Opposite of --color'], ['-a', '--all', 'Run all tests in paths'], ['-A', '--no-all', 'Run only files ending in test or spec'], ['--qunit', 'Include qUnit helpers'], ['-p', '--package', 'Include package for tests'], ['-V', '--version', 'Show version info'], ['-h', '--help', 'Show help info'] ]; var parser = new OptionParser(SWITCHES); parser.on('verbose', function() { ENV.VERBOSE = true; }); parser.on('color', function() { ENV.COLOR = true; }); parser.on('quiet', function() { ENV.VERBOSE = false; }); parser.on('no-color', function() { ENV.COLOR = false; }); parser.on('all', function() { ENV.ALL_FILES = true; }); parser.on('no-all', function() { ENV.ALL_FILES = false; }); parser.on('qunit', function() { ENV.QUNIT = true; }); parser.on('package', function(){ ENV.PACKAGE = true; }); parser.on('help', function() { require('core-test/core'); $util.puts("cot v"+Ct.VERSION+"\nFlexible testing library"); $util.puts(parser.toString()); exit(0); }); parser.on('version', function() { // important: don't load core-test unless its needed until after we // process any args. Otherwise they might not be picked up require('core-test/core'); $util.puts("cot v"+Ct.VERSION+"\nFlexible testing library"); exit(0); }); var shouldUpdate = ARGV[0] && (ARGV[0].toLowerCase() === 'update'); if (shouldUpdate) ARGV.shift(); var paths = parser.parse(ARGV); // .......................................................... // HELPERS // function expandPath(path) { if (path[0] !== '/') path = $path.join($path.cwd(), path); return $path.normalize(path); } function discoverPackage(path) { path = expandPath(path); function direxists(dirname) { return $path.exists($path.join(path, dirname)); } while(path !== '/') { if (['package.json', '.spade'].some(direxists)) return path; else path = $path.dirname(path); } return null; } function loadPackage(path) { path = expandPath(path); var packagePath = $path.join(path, 'package.json'); var json = {}; if ($path.exists(packagePath)) { json = JSON.parse($fs.readFile(packagePath, 'utf8')); } // normalize if (!json.name) json.name = $path.basename(path); if (!json.directories) json.directories = {}; if (!json.directories.tests) json.directories.tests = 'tests'; json.path = path; return json; } function uniq(array) { var hash = {}, ret = []; array.forEach(function(item) { if (hash[item]) return; hash[item] = item; ret.push(item); }); return ret; } // expands the named path to include all any included files (if path is a dir) function glob(path) { if (!$path.exists(path)) throw new Error(path+' does not exist'); if (!$fs.stat(path).isDirectory()) return [path]; // just this file var ret = []; $fs.readdir(path).forEach(function(name) { if (name[0]==='.') return; //skip hidden files ret = ret.concat(glob($path.join(path, name))); }); return ret ; } var EXPECTS = ['test', 'spec']; function isTestFile(path) { var ext = $path.extname(path), filename = $path.basename(path), idx; if (ext && ext.length>0) filename = filename.slice(0, 0-ext.length); idx = filename.lastIndexOf('-'); if (idx<0) idx = filename.lastIndexOf('_'); if (idx<0) return false; return EXPECTS.indexOf(filename.slice(idx+1).toLowerCase())>=0; } // .......................................................... // Handle update case // if (shouldUpdate) { if (paths.length === 0) paths = ['.'] paths = paths.map(discoverPackage); if (paths.some(function(p) { return !p; })) { throw new Error('Not all paths specified are within a package'); } // load uniq packages uniq(paths).map(loadPackage).forEach(function(pkg) { var testPath = $path.join(pkg.path, pkg.directories.tests); if (!$path.exists(testPath) || !$fs.stat(testPath).isDirectory()) { console.warn('Skipping '+testPath+' because it is invalid'); return ; } var modRoot = pkg.name + '/~tests'; var moduleIds = []; moduleIds = glob(testPath).filter(isTestFile).map(function(path) { return modRoot+path.slice(testPath.length, -$path.extname(path).length); }); var body = [ '// GENERATED: '+ new Date().toString(), '// This file is automatically generated by spade. To update run ', "// 'cot update'. To use this file, reference it in your HTML file.", "// Be sure that your base URL is to the top level directory of your", "// package.", "", "var runner = require('core-test/runner');", "runner.register('"+moduleIds.join("','")+"');", "runner.run();", "" ].join("\n"); var runnerPath = $path.join(testPath, 'ct-runner.js'); $fs.writeFile(runnerPath, body, 'w+'); console.log('Updated file at '+runnerPath); }); exit(0); } // .......................................................... // From paths, find all tests to load and run // if (paths.length === 0) paths = ['.']; // assume cwd() only paths = paths.map(function(path) { if (path[0] !== '/') path = $path.join(cwd, path); return $path.normalize(path); }); // expand the paths to include all contents var expanded = []; paths.forEach(function(path) { expanded = expanded.concat(glob(path)); }); paths = expanded; // filter only files ending in '_test', '-test', '_spec' or '-spec' if (!paths) paths = []; else if ((paths.length!==1) && !ENV.ALL_FILES) { paths = paths.filter(isTestFile); } var rootdir = null; paths.forEach(function(path) { var root = spade.loader.discoverRoot(path); if (!rootdir) rootdir = root; else if (rootdir !== root) { throw new Error('Only one package root supported per test run'); } }); spade.loader.root(rootdir); var Ct = require('core-test'); // now go through paths, and require the tests paths.forEach(function(path) { var sb = require.sandbox(true); var sbCt = sb.require('core-test'); var requirePath = "file:"+path; sbCt.defaultLogger = Ct.defaultLogger; // share logger if (ENV.QUNIT) { sb.require('core-test/qunit'); } if (ENV.PACKAGE) { var packagePath = discoverPackage(path), packageName = packagePath ? $path.basename(packagePath) : null; // Is loading by basename correct? if (packagePath) { sb.require(packageName); // Load in the context of the package requirePath = packageName+"/~"+path.replace(packagePath+'/','').replace('.js',''); } } sb.require(requirePath); sbCt.run(); }); // don't put before options processing since the logger is created here Ct.then(this, function() { var summary = Ct.defaultLogger.summary(); $util.puts('\n\n'+summary); $util.puts('\n'); }); Ct.run();