<commit_before>//test runner, based on protractor

var util = require('util'),
  fs = require('fs'),
  path = require('path'),
  glob = require('glob'),
  remote = require('selenium-webdriver/remote'),
  chrome = require('selenium-webdriver/chrome'),
  adapters = require('selenium-selenium-webdriver/testing'),
  Mocha = require('mocha'),
  Browser = require('./browser');

var server,
  driver,
  mocha,
  defaultConfig = {
    configDir: './defaultConfig',

    seleniumServerJar: null,
    seleniumArgs: [],
    seleniumPort: null,
    seleniumAddress: null,

    scriptsTimeout: 10000,

    capabilities: {
      browserName: 'chrome'
    },

    params: {},

    mochaOpts: {
      ui: 'bdd',
      reporter: 'spec'
    }
  };

/**
 * Merge config objects together.
 * @param {Object} into
 * @param {Object} from
 * @return {Object} The 'into' config.
 */
function merge(into, from) {
  for (var key in from) {
    if (into[key] instanceof Object && !(into[key] instanceof Array)) {
      merge(into[key], from[key]);
    } else {
      into[key] = from[key];
    }
  }
  return into;
}

/**
 * Add the options in the parameter config to this runner instance.
 * @param {Object} config
 */
function extendConfig(config) {
  // All filepaths should be kept relative to the current config location.
  // This will not affect absolute paths.
  ['seleniumServerJar', 'chromeDriver', 'beforeStart'].forEach(function(name) {
    if (config[name] && config.configDir && typeof config[name] === 'string') {
      config[name] = path.resolve(config.configDir, config[name]);
    }
  });
  merge(config, config);
}

/**
 * Sets up the Selenium server and returns a promise once the server is ready to go. After this function is called,
 * defaultConfig.seleniumAddress and defaultConfig.capabilities are set up.
 * @return {webdriver.promise.Promise.<string>} A promise which resolves to the value of the selenium address
 *     that will be used.
 */
function setupSelenium() {
  var dfd = webdriver.promise.defer(),
    defaultChromedriver;

  if (defaultConfig.chromeDriver) {
    if (!fs.existsSync(defaultConfig.chromeDriver)) {
      if (fs.existsSync(defaultConfig.chromeDriver + '.exe')) {
        defaultConfig.chromeDriver += '.exe';
      } else {
        throw new Error('Could not find chromedriver at ' + defaultConfig.chromeDriver);
      }
    }
  } else {
    defaultChromedriver = path.resolve(__dirname, '../selenium/chromedriver');
    if (fs.existsSync(defaultChromedriver)) {
      defaultConfig.chromeDriver = defaultChromedriver;
    } else if (fs.existsSync(defaultChromedriver + '.exe')) {
      defaultConfig.chromeDriver = defaultChromedriver + '.exe';
    }
  }

  if (defaultConfig.chromeOnly) {
    util.puts('Using ChromeDriver directly...');
    dfd.fulfill(null);
  } else if (defaultConfig.seleniumAddress) {
    util.puts('Using the selenium server at ' + defaultConfig.seleniumAddress);
    dfd.fulfill(defaultConfig.seleniumAddress);
  } else {
    util.puts('Starting selenium standalone server...');

    if (!defaultConfig.seleniumServerJar) {
      // Try to use the default location.
      var defaultStandalone = path.resolve(__dirname,
        '../selenium/selenium-server-standalone-' + require('../package.json').webdriverVersions.selenium + '.jar');
      if (!fs.existsSync(defaultStandalone)) {
        throw new Error('Unable to start selenium. You must specify either a seleniumAddress, seleniumServerJar, ' +
          'or use webdriver-manager.');
      } else {
        defaultConfig.seleniumServerJar = defaultStandalone;
      }
    } else if (!fs.existsSync(defaultConfig.seleniumServerJar)) {
      throw new Error('there\'s no selenium server jar at the specified location. Do you have the correct version?');
    }

    if (defaultConfig.chromeDriver) {
      defaultConfig.seleniumArgs.push('-Dwebdriver.chrome.driver=' + defaultConfig.chromeDriver);
    }

    server = new remote.SeleniumServer(defaultConfig.seleniumServerJar, {
      args: defaultConfig.seleniumArgs,
      port: defaultConfig.seleniumPort
    });

    server.start().then(function(url) {
      util.puts('Selenium standalone server started at ' + url);
      defaultConfig.seleniumAddress = server.address();
      dfd.fulfill(defaultConfig.seleniumAddress);
    });
  }
}

/**
 * Cleans up the driver and selenium server
 * @param {number} failures
 */
function cleanupSelenium(failures) {
  var exitCode = (failures === 0) ? 0 : 1;
  if (server) {
    util.puts('Shutting down selenium standalone server.');
    server.stop().then(function() {
      process.exit(exitCode);
    });
  } else {
    process.exit(exitCode);
  }
}

/**
 * Run the tests.
 * @return {webdriver.promise.Promise} A promise that will resolve when the test run is finished.
 */
function runSpecs() {
  var specs = defaultConfig.specs,
    resolvedSpecs = [];

  for (var i = 0; i < specs.length; ++i) {
    var matches = glob.sync(specs[i], {cwd: defaultConfig.configDir});
    if (!matches.length) {
      util.puts('Warning: pattern ' + specs[i] + ' did not match any files.');
    }
    for (var j = 0; j < matches.length; ++j) {
      resolvedSpecs.push(path.resolve(defaultConfig.configDir, matches[j]));
    }
  }
  if (!resolvedSpecs.length) {
    throw new Error('Spec patterns did not match any files.');
  }

  var dfd = webdriver.promise.defer();

  if (defaultConfig.chromeOnly) {
    var service = new chrome.ServiceBuilder(defaultConfig.chromeDriver).build();
    driver = chrome.createDriver(
      new webdriver.Capabilities(defaultConfig.capabilities), service);
  } else {
    driver = new webdriver.Builder().
      usingServer(defaultConfig.seleniumAddress).
      withCapabilities(defaultConfig.capabilities).build();
  }

  driver.manage().timeouts().setScriptTimeout(defaultConfig.scriptsTimeout);

  var browser = Browser.wrap(driver, defaultConfig.baseUrl);
  Browser.setInstance(browser);
  browser.params = defaultConfig.params;

  global.browser = browser;
  global.$ = browser.$;
  global.element = browser.element;

  mocha = new Mocha(defaultConfig.mochaOpts);
  resolvedSpecs.forEach(mocha.addFile.bind(mocha));

  // Mocha doesn't set up the ui until the pre-require event, so
  // wait until then to load mocha-webdriver adapters as well.
  mocha.suite.on('pre-require', function() {
    global.describe = adapters.describe;
    global.describe.skip = global.xdescribe = adapters.xdescribe;
    global.describe.only = global.ddescribe = adapters.describe.only;

    global.after = adapters.after;
    global.afterEach = adapters.afterEach;
    global.before = adapters.before;
    global.beforeEach = adapters.beforeEach;

    global.it = adapters.it;
    global.it.skip = global.xit = adapters.xit;
    global.it.only = global.iit = adapters.it.only;

    global.ignore = adapters.ignore;
  });
  mocha.loadFiles();

  webdriver.promise.controlFlow().execute(function () {
    if (defaultConfig.beforeStart) {
      if (typeof defaultConfig.beforeStart === 'function') {
        defaultConfig.beforeStart();
      } else if (typeof defaultConfig.beforeStart === 'string') {
        require(path.resolve(defaultConfig.configDir, defaultConfig.beforeStart));
      } else {
        throw new Error('config.beforeStart must be a string or function');
      }
    }
  }).then(function() {
    mocha.run(function(failures) {
      driver.quit().then(function() {
        dfd.fulfill(failures);
      });
    });
  });

  return dfd.promise;
}

function runOnce() {
  var specs = defaultConfig.specs;

  if (!specs || specs.length === 0) {
    util.puts('No spec files found');
    process.exit(0);
  }

  return setupSelenium().then(function () {
    return runSpecs().then(cleanupSelenium);
  });
}

exports.runOnce = runOnce;
exports.extendConfig = extendConfig;
<commit_msg>Refactoring. Added global flow exception handler.<commit_after>//test runner, based on protractor

var util = require('util'),
  fs = require('fs'),
  path = require('path'),
  glob = require('glob'),
  webdriver = require('selenium-webdriver'),
  remote = require('selenium-webdriver/remote'),
  chrome = require('selenium-webdriver/chrome'),
  Mocha = require('mocha'),
  Browser = require('./browser');

var server,
  driver,
  mocha,
  config = {
    configDir: './config',

    seleniumServerJar: null,
    seleniumArgs: [],
    seleniumPort: null,
    seleniumAddress: null,

    scriptsTimeout: 10000,

    capabilities: {
      browserName: 'chrome'
    },

    params: {},

    mochaOpts: {
      ui: 'bdd',
      reporter: 'spec'
    }
  };

/**
 * Merge config objects together.
 * @param {Object} into
 * @param {Object} from
 * @return {Object} The 'into' config.
 */
function merge(into, from) {
  for (var key in from) {
    if (into[key] instanceof Object && !(into[key] instanceof Array)) {
      merge(into[key], from[key]);
    } else {
      into[key] = from[key];
    }
  }
  return into;
}

/**
 * Add the options in the parameter config to this runner instance.
 * @param {Object} config
 */
function extendConfig(additionalConfig) {
  // All filepaths should be kept relative to the current config location.
  // This will not affect absolute paths.
  ['seleniumServerJar', 'chromeDriver', 'beforeStart'].forEach(function(name) {
    if (additionalConfig[name] && additionalConfig.configDir && typeof additionalConfig[name] === 'string') {
      additionalConfig[name] = path.resolve(additionalConfig.configDir, additionalConfig[name]);
    }
  });
  merge(config, additionalConfig);
}

/**
 * Sets up the Selenium server and returns a promise once the server is ready to go. After this function is called,
 * config.seleniumAddress and config.capabilities are set up.
 * @return {webdriver.promise.Promise.<string>} A promise which resolves to the value of the selenium address
 *     that will be used.
 */
function setupSelenium() {
  var dfd = webdriver.promise.defer(),
    defaultChromedriver;

  if (config.chromeDriver) {
    if (!fs.existsSync(config.chromeDriver)) {
      if (fs.existsSync(config.chromeDriver + '.exe')) {
        config.chromeDriver += '.exe';
      } else {
        throw new Error('Could not find chromedriver at ' + config.chromeDriver);
      }
    }
  } else {
    defaultChromedriver = path.resolve(__dirname, '../selenium/chromedriver');
    if (fs.existsSync(defaultChromedriver)) {
      config.chromeDriver = defaultChromedriver;
    } else if (fs.existsSync(defaultChromedriver + '.exe')) {
      config.chromeDriver = defaultChromedriver + '.exe';
    }
  }

  if (config.chromeOnly) {
    util.puts('Using ChromeDriver directly...');
    dfd.fulfill(null);
  } else if (config.seleniumAddress) {
    util.puts('Using the selenium server at ' + config.seleniumAddress);
    dfd.fulfill(config.seleniumAddress);
  } else {
    util.puts('Starting selenium standalone server...');

    if (!config.seleniumServerJar) {
      // Try to use the default location.
      var defaultStandalone = path.resolve(__dirname,
        '../selenium/selenium-server-standalone-' + require('../package.json').webdriverVersions.selenium + '.jar');
      if (!fs.existsSync(defaultStandalone)) {
        throw new Error('Unable to start selenium. You must specify either a seleniumAddress, seleniumServerJar, ' +
          'or use webdriver-manager.');
      } else {
        config.seleniumServerJar = defaultStandalone;
      }
    } else if (!fs.existsSync(config.seleniumServerJar)) {
      throw new Error('there\'s no selenium server jar at the specified location. Do you have the correct version?');
    }

    if (config.chromeDriver) {
      config.seleniumArgs.push('-Dwebdriver.chrome.driver=' + config.chromeDriver);
    }

    server = new remote.SeleniumServer(config.seleniumServerJar, {
      args: config.seleniumArgs,
      port: config.seleniumPort
    });

    server.start().then(function(url) {
      util.puts('Selenium standalone server started at ' + url);
      config.seleniumAddress = server.address();
      dfd.fulfill(config.seleniumAddress);
    });
  }

  return dfd.promise;
}

/**
 * Cleans up the driver and selenium server
 * @param {number} failures
 */
function cleanupSelenium(failures) {
  var exitCode = (failures === 0) ? 0 : 1;
  if (server) {
    util.puts('Shutting down selenium standalone server.');
    server.stop().then(function() {
      process.exit(exitCode);
    });
  } else {
    process.exit(exitCode);
  }
}

/**
 * Run the tests.
 * @return {webdriver.promise.Promise} A promise that will resolve when the test run is finished.
 */
function runSpecs() {
  var specs = config.specs,
    resolvedSpecs = [];

  for (var i = 0; i < specs.length; ++i) {
    var matches = glob.sync(specs[i], {cwd: config.configDir});
    if (!matches.length) {
      util.puts('Warning: pattern ' + specs[i] + ' did not match any files.');
    }
    for (var j = 0; j < matches.length; ++j) {
      resolvedSpecs.push(path.resolve(config.configDir, matches[j]));
    }
  }
  if (!resolvedSpecs.length) {
    throw new Error('Spec patterns did not match any files.');
  }

  var dfd = webdriver.promise.defer();

  if (config.chromeOnly) {
    var service = new chrome.ServiceBuilder(config.chromeDriver).build();
    driver = chrome.createDriver(
      new webdriver.Capabilities(config.capabilities), service);
  } else {
    driver = new webdriver.Builder().
      usingServer(config.seleniumAddress).
      withCapabilities(config.capabilities).build();
  }

  driver.manage().timeouts().setScriptTimeout(config.scriptsTimeout);

  var browser = Browser.wrap(driver, config.baseUrl);
  Browser.setInstance(browser);

  browser.params = config.params;
  global.browser = browser;

  global.$ = Browser.$;
  global.element = Browser.element;

  mocha = new Mocha(config.mochaOpts);
  resolvedSpecs.forEach(mocha.addFile.bind(mocha));

  // Mocha doesn't set up the ui until the pre-require event, so
  // wait until then to load mocha-webdriver adapters as well.
  mocha.suite.on('pre-require', function() {
    var adapters = require('selenium-webdriver/testing');

    global.describe = adapters.describe;
    global.describe.skip = global.xdescribe = adapters.xdescribe;
    global.describe.only = global.ddescribe = adapters.describe.only;

    global.after = adapters.after;
    global.afterEach = adapters.afterEach;
    global.before = adapters.before;
    global.beforeEach = adapters.beforeEach;

    global.it = adapters.it;
    global.it.skip = global.xit = adapters.xit;
    global.it.only = global.iit = adapters.it.only;

    global.ignore = adapters.ignore;
  });
  mocha.loadFiles();

  webdriver.promise.controlFlow().on('uncaughtException', function(err) {
    console.log('There was an uncaught exception: ' + err);
    browser.quit();
    cleanupSelenium(err);
  });

  webdriver.promise.controlFlow().execute(function () {
    if (config.beforeStart) {
      if (typeof config.beforeStart === 'function') {
        config.beforeStart();
      } else if (typeof config.beforeStart === 'string') {
        require(path.resolve(config.configDir, config.beforeStart));
      } else {
        throw new Error('config.beforeStart must be a string or function');
      }
    }
  }).then(function() {
    mocha.run(function(failures) {
      driver.quit().then(function() {
        dfd.fulfill(failures);
      });
    });
  });

  return dfd.promise;
}

function runOnce() {
  var specs = config.specs;

  if (!specs || specs.length === 0) {
    util.puts('No spec files found');
    process.exit(0);
  }

  return setupSelenium().then(function () {
    return runSpecs().then(cleanupSelenium);
  });
}

exports.runOnce = runOnce;
exports.extendConfig = extendConfig;
<|endoftext|>


<commit_before>(function() {

    "use strict";

    const app = {};

    app.colorModule = (() => {

        const generateColor = () => '#' + Math.random().toString(16).slice(2, 8);

        const updateColorVar = () => {
            console.log('some stuff');
            document.body.style.setProperty('--color-var', generateColor());
        };

        const init = () => updateColorVar();

        return {
            init: init
        };

    })();

    app.colorModule.init();

})();<commit_msg>format app.js markup<commit_after>(function() {

    "use strict";

    const app = {};

    app.colorModule = (() => {

        const generateColor = () => '#' + Math.random().toString(16).slice(2, 8);
        const updateColorVar = () => document.body.style.setProperty('--color-var', generateColor());
        const init = () => updateColorVar();

        return {
            init: init
        };

    })();

    app.colorModule.init();

})();<|endoftext|>


<commit_before>var extend      = require('util')._extend,
    split       = require('split'),
    eventstream = require('event-stream'),
    stdout      = require('stdout-stream'),
    streamUtil  = require('./stream-util'),
    merge       = require('merge'),
    path        = require('path'),

    helper      = require('./helper'),
    std_formatters  = require('./formatters'),
    std_parsers     = require('./parsers');

var IS_INITIALIZED = false,
    _jfloInstance;

function initialize(jflo) {
    if (IS_INITIALIZED) {
        return;
    }
    try {
        var localConfig = require(path.resolve('jflofile.js'));
        // This will initialize from the local jflofile
        if (typeof localConfig == 'function') {
            localConfig(jflo);
        }

    }
    catch (err) {
        // No output; jflofile is not mandatory
    }

    // Load NPM plugins
    jflo.include([
        'jflo-*',
        './lib/**/jflo-*',
        './node_modules/jflo-*'
    ]);

    // Install built-in formatters
    std_formatters(jflo);
    // Install built-in parsers
    std_parsers(jflo);

    _jfloInstance = jflo;
    IS_INITIALIZED = true;
}

/**
 * CLI executor
 * @param jflo
 * @param config
 */
function run(jflo, config) {

    config = config || {},
        exeName = config.binary_name || 'jflo';

    var yargs = require('yargs'),
        argv = yargs.argv;

    jflo = jflo || _jfloInstance;
    // Initialize plugins
    initialize(jflo);

    // Run
    var plugpath = argv._;
    var flow_class = plugpath.join('.');
    if (!flow_class.length) {
        return helper.print_help(jflo, null, "", config);
    }

    if (config.flow_path) {
        // Only expose the host application's specific flows
        flow_class = (config.flow_path, flow_class).join('.');
    }

    var Flow = jflo.flow(flow_class);

    if (argv.help || !Flow) {
        helper.print_help(jflo, Flow, flow_class, config);
        return;
    }

    if (typeof Flow == 'function' && Flow._meta) {

        var inputs = {},
            outputs = {};

        // First, expand argv.**$ (deserialize JSON values)

        // Read flow defined and user defined configuration defaults {cli,params}
        var config_cli = (jflo.resolveConfig(flow_class, argv.profile || '$default') || {}).cli || {};

        // For each of config.cli.[stdin,stdout,stderr,logger,metrics,ctrl,in.*,out.*,io.*] DO:
        // - Convert url-only strings to {url,parser/formatter,parserArgs/formatterArgs} objects
        // - Next, convert argv.[stdin,stdout,stderr,logger,metrics,ctrl,in.*,out.*,io.*] accordingly
        // - Finally, override config.cli with argv values

        var cli_args = _expandSerializedJSON(argv);

        // Open and bind standard streams.
        // TODO implement jflofile overrides
        // TODO handle object format: { url, parser, parserArgs} { url, formatter, formatterArgs }
        var input = streamUtil.openStream(argv.stdin, 'read') || process.stdin;
        var output = streamUtil.openStream(argv.stdout, 'write') || stdout;
        var logger = streamUtil.openStream(argv.logger, 'write') || process.stderr;
        var metrics = streamUtil.openStream(argv.metrics, 'write') || process.stderr;

        // TODO: Resolve each --in.{name}, --out.{name}, --inout.{name} into streams
        // > config.ins = { n: openStream(v).in }
        // > config.outs = { n: openStream(v).out }
        var aux_ins = argv.in,
            aux_outs = argv.out,
            aux_inouts = argv.io;

        var bind_opts = {
            parser: jflo.parsers.$default,
            formatter: jflo.formatters.$default,
            null: jflo.null
        };

        if (Object(aux_ins) === aux_ins) {
            _bind_aux_streams(aux_ins, inputs, 'read', bind_opts);
        }

        if (Object(aux_outs) === aux_outs) {
            _bind_aux_streams(aux_outs, outputs, 'write', bind_opts);
        }

        if (Object(aux_inouts) === aux_inouts) {
            _bind_aux_streams(aux_inouts, [inputs, outputs], 'duplex', bind_opts);
        }

        jflo.metrics.pipe(jflo.formatters.ndjson("metrics> ")).pipe(metrics);
        jflo.logger.pipe(jflo.formatters.ndjson("log> ")).pipe(logger);

        // Compile flow initialization parameters
        var explicit_parameters = argv.c || {};
        // Add shortcuts; known & non-conflicted --{name} will be accepted instead of --c.{name}
        var known_shortcuts = {};
        for (var name in (Flow._meta.info || {}).params || {}) {
            if ((name in cli_args) && !(name in helper.common_parameters)) {
                known_shortcuts[name] = cli_args[name];
            }
        }
        merge.recursive(explicit_parameters, known_shortcuts);
        // Add nameless parameters
        explicit_parameters._ = argv._.slice(Flow._meta.name.split('.').length);

        // Instantiate flow
        var flow = Flow({
            id: argv.id,
            profile: cli_args.profile, // defaults to $default
            params: explicit_parameters,
            in: inputs,
            out: outputs
        });

        var formatter = (argv.formatter && jflo.formatters[argv.formatter] )
            || jflo.formatters.$default;
        var parser = (argv.parser && jflo.parsers[argv.parser])
            || jflo.parsers.$default;

        // Run it
        input
            .pipe(parser())
            .pipe(flow)
            .pipe(formatter())
            .on('end', function _end() {
                output.end(function() {
                    setTimeout(function() {
                        process.exit(0);
                    }, 1000);
                });
            })
            .pipe(output);
    }

    else if (Flow === Object(Flow)) {
        console.error("Usage: " + [exeName].concat(plugpath).join(' ') + ' '
        + Object.keys(Flow).join('|') + ' [options]');
    }
    else {
        console.error("Usage: " + exeName + " [flow [subflow]] [options]");
        return;
    }
}

// Utility for recursively parsing <name>$: "json_string" into a <name>: <object>
function _expandSerializedJSON(obj) {

    if (Object(obj) !== obj) {
        return obj;
    }
    var expanded = Array.isArray(obj) ? [] : {};
    Object.keys(obj).forEach(function(key) {
        if (obj.hasOwnProperty(key) && (key.indexOf('$') == key.length-1)) {
            try {
                var stdkey = key.slice(0, key.length - 1);
                expanded[stdkey] = merge.recursive(
                    _expandSerializedJSON(obj[stdkey]),
                    obj.key ? JSON.parse(obj[key]) : {});

            } catch (err) {
                throw new Error("Parse error: could not parse JSON in " + obj[key]);
            }
        }
        else {
            expanded[key] = _expandSerializedJSON(obj[key]);
        }
    });
    return expanded;
}

/**
 * Tries to open file/pipe/socket streams as requested (forcing quasi-duplex).
 * Binds them to the provided hashes (pools)
 * @param stream_names
 * @param pools
 * @param mode
 * @private
 */
function _bind_aux_streams(stream_names, pools, mode, opts) {
    pools = [].concat(pools);

    for (var name in stream_names) {
        var raw = streamUtil.openStream(stream_names[name], mode);
        var reader, writer;

        if (raw && raw.readable) {
            reader = opts.parser();
            raw.pipe(reader);
        }
        else {
            reader = opts.null();
        }
        if (raw && raw.writable) {
            writer = opts.formatter(),
            writer.pipe(raw);
        }
        else {
            writer = opts.null();
        }

        var duplex = eventstream.duplex(writer, reader);

        pools.forEach(function(pool) {
            pool[name] = eventstream.duplex(writer, reader);
        });
    }
}

module.exports = {
    run: run,
    initialize: initialize
}<commit_msg>Bugfix<commit_after>var extend      = require('util')._extend,
    split       = require('split'),
    eventstream = require('event-stream'),
    stdout      = require('stdout-stream'),
    streamUtil  = require('./stream-util'),
    merge       = require('merge'),
    path        = require('path'),

    helper      = require('./helper'),
    std_formatters  = require('./formatters'),
    std_parsers     = require('./parsers');

var IS_INITIALIZED = false,
    _jfloInstance;

function initialize(jflo) {
    if (IS_INITIALIZED) {
        return;
    }
    try {
        var localConfig = require(path.resolve('jflofile.js'));
        // This will initialize from the local jflofile
        if (typeof localConfig == 'function') {
            localConfig(jflo);
        }

    }
    catch (err) {
        // No output; jflofile is not mandatory
    }

    // Load NPM plugins
    jflo.include([
        'jflo-*',
        './lib/**/jflo-*',
        './node_modules/jflo-*'
    ]);

    // Install built-in formatters
    std_formatters(jflo);
    // Install built-in parsers
    std_parsers(jflo);

    _jfloInstance = jflo;
    IS_INITIALIZED = true;
}

/**
 * CLI executor
 * @param jflo
 * @param config
 */
function run(jflo, config) {

    config = config || {},
        exeName = config.binary_name || 'jflo';

    var yargs = require('yargs'),
        argv = yargs.argv;

    jflo = jflo || _jfloInstance;
    // Initialize plugins
    initialize(jflo);

    // Run
    var plugpath = argv._;
    var flow_class = plugpath.join('.');
    if (!flow_class.length) {
        return helper.print_help(jflo, null, "", config);
    }

    if (config.flow_path) {
        // Only expose the host application's specific flows
        flow_class = [config.flow_path, flow_class].join('.');
    }

    var Flow = jflo.flow(flow_class);

    if (argv.help || !Flow) {
        helper.print_help(jflo, Flow, flow_class, config);
        return;
    }

    if (typeof Flow == 'function' && Flow._meta) {

        var inputs = {},
            outputs = {};

        // First, expand argv.**$ (deserialize JSON values)

        // Read flow defined and user defined configuration defaults {cli,params}
        var config_cli = (jflo.resolveConfig(flow_class, argv.profile || '$default') || {}).cli || {};

        // For each of config.cli.[stdin,stdout,stderr,logger,metrics,ctrl,in.*,out.*,io.*] DO:
        // - Convert url-only strings to {url,parser/formatter,parserArgs/formatterArgs} objects
        // - Next, convert argv.[stdin,stdout,stderr,logger,metrics,ctrl,in.*,out.*,io.*] accordingly
        // - Finally, override config.cli with argv values

        var cli_args = _expandSerializedJSON(argv);

        // Open and bind standard streams.
        // TODO implement jflofile overrides
        // TODO handle object format: { url, parser, parserArgs} { url, formatter, formatterArgs }
        var input = streamUtil.openStream(argv.stdin, 'read') || process.stdin;
        var output = streamUtil.openStream(argv.stdout, 'write') || stdout;
        var logger = streamUtil.openStream(argv.logger, 'write') || process.stderr;
        var metrics = streamUtil.openStream(argv.metrics, 'write') || process.stderr;

        // TODO: Resolve each --in.{name}, --out.{name}, --inout.{name} into streams
        // > config.ins = { n: openStream(v).in }
        // > config.outs = { n: openStream(v).out }
        var aux_ins = argv.in,
            aux_outs = argv.out,
            aux_inouts = argv.io;

        var bind_opts = {
            parser: jflo.parsers.$default,
            formatter: jflo.formatters.$default,
            null: jflo.null
        };

        if (Object(aux_ins) === aux_ins) {
            _bind_aux_streams(aux_ins, inputs, 'read', bind_opts);
        }

        if (Object(aux_outs) === aux_outs) {
            _bind_aux_streams(aux_outs, outputs, 'write', bind_opts);
        }

        if (Object(aux_inouts) === aux_inouts) {
            _bind_aux_streams(aux_inouts, [inputs, outputs], 'duplex', bind_opts);
        }

        jflo.metrics.pipe(jflo.formatters.ndjson("metrics> ")).pipe(metrics);
        jflo.logger.pipe(jflo.formatters.ndjson("log> ")).pipe(logger);

        // Compile flow initialization parameters
        var explicit_parameters = argv.c || {};
        // Add shortcuts; known & non-conflicted --{name} will be accepted instead of --c.{name}
        var known_shortcuts = {};
        for (var name in (Flow._meta.info || {}).params || {}) {
            if ((name in cli_args) && !(name in helper.common_parameters)) {
                known_shortcuts[name] = cli_args[name];
            }
        }
        merge.recursive(explicit_parameters, known_shortcuts);
        // Add nameless parameters
        explicit_parameters._ = argv._.slice(Flow._meta.name.split('.').length);

        // Instantiate flow
        var flow = Flow({
            id: argv.id,
            profile: cli_args.profile, // defaults to $default
            params: explicit_parameters,
            in: inputs,
            out: outputs
        });

        var formatter = (argv.formatter && jflo.formatters[argv.formatter] )
            || jflo.formatters.$default;
        var parser = (argv.parser && jflo.parsers[argv.parser])
            || jflo.parsers.$default;

        // Run it
        input
            .pipe(parser())
            .pipe(flow)
            .pipe(formatter())
            .on('end', function _end() {
                output.end(function() {
                    setTimeout(function() {
                        process.exit(0);
                    }, 1000);
                });
            })
            .pipe(output);
    }

    else if (Flow === Object(Flow)) {
        console.error("Usage: " + [exeName].concat(plugpath).join(' ') + ' '
        + Object.keys(Flow).join('|') + ' [options]');
    }
    else {
        console.error("Usage: " + exeName + " [flow [subflow]] [options]");
        return;
    }
}

// Utility for recursively parsing <name>$: "json_string" into a <name>: <object>
function _expandSerializedJSON(obj) {

    if (Object(obj) !== obj) {
        return obj;
    }
    var expanded = Array.isArray(obj) ? [] : {};
    Object.keys(obj).forEach(function(key) {
        if (obj.hasOwnProperty(key) && (key.indexOf('$') == key.length-1)) {
            try {
                var stdkey = key.slice(0, key.length - 1);
                expanded[stdkey] = merge.recursive(
                    _expandSerializedJSON(obj[stdkey]),
                    obj.key ? JSON.parse(obj[key]) : {});

            } catch (err) {
                throw new Error("Parse error: could not parse JSON in " + obj[key]);
            }
        }
        else {
            expanded[key] = _expandSerializedJSON(obj[key]);
        }
    });
    return expanded;
}

/**
 * Tries to open file/pipe/socket streams as requested (forcing quasi-duplex).
 * Binds them to the provided hashes (pools)
 * @param stream_names
 * @param pools
 * @param mode
 * @private
 */
function _bind_aux_streams(stream_names, pools, mode, opts) {
    pools = [].concat(pools);

    for (var name in stream_names) {
        var raw = streamUtil.openStream(stream_names[name], mode);
        var reader, writer;

        if (raw && raw.readable) {
            reader = opts.parser();
            raw.pipe(reader);
        }
        else {
            reader = opts.null();
        }
        if (raw && raw.writable) {
            writer = opts.formatter(),
            writer.pipe(raw);
        }
        else {
            writer = opts.null();
        }

        var duplex = eventstream.duplex(writer, reader);

        pools.forEach(function(pool) {
            pool[name] = eventstream.duplex(writer, reader);
        });
    }
}

module.exports = {
    run: run,
    initialize: initialize
}<|endoftext|>


<commit_before>/*global define, amplify*/
define([
    'jquery',
    'require',
    'underscore',
    'loglevel',
    'fx-m-c/config/errors',
    'fx-m-c/config/events',
    'fx-m-c/config/config',
    'fx-m-c/config/config-default',
    'fenix-ui-map',
    'fx-common/pivotator/start',
    'amplify'
], function ($, require, _, log, ERR, EVT, C, CD,
    FMMap,
    Pivotator
    ) {

    'use strict';

    function MapCreator(o) {
        log.info("FENIX MapCreator");
        log.info(o);

        $.extend(true, this, CD, C, {initial: o});

        this._parseInput(o);

        this.fenix_ui_mapConfig = o.fenix_ui_map;

        var valid = this._validateInput();

        if (valid === true) {

            this._initVariables();

            this._bindEventListeners();

            this._renderMap();

            return this;

        } else {
            log.error("Impossible to create MapCreator");
            log.error(valid)
        }

    }

    // API

    /**
     * pub/sub
     * @return {Object} MapCreator instance
     */
    MapCreator.prototype.on = function (channel, fn) {
        if (!this.channels[channel]) {
            this.channels[channel] = [];
        }
        this.channels[channel].push({context: this, callback: fn});
        return this;
    };

    /**
     * Add a base layer to map
     */
    MapCreator.prototype.addBaseLayer = function (layer) {

        if (this.status.ready !== true) {
            return;
        }

        return this._addBaseLayer(layer);
    };

    /**
     * Add a layer to map
     */
    MapCreator.prototype.addLayer = function (model, layerOptions, modelOptions) {

        if (this.status.ready !== true) {
            return;
        }

        return this._addLayer(model, layerOptions, modelOptions);
    };

    /**
     * Remove a layer from map
     */
    MapCreator.prototype.removeLayer = function (layer) {

        if (this.status.ready !== true) {
            return;
        }

        return this._removeLayer(layer);
    };

    /**
     * Invalidate map size
     */
    MapCreator.prototype.invalidateSize = function () {

        if (this.status.ready !== true) {
            return;
        }

        return this._invalidateSize();
    };

    // end API

    MapCreator.prototype._parseInput = function () {

        this.id = this.initial.id;
        this.$el = $(this.initial.el);
        this.model = this.initial.model;

        //pivotator config
        var pc = {};

        pc.aggregationFn = this.initial.aggregationFn;

        pc.aggregations = this.initial.aggregations || [];
        pc.hidden = this.initial.hidden || [];
        pc.columns = this.initial.x;
        pc.values = this.initial.y || ["value"];
        pc.rows = this.initial.series;

        pc.formatter = this.initial.formatter || "value";
        pc.valueOutputType = this.initial.valueOutputType;
        pc.showRowHeaders = this.initial.showRowHeaders || false;
        pc.decimals = this.initial.decimals || 2;

        pc.showCode = this.initial.showCode || false;
        pc.showFlag = this.initial.showFlag || false;
        pc.showUnit = this.initial.showUnit || false;

        // add more pivotator config

        this.pivotatorConfig = pc;

    };

    MapCreator.prototype._validateInput = function () {

        var valid = true,
            errors = [];

        //set MapCreator id
        if (!this.id) {

            window.fx_map_id >= 0 ? window.fx_map_id++ : window.fx_map_id = 0;
            this.id = String(window.fx_map_id);
            log.warn("Impossible to find MapCreator id. Set auto id to: " + this.id);
        }

        //Check if $el exist
        if (this.$el.length === 0) {
            errors.push({code: ERR.MISSING_CONTAINER});
            log.warn("Impossible to find box container");
        }

        return errors.length > 0 ? errors : valid;

    };

    MapCreator.prototype._initVariables = function () {

        //pub/sub
        this.channels = {};

        this.status = {};
        this.status.ready = false;

        this.pivotator = new Pivotator();

    };

    MapCreator.prototype._bindEventListeners = function () {

        amplify.subscribe(this._getEventName(EVT.WINDOW_RESIZE), this, this.invalidateSize);
    };

    MapCreator.prototype._renderMap = function () {

        //var model = this.pivotator.pivot(this.model, this.pivotatorConfig);
        var model = this.model;

        var config = $.extend(true, {}, {
            el: this.$el,
            model: model,
            lang: this.lang
        });

        this.map = new FM.Map(this.$el, this.fenix_ui_mapConfig);

        this.status.ready = true;  //To be set on map ready event

        this._trigger("ready");

    };

    // map methods

    MapCreator.prototype._removeLayer = function (layer) {
        return this.map.removeLayer(layer);
    };

    MapCreator.prototype._addBaseLayer = function (layer) {

        return this.map.addBaseLayer(layer);
    };

    MapCreator.prototype._addLayer = function (model, layerOptions, modelOptions) {
        return this.map.addLayer(model, layerOptions, modelOptions);
    };

    MapCreator.prototype._invalidateSize = function () {

        return this.map.invalidateSize();

    };

    // utils

    MapCreator.prototype._trigger = function (channel) {

        if (!this.channels[channel]) {
            return false;
        }
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, l = this.channels[channel].length; i < l; i++) {
            var subscription = this.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }

        return this;
    };

    MapCreator.prototype._getEventName = function (evt) {

        return this.id.concat(evt);

    };

    // disposition

    MapCreator.prototype._unbindEventListeners = function () {

        amplify.unsubscribe(this._getEventName(EVT.WINDOW_RESIZE), this.invalidateSize);
    };

    MapCreator.prototype.dispose = function () {

        //unbind event listeners
        this._unbindEventListeners();

        if (this.status.ready === true) {
            this.map.dispose();
        }

    };

    return MapCreator;
});<commit_msg>adde map istance<commit_after>/*global define, amplify*/
define([
    'jquery',
    'require',
    'underscore',
    'loglevel',
    'fx-m-c/config/errors',
    'fx-m-c/config/events',
    'fx-m-c/config/config',
    'fx-m-c/config/config-default',
    'fenix-ui-map',
    'fx-common/pivotator/start',
    'amplify'
], function ($, require, _, log, ERR, EVT, C, CD,
    FMMap,
    Pivotator
    ) {

    'use strict';

    function MapCreator(o) {
        log.info("FENIX MapCreator");
        log.info(o);

        $.extend(true, this, CD, C, {initial: o});

        this._parseInput(o);

        this.fenix_ui_mapConfig = o.fenix_ui_map;

        var valid = this._validateInput();

        if (valid === true) {

            this._initVariables();

            this._bindEventListeners();

            this._renderMap();

            return this;

        } else {
            log.error("Impossible to create MapCreator");
            log.error(valid)
        }

    }

    // API

    /**
     * pub/sub
     * @return {Object} MapCreator instance
     */
    MapCreator.prototype.on = function (channel, fn) {
        if (!this.channels[channel]) {
            this.channels[channel] = [];
        }
        this.channels[channel].push({context: this, callback: fn});
        return this;
    };

    /**
     * Add a base layer to map
     */
    MapCreator.prototype.addBaseLayer = function (layer) {

        if (this.status.ready !== true) {
            return;
        }

        return this._addBaseLayer(layer);
    };

    /**
     * Add a layer to map
     */
    MapCreator.prototype.addLayer = function (model, layerOptions, modelOptions) {

        if (this.status.ready !== true) {
            return;
        }

        return this._addLayer(model, layerOptions, modelOptions);
    };

    /**
     * Remove a layer from map
     */
    MapCreator.prototype.removeLayer = function (layer) {

        if (this.status.ready !== true) {
            return;
        }

        return this._removeLayer(layer);
    };

    /**
     * Invalidate map size
     */
    MapCreator.prototype.invalidateSize = function () {

        if (this.status.ready !== true) {
            return;
        }

        return this._invalidateSize();
    };

    // end API

    MapCreator.prototype._parseInput = function () {

        this.id = this.initial.id;
        this.$el = $(this.initial.el);
        this.model = this.initial.model;

        //pivotator config
        var pc = {};

        pc.aggregationFn = this.initial.aggregationFn;

        pc.aggregations = this.initial.aggregations || [];
        pc.hidden = this.initial.hidden || [];
        pc.columns = this.initial.x;
        pc.values = this.initial.y || ["value"];
        pc.rows = this.initial.series;

        pc.formatter = this.initial.formatter || "value";
        pc.valueOutputType = this.initial.valueOutputType;
        pc.showRowHeaders = this.initial.showRowHeaders || false;
        pc.decimals = this.initial.decimals || 2;

        pc.showCode = this.initial.showCode || false;
        pc.showFlag = this.initial.showFlag || false;
        pc.showUnit = this.initial.showUnit || false;

        // add more pivotator config

        this.pivotatorConfig = pc;

    };

    MapCreator.prototype._validateInput = function () {

        var valid = true,
            errors = [];

        //set MapCreator id
        if (!this.id) {

            window.fx_map_id >= 0 ? window.fx_map_id++ : window.fx_map_id = 0;
            this.id = String(window.fx_map_id);
            log.warn("Impossible to find MapCreator id. Set auto id to: " + this.id);
        }

        //Check if $el exist
        if (this.$el.length === 0) {
            errors.push({code: ERR.MISSING_CONTAINER});
            log.warn("Impossible to find box container");
        }

        return errors.length > 0 ? errors : valid;

    };

    MapCreator.prototype._initVariables = function () {

        //pub/sub
        this.channels = {};

        this.status = {};
        this.status.ready = false;

        this.pivotator = new Pivotator();

    };

    MapCreator.prototype._bindEventListeners = function () {

        amplify.subscribe(this._getEventName(EVT.WINDOW_RESIZE), this, this.invalidateSize);
    };

    MapCreator.prototype._renderMap = function () {

        //var model = this.pivotator.pivot(this.model, this.pivotatorConfig);
        var model = this.model;

        var config = $.extend(true, {}, {
            el: this.$el,
            model: model,
            lang: this.lang
        });

        this.map = new FM.Map(this.$el, this.fenix_ui_mapConfig);
        this.map.createMap();

        this.status.ready = true;  //To be set on map ready event

        this._trigger("ready");

    };

    // map methods

    MapCreator.prototype._removeLayer = function (layer) {
        return this.map.removeLayer(layer);
    };

    MapCreator.prototype._addBaseLayer = function (layer) {

        return this.map.addBaseLayer(layer);
    };

    MapCreator.prototype._addLayer = function (model, layerOptions, modelOptions) {
        return this.map.addLayer(model, layerOptions, modelOptions);
    };

    MapCreator.prototype._invalidateSize = function () {

        return this.map.invalidateSize();

    };

    // utils

    MapCreator.prototype._trigger = function (channel) {

        if (!this.channels[channel]) {
            return false;
        }
        var args = Array.prototype.slice.call(arguments, 1);
        for (var i = 0, l = this.channels[channel].length; i < l; i++) {
            var subscription = this.channels[channel][i];
            subscription.callback.apply(subscription.context, args);
        }

        return this;
    };

    MapCreator.prototype._getEventName = function (evt) {

        return this.id.concat(evt);

    };

    // disposition

    MapCreator.prototype._unbindEventListeners = function () {

        amplify.unsubscribe(this._getEventName(EVT.WINDOW_RESIZE), this.invalidateSize);
    };

    MapCreator.prototype.dispose = function () {

        //unbind event listeners
        this._unbindEventListeners();

        if (this.status.ready === true) {
            this.map.dispose();
        }

    };

    return MapCreator;
});<|endoftext|>


<commit_before>'use strict';

/**
 * Testing library: runner for tests.
 * (C) 2013 Alex Fernández.
 */

// requires
var Log = require('log');

// globals
var log = new Log('info');
var next;

// constants
var GREEN = '\u001b[32m';
var RED = '\u001b[1;31m';
var PURPLE = '\u001b[1;35m';
var BLACK = '\u001b[0m';
var SUCCESS_START = GREEN + '✓ ';
var END = BLACK;
var FAILURE_START = RED + '✕ ';
var UNKNOWN_START = PURPLE + '? ';
var INVALID_START = PURPLE + '??? ';
var SEPARATOR = ': ';

/**
 * A test result.
 */
var TestResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.message = null;
	var finished = false;

	/**
	 * Callback for the test result.
	 * Can only be called once.
	 */
	self.callback = function(error, result)
	{
		if (error)
		{
			// only report the first failure
			if (!self.failure)
			{
				fail(error);
			}
		}
		else
		{
			if (result && result.failure)
			{
				return fail(result);
			}
			return succeed(result);
		}
	};

	/**
	 * Report a failure.
	 */
	function fail(message)
	{
		self.failure = true;
		self.success = false;
		self.message = message;
		console.log(FAILURE_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Report a success.
	 */
	function succeed(message)
	{
		if (finished)
		{
			if (self.success)
			{
				message = 'Duplicated call to callback';
			}
			// only one success allowed
			return fail(message);
		}
		self.success = true;
		self.message = message;
		console.log(SUCCESS_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		var message = self.key;
		if (self.message)
		{
			message += SEPARATOR + self.message;
		}
		return getPrintable(self, message, indent);
	};
};

/**
 * A result that contains other results.
 */
var CompositeResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.failures = 0;
	self.results = {};

	/**
	 * Add a sub-result.
	 */
	self.add = function(result)
	{
		self.results[result.key] = result;
		if (result.success && !self.failure)
		{
			self.success = true;
		}
		if (result.failure)
		{
			self.success = false;
			self.failure = true;
		}
	};

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		indent = indent || 0;
		var message = getPrintable(self, self.key + SEPARATOR, indent) + '\n';
		message += getIndented(indent) + '{\n';
		for (var key in self.results)
		{
			var result = self.results[key];
			message += result.toString(indent + 1) + ',\n';
		}
		message += getIndented(indent) + '}';
		return message;
	};

	/**
	 * Return a shortened representation.
	 */
	self.getSummary = function()
	{
		return getPrintable(self, self.key, 0);
	};
};

/**
 * Get the printable representation for a result, like this:
 * START message END.
 * An optional indent is applied as an equivalent number of tabs.
 */
function getPrintable(result, message, indent)
{
	var start;
	if (!result.failure && !result.success)
	{
		start = UNKNOWN_START;
	}
	else if (result.success && result.failure)
	{
		start = INVALID_START;
	}
	else if (result.success)
	{
		start = SUCCESS_START;
	}
	else
	{
		start = FAILURE_START;
	}
	return getIndented(indent) + start + message + END;
}

/**
 * Get an indented string, with an equivalent number of tabs.
 */
function getIndented(indent)
{
	var indented = '';
	if (indent)
	{
		for (var i = 0; i < indent; i++)
		{
			indented += '\t';
		}
	}
	return indented;
}

/**
 * Run a series of functions sequentially. Parameters:
 *	- param: an indexed object with functions, or with nested indexed objects.
 *	- callback: a function(error, result) to pass an indexed object with the results.
 */
exports.run = function(param, callback)
{
	var series = clone(param);
	// uncaught exceptions
	process.on('uncaughtException', function(error)
	{
		uncaughtException(error);
	});
	var runner = new Runner('success', series);
	runner.runAll(function(error, result)
	{
		if (result.failure)
		{
			result.key = 'failure';
		}
		callback(error, result);
	});
};

/**
 * Process an uncaught exception.
 */
function uncaughtException(error)
{
	if (!next)
	{
		return log.error('Exception without next text');
	}
	next('Uncaught exception: ' + error.stack);
}

/**
 * A runner for a series of tests.
 */
var Runner = function(key, series)
{
	// self-reference
	var self = this;

	// attributes
	self.result = new CompositeResult(key);
	var finished = false;

	/**
	 * Run all functions in the series, one by one.
	 */
	self.runAll = function(callback)
	{
		runOne(function(error, result)
		{
			console.log('Finished test: %s', result.getSummary());
			if (finished)
			{
				return;
			}
			finished = true;
			callback(error, result);
		});
	};

	/**
	 * Run one function in the given series, go to the next.
	 */
	function runOne(callback)
	{
		if (isEmpty(series))
		{
			return callback(null, self.result);
		}
		for (var key in series)
		{
			var value = series[key];
			if (typeof value == 'object')
			{
				var runner = new Runner(key, value);
				runner.runAll(function(error)
				{
					if (error)
					{
						log.error('Could not run all functions');
						return;
					}
					self.result.add(runner.result);
					deleteAndRunNext(key, callback);
				});
			}
			else if (typeof value == 'function')
			{
				var testKey = key;
				if (isNumber(key) && value.name)
				{
					testKey = value.name;
				}
				var subResult = new TestResult(testKey);
				// it is a function to run
				next = getNext(key, subResult, callback);
				value(next);
			}
			else
			{
				log.error('Invalid value %s', value);
				self.result.callback('Key %s has an invalid value %s');
				deleteAndRunNext(key, callback);
			}
			// only the first element in series is used;
			// the rest are called by recursion in deleteAndRunNext()
			return;
		}
	}

	/**
	 * Get a function to call after a test.
	 */
	function getNext(key, testResult, callback)
	{
		return function(error, result)
		{
			testResult.callback(error, result);
			self.result.add(testResult);
			deleteAndRunNext(key, callback);
		};
	}

	/**
	 * Delete the current function, run the next.
	 */
	function deleteAndRunNext(key, callback)
	{
		if (!(key in series))
		{
			// already run
			return;
		}
		delete series[key];
		return process.nextTick(function()
		{
			runOne(callback);
		});
	}
};

/**
 * Test to run some functions.
 */
function testRun()
{
	var series = {
		a: function(callback) {
			callback(null, 'a');
		},
		b: {
			e: function(callback) {
				callback('e');
			},
			c: function(callback) {
				callback(null, 'c');
			},
			f: function() {
				throw new Error('exception');
			},
		},
		g: [function(callback) {
			callback(null, 'g0');
		}, function two(callback) {
			callback(null, 'g1');
		}],
	};
	exports.run(series, function(error, result)
	{
		console.assert(result.failure, 'Root should be failure');
		console.assert(result.results.a, 'Should have result for a');
		console.assert(result.results.a.success, 'Should have success for a');
		console.assert(result.results.a.message == 'a', 'Should have an a for a');
		console.assert(result.results.b, 'Should have result for b');
		console.assert(result.results.b.failure, 'Should have failure for b');
		console.assert(result.results.b.results.c, 'Should have result for b.c');
		console.assert(result.results.b.results.c.success, 'Should have success for b.c');
		console.assert(result.results.b.results.c.message == 'c', 'Should have a c for b.c');
		console.assert(result.results.b.results.e, 'Should have result for b.e');
		console.assert(result.results.b.results.e.failure, 'Should have failure for b.e');
		console.assert(result.results.b.results.e.message == 'e', 'Should have a e for b.e');
		console.assert(result.results.g, 'Should have result for g');
		console.assert(result.results.g.success, 'Should have success for g');
		console.assert(result.results.g.results[0], 'Should have result for g[0]');
		console.assert(result.results.g.results[0].success, 'Should have success for g[0]');
		console.assert(result.results.g.results[0].message == 'g0', 'Should have g0 for g[0]');
		console.assert(result.results.g.results.two, 'Should have result for two');
		console.assert(result.results.g.results.two.success, 'Should have success for two');
		console.assert(result.results.g.results.two.message == 'g1', 'Should have g1 for two');
		log.info('Test run successful with 2 failures: %s', result);
	});
}

/**
 * Clone a series of functions. Performs a sanity check.
 */
function clone(series)
{
	if (typeof series != 'object')
	{
		log.error('Invalid series %s', JSON.stringify(series));
		return;
	}
	var copy = {};
	for (var key in series)
	{
		var value = series[key];
		if (typeof value == 'function')
		{
			copy[key] = value;
		}
		else
		{
			copy[key] = clone(value);
		}
	}
	return copy;
}

/**
 * Test the clone function.
 */
function testClone()
{
	var original = {
		a: function() {},
		b: {
			c: function() {},
		},
	};
	var cloned = clone(original);
	console.assert(cloned.a, 'Cloned object should have function property');
	console.assert(typeof cloned.a == 'function', 'Cloned object should have function');
	console.assert(cloned.b, 'Cloned object should have object property');
	console.assert(typeof cloned.b == 'object', 'Cloned object should have object');
	console.assert(cloned.b.c, 'Cloned object should have sub-object property');
	console.assert(typeof cloned.b.c == 'function', 'Cloned object should have sub-object');
}

/**
 * Find out if the object is empty.
 */
function isEmpty(object)
{
	for (var key in object)
	{
		if (object[key])
		{
			return false;
		}
	}
	return true;
}

/**
 * Test the empty function.
 */
function testEmpty()
{
	console.assert(isEmpty({}), 'Empty object should be empty');
	console.assert(!isEmpty({a: 'a'}), 'Not empty object is empty');
	console.assert(isEmpty([]), 'Empty array should be empty');
	console.assert(!isEmpty(['a']), 'Not empty array is empty');
}

/**
 * Find out if the argument is a number.
 * http://stackoverflow.com/a/1830844/978796
 */
function isNumber(n)
{
	return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * Run all module tests.
 * Cannot use testing since it is not defined yet.
 */
function test()
{
	testEmpty();
	testClone();
	testRun();
}

// run tests if invoked directly
if (__filename == process.argv[1])
{
	test();
}

<commit_msg>Clean up listeners after running tests.<commit_after>'use strict';

/**
 * Testing library: runner for tests.
 * (C) 2013 Alex Fernández.
 */

// requires
var Log = require('log');

// globals
var log = new Log('info');
var next;

// constants
var GREEN = '\u001b[32m';
var RED = '\u001b[1;31m';
var PURPLE = '\u001b[1;35m';
var BLACK = '\u001b[0m';
var SUCCESS_START = GREEN + '✓ ';
var END = BLACK;
var FAILURE_START = RED + '✕ ';
var UNKNOWN_START = PURPLE + '? ';
var INVALID_START = PURPLE + '??? ';
var SEPARATOR = ': ';

/**
 * A test result.
 */
var TestResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.message = null;
	var finished = false;

	/**
	 * Callback for the test result.
	 * Can only be called once.
	 */
	self.callback = function(error, result)
	{
		if (error)
		{
			// only report the first failure
			if (!self.failure)
			{
				fail(error);
			}
		}
		else
		{
			if (result && result.failure)
			{
				return fail(result);
			}
			return succeed(result);
		}
	};

	/**
	 * Report a failure.
	 */
	function fail(message)
	{
		self.failure = true;
		self.success = false;
		self.message = message;
		console.log(FAILURE_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Report a success.
	 */
	function succeed(message)
	{
		if (finished)
		{
			if (self.success)
			{
				message = 'Duplicated call to callback';
			}
			// only one success allowed
			return fail(message);
		}
		self.success = true;
		self.message = message;
		console.log(SUCCESS_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		var message = self.key;
		if (self.message)
		{
			message += SEPARATOR + self.message;
		}
		return getPrintable(self, message, indent);
	};
};

/**
 * A result that contains other results.
 */
var CompositeResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.failures = 0;
	self.results = {};

	/**
	 * Add a sub-result.
	 */
	self.add = function(result)
	{
		self.results[result.key] = result;
		if (result.success && !self.failure)
		{
			self.success = true;
		}
		if (result.failure)
		{
			self.success = false;
			self.failure = true;
		}
	};

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		indent = indent || 0;
		var message = getPrintable(self, self.key + SEPARATOR, indent) + '\n';
		message += getIndented(indent) + '{\n';
		for (var key in self.results)
		{
			var result = self.results[key];
			message += result.toString(indent + 1) + ',\n';
		}
		message += getIndented(indent) + '}';
		return message;
	};

	/**
	 * Return a shortened representation.
	 */
	self.getSummary = function()
	{
		return getPrintable(self, self.key, 0);
	};
};

/**
 * Get the printable representation for a result, like this:
 * START message END.
 * An optional indent is applied as an equivalent number of tabs.
 */
function getPrintable(result, message, indent)
{
	var start;
	if (!result.failure && !result.success)
	{
		start = UNKNOWN_START;
	}
	else if (result.success && result.failure)
	{
		start = INVALID_START;
	}
	else if (result.success)
	{
		start = SUCCESS_START;
	}
	else
	{
		start = FAILURE_START;
	}
	return getIndented(indent) + start + message + END;
}

/**
 * Get an indented string, with an equivalent number of tabs.
 */
function getIndented(indent)
{
	var indented = '';
	if (indent)
	{
		for (var i = 0; i < indent; i++)
		{
			indented += '\t';
		}
	}
	return indented;
}

/**
 * Run a series of functions sequentially. Parameters:
 *	- param: an indexed object with functions, or with nested indexed objects.
 *	- callback: a function(error, result) to pass an indexed object with the results.
 */
exports.run = function(param, callback)
{
	var series = clone(param);
	// uncaught exceptions
	process.on('uncaughtException', uncaughtException);
	var runner = new Runner('success', series);
	runner.runAll(function(error, result)
	{
		if (result.failure)
		{
			result.key = 'failure';
		}
		callback(error, result);
		process.removeListener('uncaughtException', uncaughtException);
	});
};

/**
 * Process an uncaught exception.
 */
function uncaughtException(error)
{
	if (!next)
	{
		return log.error('Exception without next text');
	}
	next('Uncaught exception: ' + error.stack);
}

/**
 * A runner for a series of tests.
 */
var Runner = function(key, series)
{
	// self-reference
	var self = this;

	// attributes
	self.result = new CompositeResult(key);
	var finished = false;

	/**
	 * Run all functions in the series, one by one.
	 */
	self.runAll = function(callback)
	{
		runOne(function(error, result)
		{
			console.log('Finished test: %s', result.getSummary());
			if (finished)
			{
				return;
			}
			finished = true;
			callback(error, result);
		});
	};

	/**
	 * Run one function in the given series, go to the next.
	 */
	function runOne(callback)
	{
		if (isEmpty(series))
		{
			return callback(null, self.result);
		}
		for (var key in series)
		{
			var value = series[key];
			if (typeof value == 'object')
			{
				var runner = new Runner(key, value);
				runner.runAll(function(error)
				{
					if (error)
					{
						log.error('Could not run all functions');
						return;
					}
					self.result.add(runner.result);
					deleteAndRunNext(key, callback);
				});
			}
			else if (typeof value == 'function')
			{
				var testKey = key;
				if (isNumber(key) && value.name)
				{
					testKey = value.name;
				}
				var subResult = new TestResult(testKey);
				// it is a function to run
				next = getNext(key, subResult, callback);
				value(next);
			}
			else
			{
				log.error('Invalid value %s', value);
				self.result.callback('Key %s has an invalid value %s');
				deleteAndRunNext(key, callback);
			}
			// only the first element in series is used;
			// the rest are called by recursion in deleteAndRunNext()
			return;
		}
	}

	/**
	 * Get a function to call after a test.
	 */
	function getNext(key, testResult, callback)
	{
		return function(error, result)
		{
			testResult.callback(error, result);
			self.result.add(testResult);
			deleteAndRunNext(key, callback);
		};
	}

	/**
	 * Delete the current function, run the next.
	 */
	function deleteAndRunNext(key, callback)
	{
		if (!(key in series))
		{
			// already run
			return;
		}
		delete series[key];
		return process.nextTick(function()
		{
			runOne(callback);
		});
	}
};

/**
 * Test to run some functions.
 */
function testRun()
{
	var series = {
		a: function(callback) {
			callback(null, 'a');
		},
		b: {
			e: function(callback) {
				callback('e');
			},
			c: function(callback) {
				callback(null, 'c');
			},
			f: function() {
				throw new Error('exception');
			},
		},
		g: [function(callback) {
			callback(null, 'g0');
		}, function two(callback) {
			callback(null, 'g1');
		}],
	};
	exports.run(series, function(error, result)
	{
		console.assert(result.failure, 'Root should be failure');
		console.assert(result.results.a, 'Should have result for a');
		console.assert(result.results.a.success, 'Should have success for a');
		console.assert(result.results.a.message == 'a', 'Should have an a for a');
		console.assert(result.results.b, 'Should have result for b');
		console.assert(result.results.b.failure, 'Should have failure for b');
		console.assert(result.results.b.results.c, 'Should have result for b.c');
		console.assert(result.results.b.results.c.success, 'Should have success for b.c');
		console.assert(result.results.b.results.c.message == 'c', 'Should have a c for b.c');
		console.assert(result.results.b.results.e, 'Should have result for b.e');
		console.assert(result.results.b.results.e.failure, 'Should have failure for b.e');
		console.assert(result.results.b.results.e.message == 'e', 'Should have a e for b.e');
		console.assert(result.results.g, 'Should have result for g');
		console.assert(result.results.g.success, 'Should have success for g');
		console.assert(result.results.g.results[0], 'Should have result for g[0]');
		console.assert(result.results.g.results[0].success, 'Should have success for g[0]');
		console.assert(result.results.g.results[0].message == 'g0', 'Should have g0 for g[0]');
		console.assert(result.results.g.results.two, 'Should have result for two');
		console.assert(result.results.g.results.two.success, 'Should have success for two');
		console.assert(result.results.g.results.two.message == 'g1', 'Should have g1 for two');
		log.info('Test run successful with 2 failures: %s', result);
	});
}

/**
 * Clone a series of functions. Performs a sanity check.
 */
function clone(series)
{
	if (typeof series != 'object')
	{
		log.error('Invalid series %s', JSON.stringify(series));
		return;
	}
	var copy = {};
	for (var key in series)
	{
		var value = series[key];
		if (typeof value == 'function')
		{
			copy[key] = value;
		}
		else
		{
			copy[key] = clone(value);
		}
	}
	return copy;
}

/**
 * Test the clone function.
 */
function testClone()
{
	var original = {
		a: function() {},
		b: {
			c: function() {},
		},
	};
	var cloned = clone(original);
	console.assert(cloned.a, 'Cloned object should have function property');
	console.assert(typeof cloned.a == 'function', 'Cloned object should have function');
	console.assert(cloned.b, 'Cloned object should have object property');
	console.assert(typeof cloned.b == 'object', 'Cloned object should have object');
	console.assert(cloned.b.c, 'Cloned object should have sub-object property');
	console.assert(typeof cloned.b.c == 'function', 'Cloned object should have sub-object');
}

/**
 * Find out if the object is empty.
 */
function isEmpty(object)
{
	for (var key in object)
	{
		if (object[key])
		{
			return false;
		}
	}
	return true;
}

/**
 * Test the empty function.
 */
function testEmpty()
{
	console.assert(isEmpty({}), 'Empty object should be empty');
	console.assert(!isEmpty({a: 'a'}), 'Not empty object is empty');
	console.assert(isEmpty([]), 'Empty array should be empty');
	console.assert(!isEmpty(['a']), 'Not empty array is empty');
}

/**
 * Find out if the argument is a number.
 * http://stackoverflow.com/a/1830844/978796
 */
function isNumber(n)
{
	return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * Run all module tests.
 * Cannot use testing since it is not defined yet.
 */
function test()
{
	testEmpty();
	testClone();
	testRun();
}

// run tests if invoked directly
if (__filename == process.argv[1])
{
	test();
}

<|endoftext|>


<commit_before>'use strict';

/**
 * Testing library: runner for tests.
 * (C) 2013 Alex Fernández.
 */

// requires
var Log = require('log');

// globals
var log = new Log('info');
var next;

// constants
var GREEN = '\u001b[32m';
var RED = '\u001b[1;31m';
var PURPLE = '\u001b[1;35m';
var BLACK = '\u001b[0m';
var SUCCESS_START = GREEN + '✓ ';
var END = BLACK;
var FAILURE_START = RED + '✕ ';
var UNKNOWN_START = PURPLE + '? ';
var INVALID_START = PURPLE + '??? ';
var SEPARATOR = ': ';

/**
 * A test result.
 */
var TestResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.message = null;
	var finished = false;

	/**
	 * Callback for the test result.
	 * Can only be called once.
	 */
	self.callback = function(error, result)
	{
		if (error)
		{
			// only report the first failure
			if (!self.failure)
			{
				fail(error);
			}
		}
		else
		{
			if (result && result.failure)
			{
				return fail(result);
			}
			return succeed(result);
		}
	};

	/**
	 * Report a failure.
	 */
	function fail(message)
	{
		self.failure = true;
		self.success = false;
		self.message = message;
		console.log(FAILURE_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Report a success.
	 */
	function succeed(message)
	{
		if (finished)
		{
			if (self.success)
			{
				message = 'Duplicated call to callback';
			}
			// only one success allowed
			return fail(message);
		}
		self.success = true;
		self.message = message;
		console.log(SUCCESS_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		var message = self.key;
		if (self.message)
		{
			message += SEPARATOR + self.message;
		}
		return getPrintable(self, message, indent);
	};
};

/**
 * A result that contains other results.
 */
var CompositeResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.failures = 0;
	self.results = {};

	/**
	 * Add a sub-result.
	 */
	self.add = function(result)
	{
		self.results[result.key] = result;
		if (result.success && !self.failure)
		{
			self.success = true;
		}
		if (result.failure)
		{
			self.success = false;
			self.failure = true;
		}
	};

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		indent = indent || 0;
		var message = getPrintable(self, self.key + SEPARATOR, indent) + '\n';
		message += getIndented(indent) + '{\n';
		for (var key in self.results)
		{
			var result = self.results[key];
			message += result.toString(indent + 1) + ',\n';
		}
		message += getIndented(indent) + '}';
		return message;
	};

	/**
	 * Return a shortened representation.
	 */
	self.getSummary = function()
	{
		return getPrintable(self, self.key, 0);
	};
};

/**
 * Get the printable representation for a result, like this:
 * START message END.
 * An optional indent is applied as an equivalent number of tabs.
 */
function getPrintable(result, message, indent)
{
	var start;
	if (!result.failure && !result.success)
	{
		start = UNKNOWN_START;
	}
	else if (result.success && result.failure)
	{
		start = INVALID_START;
	}
	else if (result.success)
	{
		start = SUCCESS_START;
	}
	else
	{
		start = FAILURE_START;
	}
	return getIndented(indent) + start + message + END;
}

/**
 * Get an indented string, with an equivalent number of tabs.
 */
function getIndented(indent)
{
	var indented = '';
	if (indent)
	{
		for (var i = 0; i < indent; i++)
		{
			indented += '\t';
		}
	}
	return indented;
}

/**
 * Run a series of functions sequentially. Parameters:
 *	- param: an indexed object with functions, or with nested indexed objects.
 *	- callback: a function(error, result) to pass an indexed object with the results.
 */
exports.run = function(param, callback)
{
	var series = clone(param);
	// uncaught exceptions
	process.on('uncaughtException', uncaughtException);
	var runner = new Runner('main', series);
	runner.runAll(function(error, result)
	{
		process.removeListener('uncaughtException', uncaughtException);
		if (result && result.failure)
		{
			result.key = 'failure';
			return callback(result);
		}
		callback(error, result);
	});
};

/**
 * Process an uncaught exception.
 */
function uncaughtException(error)
{
	if (!next)
	{
		return log.error('Exception without next text');
	}
	next('Uncaught exception: ' + error.stack);
}

/**
 * A runner for a series of tests.
 */
var Runner = function(name, series)
{
	// self-reference
	var self = this;

	// attributes
	self.result = new CompositeResult(name);
	var finished = false;

	/**
	 * Run all functions in the series, one by one.
	 */
	self.runAll = function(callback)
	{
		runOne(function(error, result)
		{
			if (error)
			{
				console.error('Error in test %s: %s', name, error);
			}
			if (result)
			{
				console.log('Finished test %s: %s', name, result.getSummary());
			}
			if (finished)
			{
				return;
			}
			finished = true;
			callback(error, result);
		});
	};

	/**
	 * Run one function in the given series, go to the next.
	 */
	function runOne(callback)
	{
		if (isEmpty(series))
		{
			return callback(null, self.result);
		}
		for (var key in series)
		{
			var value = series[key];
			if (!value)
			{
				return callback('Empty test for ' + key);
			}
			else if (typeof value == 'object')
			{
				var runner = new Runner(key, value);
				return runner.runAll(function(error)
				{
					if (error)
					{
						log.error('Could not run all functions');
						return;
					}
					self.result.add(runner.result);
					deleteAndRunNext(key, callback);
				});
			}
			else if (typeof value == 'function')
			{
				var testName = key;
				if (isNumber(key) && value.name)
				{
					testName = value.name;
				}
				// it is a function to run
				next = getNext(key, testName, callback);
				return value(next);
			}
			else if (typeof value == 'string')
			{
				// it is a file name, with a test function
				var testFunction = getFileTest(value);
				if (!testFunction)
				{
					log.error('Cannot get test function from file %s', value);
					return deleteAndRunNext(key, callback);
				}
				next = getNext(key, value, callback);
				return testFunction(next);
			}
			else
			{
				log.error('Key %s has an invalid value %s', key, value);
				return deleteAndRunNext(key, callback);
			}
			// only the first element in the series is used;
			// the rest are called by recursion in deleteAndRunNext()
			return;
		}
	}

	/**
	 * Get the test function in a file.
	 */
	function getFileTest(filename)
	{
		try
		{
			var file = require(filename);
			return file.test;
		}
		catch(exception)
		{
			log.error('File %s not found: %s', filename, exception);
		}
	}

	/**
	 * Get a function to call after a test.
	 */
	function getNext(key, name, callback)
	{
		var testResult = new TestResult(name);
		return function(error, result)
		{
			testResult.callback(error, result);
			self.result.add(testResult);
			deleteAndRunNext(key, callback);
		};
	}

	/**
	 * Delete the current function, run the next.
	 */
	function deleteAndRunNext(key, callback)
	{
		if (!(key in series))
		{
			// already run
			return;
		}
		delete series[key];
		var defer = process.nextTick;
		if (typeof setImmediate != 'undefined')
		{
			// node v0.10.x
			defer = setImmediate;
		}
		return defer(function()
		{
			runOne(callback);
		});
	}
};

/**
 * Test to run some functions.
 */
function testRun()
{
	var series = {
		a: function(callback) {
			callback(null, 'a');
		},
		b: {
			e: function(callback) {
				callback('e');
			},
			c: function(callback) {
				callback(null, 'c');
			},
			f: function() {
				throw new Error('exception');
			},
		},
		g: [function(callback) {
			callback(null, 'g0');
		}, function two(callback) {
			callback(null, 'g1');
		}],
		h: ['../index.js', 'shouldNotFindThis.js'],
	};
	exports.run(series, function(error, result)
	{
		console.assert(result.failure, 'Root should be failure');
		console.assert(result.results.a, 'Should have result for a');
		console.assert(result.results.a.success, 'Should have success for a');
		console.assert(result.results.a.message == 'a', 'Should have an a for a');
		console.assert(result.results.b, 'Should have result for b');
		console.assert(result.results.b.failure, 'Should have failure for b');
		console.assert(result.results.b.results.c, 'Should have result for b.c');
		console.assert(result.results.b.results.c.success, 'Should have success for b.c');
		console.assert(result.results.b.results.c.message == 'c', 'Should have a c for b.c');
		console.assert(result.results.b.results.e, 'Should have result for b.e');
		console.assert(result.results.b.results.e.failure, 'Should have failure for b.e');
		console.assert(result.results.b.results.e.message == 'e', 'Should have a e for b.e');
		console.assert(result.results.g, 'Should have result for g');
		console.assert(result.results.g.success, 'Should have success for g');
		console.assert(result.results.g.results[0], 'Should have result for g[0]');
		console.assert(result.results.g.results[0].success, 'Should have success for g[0]');
		console.assert(result.results.g.results[0].message == 'g0', 'Should have g0 for g[0]');
		console.assert(result.results.g.results.two, 'Should have result for two');
		console.assert(result.results.g.results.two.success, 'Should have success for two');
		console.assert(result.results.g.results.two.message == 'g1', 'Should have g1 for two');
		log.info('Test run successful with 2 failures: %s', result);
	});
}

/**
 * Clone a series of functions. Performs a sanity check.
 */
function clone(series)
{
	if (typeof series != 'object')
	{
		log.error('Invalid series %s', JSON.stringify(series));
		return;
	}
	var copy = {};
	for (var key in series)
	{
		var value = series[key];
		if (typeof value == 'function' || typeof value == 'string')
		{
			copy[key] = value;
		}
		else
		{
			copy[key] = clone(value);
		}
	}
	return copy;
}

/**
 * Test the clone function.
 */
function testClone()
{
	var original = {
		a: function() {},
		b: {
			c: function() {},
		},
	};
	var cloned = clone(original);
	console.assert(cloned.a, 'Cloned object should have function property');
	console.assert(typeof cloned.a == 'function', 'Cloned object should have function');
	console.assert(cloned.b, 'Cloned object should have object property');
	console.assert(typeof cloned.b == 'object', 'Cloned object should have object');
	console.assert(cloned.b.c, 'Cloned object should have sub-object property');
	console.assert(typeof cloned.b.c == 'function', 'Cloned object should have sub-object');
}

/**
 * Find out if the object is empty.
 */
function isEmpty(object)
{
	for (var key in object)
	{
		if (object[key])
		{
			return false;
		}
	}
	return true;
}

/**
 * Test the empty function.
 */
function testEmpty()
{
	console.assert(isEmpty({}), 'Empty object should be empty');
	console.assert(!isEmpty({a: 'a'}), 'Not empty object is empty');
	console.assert(isEmpty([]), 'Empty array should be empty');
	console.assert(!isEmpty(['a']), 'Not empty array is empty');
}

/**
 * Find out if the argument is a number.
 * http://stackoverflow.com/a/1830844/978796
 */
function isNumber(n)
{
	return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * Run all module tests.
 * Cannot use testing since it is not defined yet.
 */
function test()
{
	testEmpty();
	testClone();
	testRun();
}

// run tests if invoked directly
if (__filename == process.argv[1])
{
	test();
}

<commit_msg>Do not report a success after a failure.<commit_after>'use strict';

/**
 * Testing library: runner for tests.
 * (C) 2013 Alex Fernández.
 */

// requires
var Log = require('log');

// globals
var log = new Log('info');
var next;

// constants
var GREEN = '\u001b[32m';
var RED = '\u001b[1;31m';
var PURPLE = '\u001b[1;35m';
var BLACK = '\u001b[0m';
var SUCCESS_START = GREEN + '✓ ';
var END = BLACK;
var FAILURE_START = RED + '✕ ';
var UNKNOWN_START = PURPLE + '? ';
var INVALID_START = PURPLE + '??? ';
var SEPARATOR = ': ';

/**
 * A test result.
 */
var TestResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.message = null;
	var finished = false;

	/**
	 * Callback for the test result.
	 * Can only be called once.
	 */
	self.callback = function(error, result)
	{
		if (error)
		{
			// only report the first failure
			if (!self.failure)
			{
				fail(error);
			}
		}
		else if (!self.failure)
		{
			if (result && result.failure)
			{
				return fail(result);
			}
			return succeed(result);
		}
	};

	/**
	 * Report a failure.
	 */
	function fail(message)
	{
		self.failure = true;
		self.success = false;
		self.message = message;
		console.log(FAILURE_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Report a success.
	 */
	function succeed(message)
	{
		if (finished)
		{
			if (self.success)
			{
				message = 'Duplicated call to callback';
			}
			// only one success allowed
			return fail(message);
		}
		self.success = true;
		self.message = message;
		console.log(SUCCESS_START + self.key + SEPARATOR + message + END);
		finished = true;
	}

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		var message = self.key;
		if (self.message)
		{
			message += SEPARATOR + self.message;
		}
		return getPrintable(self, message, indent);
	};
};

/**
 * A result that contains other results.
 */
var CompositeResult = function(key)
{
	// self-reference
	var self = this;

	// attributes
	self.key = key;
	self.success = false;
	self.failure = false;
	self.failures = 0;
	self.results = {};

	/**
	 * Add a sub-result.
	 */
	self.add = function(result)
	{
		self.results[result.key] = result;
		if (result.success && !self.failure)
		{
			self.success = true;
		}
		if (result.failure)
		{
			self.success = false;
			self.failure = true;
		}
	};

	/**
	 * Return a printable representation.
	 */
	self.toString = function(indent)
	{
		indent = indent || 0;
		var message = getPrintable(self, self.key + SEPARATOR, indent) + '\n';
		message += getIndented(indent) + '{\n';
		for (var key in self.results)
		{
			var result = self.results[key];
			message += result.toString(indent + 1) + ',\n';
		}
		message += getIndented(indent) + '}';
		return message;
	};

	/**
	 * Return a shortened representation.
	 */
	self.getSummary = function()
	{
		return getPrintable(self, self.key, 0);
	};
};

/**
 * Get the printable representation for a result, like this:
 * START message END.
 * An optional indent is applied as an equivalent number of tabs.
 */
function getPrintable(result, message, indent)
{
	var start;
	if (!result.failure && !result.success)
	{
		start = UNKNOWN_START;
	}
	else if (result.success && result.failure)
	{
		start = INVALID_START;
	}
	else if (result.success)
	{
		start = SUCCESS_START;
	}
	else
	{
		start = FAILURE_START;
	}
	return getIndented(indent) + start + message + END;
}

/**
 * Get an indented string, with an equivalent number of tabs.
 */
function getIndented(indent)
{
	var indented = '';
	if (indent)
	{
		for (var i = 0; i < indent; i++)
		{
			indented += '\t';
		}
	}
	return indented;
}

/**
 * Run a series of functions sequentially. Parameters:
 *	- param: an indexed object with functions, or with nested indexed objects.
 *	- callback: a function(error, result) to pass an indexed object with the results.
 */
exports.run = function(param, callback)
{
	var series = clone(param);
	// uncaught exceptions
	process.on('uncaughtException', uncaughtException);
	var runner = new Runner('main', series);
	runner.runAll(function(error, result)
	{
		process.removeListener('uncaughtException', uncaughtException);
		if (result && result.failure)
		{
			result.key = 'failure';
			return callback(result);
		}
		callback(error, result);
	});
};

/**
 * Process an uncaught exception.
 */
function uncaughtException(error)
{
	if (!next)
	{
		return log.error('Exception without next text');
	}
	next('Uncaught exception: ' + error.stack);
}

/**
 * A runner for a series of tests.
 */
var Runner = function(name, series)
{
	// self-reference
	var self = this;

	// attributes
	self.result = new CompositeResult(name);
	var finished = false;

	/**
	 * Run all functions in the series, one by one.
	 */
	self.runAll = function(callback)
	{
		runOne(function(error, result)
		{
			if (error)
			{
				console.error('Error in test %s: %s', name, error);
			}
			if (result)
			{
				console.log('Finished test %s: %s', name, result.getSummary());
			}
			if (finished)
			{
				return;
			}
			finished = true;
			callback(error, result);
		});
	};

	/**
	 * Run one function in the given series, go to the next.
	 */
	function runOne(callback)
	{
		if (isEmpty(series))
		{
			return callback(null, self.result);
		}
		for (var key in series)
		{
			var value = series[key];
			if (!value)
			{
				return callback('Empty test for ' + key);
			}
			else if (typeof value == 'object')
			{
				var runner = new Runner(key, value);
				return runner.runAll(function(error)
				{
					if (error)
					{
						log.error('Could not run all functions');
						return;
					}
					self.result.add(runner.result);
					deleteAndRunNext(key, callback);
				});
			}
			else if (typeof value == 'function')
			{
				var testName = key;
				if (isNumber(key) && value.name)
				{
					testName = value.name;
				}
				// it is a function to run
				next = getNext(key, testName, callback);
				return value(next);
			}
			else if (typeof value == 'string')
			{
				// it is a file name, with a test function
				var testFunction = getFileTest(value);
				if (!testFunction)
				{
					log.error('Cannot get test function from file %s', value);
					return deleteAndRunNext(key, callback);
				}
				next = getNext(key, value, callback);
				return testFunction(next);
			}
			else
			{
				log.error('Key %s has an invalid value %s', key, value);
				return deleteAndRunNext(key, callback);
			}
			// only the first element in the series is used;
			// the rest are called by recursion in deleteAndRunNext()
			return;
		}
	}

	/**
	 * Get the test function in a file.
	 */
	function getFileTest(filename)
	{
		try
		{
			var file = require(filename);
			return file.test;
		}
		catch(exception)
		{
			log.error('File %s not found: %s', filename, exception);
		}
	}

	/**
	 * Get a function to call after a test.
	 */
	function getNext(key, name, callback)
	{
		var testResult = new TestResult(name);
		return function(error, result)
		{
			testResult.callback(error, result);
			self.result.add(testResult);
			deleteAndRunNext(key, callback);
		};
	}

	/**
	 * Delete the current function, run the next.
	 */
	function deleteAndRunNext(key, callback)
	{
		if (!(key in series))
		{
			// already run
			return;
		}
		delete series[key];
		var defer = process.nextTick;
		if (typeof setImmediate != 'undefined')
		{
			// node v0.10.x
			defer = setImmediate;
		}
		return defer(function()
		{
			runOne(callback);
		});
	}
};

/**
 * Test to run some functions.
 */
function testRun()
{
	var series = {
		a: function(callback) {
			callback(null, 'a');
		},
		b: {
			e: function(callback) {
				callback('e');
			},
			c: function(callback) {
				callback(null, 'c');
			},
			f: function() {
				throw new Error('exception');
			},
		},
		g: [function(callback) {
			callback(null, 'g0');
		}, function two(callback) {
			callback(null, 'g1');
		}],
		h: ['../index.js', 'shouldNotFindThis.js'],
	};
	exports.run(series, function(error, result)
	{
		console.assert(result.failure, 'Root should be failure');
		console.assert(result.results.a, 'Should have result for a');
		console.assert(result.results.a.success, 'Should have success for a');
		console.assert(result.results.a.message == 'a', 'Should have an a for a');
		console.assert(result.results.b, 'Should have result for b');
		console.assert(result.results.b.failure, 'Should have failure for b');
		console.assert(result.results.b.results.c, 'Should have result for b.c');
		console.assert(result.results.b.results.c.success, 'Should have success for b.c');
		console.assert(result.results.b.results.c.message == 'c', 'Should have a c for b.c');
		console.assert(result.results.b.results.e, 'Should have result for b.e');
		console.assert(result.results.b.results.e.failure, 'Should have failure for b.e');
		console.assert(result.results.b.results.e.message == 'e', 'Should have a e for b.e');
		console.assert(result.results.g, 'Should have result for g');
		console.assert(result.results.g.success, 'Should have success for g');
		console.assert(result.results.g.results[0], 'Should have result for g[0]');
		console.assert(result.results.g.results[0].success, 'Should have success for g[0]');
		console.assert(result.results.g.results[0].message == 'g0', 'Should have g0 for g[0]');
		console.assert(result.results.g.results.two, 'Should have result for two');
		console.assert(result.results.g.results.two.success, 'Should have success for two');
		console.assert(result.results.g.results.two.message == 'g1', 'Should have g1 for two');
		log.info('Test run successful with 2 failures: %s', result);
	});
}

/**
 * Clone a series of functions. Performs a sanity check.
 */
function clone(series)
{
	if (typeof series != 'object')
	{
		log.error('Invalid series %s', JSON.stringify(series));
		return;
	}
	var copy = {};
	for (var key in series)
	{
		var value = series[key];
		if (typeof value == 'function' || typeof value == 'string')
		{
			copy[key] = value;
		}
		else
		{
			copy[key] = clone(value);
		}
	}
	return copy;
}

/**
 * Test the clone function.
 */
function testClone()
{
	var original = {
		a: function() {},
		b: {
			c: function() {},
		},
	};
	var cloned = clone(original);
	console.assert(cloned.a, 'Cloned object should have function property');
	console.assert(typeof cloned.a == 'function', 'Cloned object should have function');
	console.assert(cloned.b, 'Cloned object should have object property');
	console.assert(typeof cloned.b == 'object', 'Cloned object should have object');
	console.assert(cloned.b.c, 'Cloned object should have sub-object property');
	console.assert(typeof cloned.b.c == 'function', 'Cloned object should have sub-object');
}

/**
 * Find out if the object is empty.
 */
function isEmpty(object)
{
	for (var key in object)
	{
		if (object[key])
		{
			return false;
		}
	}
	return true;
}

/**
 * Test the empty function.
 */
function testEmpty()
{
	console.assert(isEmpty({}), 'Empty object should be empty');
	console.assert(!isEmpty({a: 'a'}), 'Not empty object is empty');
	console.assert(isEmpty([]), 'Empty array should be empty');
	console.assert(!isEmpty(['a']), 'Not empty array is empty');
}

/**
 * Find out if the argument is a number.
 * http://stackoverflow.com/a/1830844/978796
 */
function isNumber(n)
{
	return !isNaN(parseFloat(n)) && isFinite(n);
}

/**
 * Run all module tests.
 * Cannot use testing since it is not defined yet.
 */
function test()
{
	testEmpty();
	testClone();
	testRun();
}

// run tests if invoked directly
if (__filename == process.argv[1])
{
	test();
}

<|endoftext|>