<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';


const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const util = require('util');
const async = require('async');
const csv = require('csv-parse');
const cli = require('cli');
const YAML = require('yaml-js');
const moment = require('moment');
const chalk = require('chalk');
const defaultOptions = require('rc')('artillery');
const validate = require('artillery-core').validate;
const debug = require('debug')('commands:run');

const createRunner = require('../runner');

module.exports = run;

function run(scriptPath, options) {
  let logfile;

  // is the destination a directory that exists?
  let isDir = false;
  if (options.output && options.output !== defaultOptions.output) {
    try {
      isDir = fs.statSync(options.output).isDirectory();
    } catch (err) {
      // ENOENT, don't need to do anything
    }
  }

  const defaultFormat = '[artillery_report_]YMMDD_HHmmSS[.json]';
  if (!isDir && options.output) {
    // -o is set with a filename (existing or not)
    logfile = options.output;
  } else if (!isDir && !options.output) {
    // no -o set
    logfile = moment().format(defaultOptions.output ||
                              defaultFormat);
  } else {
    // -o is set with a directory
    logfile = path.join(options.output, moment().format(defaultOptions.output ||
                                                        defaultFormat));
  }

  function log() {
    if (options.quiet) { return; }
    console.log.apply(console, arguments);
  }

  async.waterfall([
    function readScript(callback) {

      fs.readFile(scriptPath, 'utf-8', function(err, data) {

        if (err) {
          const msg = util.format('File not found: %s', scriptPath);
          return callback(new Error(msg), null);
        }

        let script;
        let fileformat;
        try {
          if (/\.ya?ml$/.test(path.extname(scriptPath))) {
            fileformat = 'YAML';
            script = YAML.load(data);
          } else {
            fileformat = 'JSON';
            script = JSON.parse(data);
          }
        } catch (e) {
          const msg2 = `File ${scriptPath} does not appear to be valid ${fileformat}: (${e.message})`;
          return callback(new Error(msg2), null);
        }

        if (options.target && script.config) {
          script.config.target = options.target;
        }

        if (!script.config.target && !options.environment) {
          const msg4 = 'No target specified and no environment chosen';
          return callback(new Error(msg4), null);
        }

        let validation = validate(script);
        if (!validation.valid) {
          log(validation.errors);
          return callback(new Error('Test script validation error'), null);
        }

        return callback(null, {script: script});
      });
    },
    function readPayload(context, callback) {

      if (context.script.config.payload && _.isArray(context.script.config.payload)) {
        async.map(context.script.config.payload,
          function(item, callback2) {
            let absoluteScriptPath = path.resolve(process.cwd(), scriptPath);
            let payloadFile = path.resolve(path.dirname(absoluteScriptPath), item.path);

            let data = fs.readFileSync(payloadFile, 'utf-8');
            csv(data, function(err, parsedData) {
              item.data = parsedData;
              return callback2(err, item);
            });
          },
          function(err, results) {
            if (err) {
              return callback(err, null);
            }
            context.payload = results;
            return callback(null, context);
          });
      } else if (context.script.config.payload &&
                 (_.isObject(context.script.config.payload) ||
                 options.payload)) {

        //use config if path set else use command line
        let csvdata;
        if (context.script.config.payload.path) {
          let absoluteScriptPath = path.resolve(process.cwd(), scriptPath);
          let payloadFile = path.resolve(path.dirname(absoluteScriptPath), context.script.config.payload.path);
          csvdata = fs.readFileSync(payloadFile, 'utf-8');
        } else {
          csvdata = fs.readFileSync(options.payload, 'utf-8');
        }
        csv(csvdata, function(err, payload) {

          if (err) {
            const msg3 = util.format(
              'File %s does not appear to be valid CSV', options.payload);
            return callback(new Error(msg3), null);
          }

          context.payload = payload;

          return callback(null, context);
        });
      } else {
        if (context.script.config.payload) {
          log(
            'WARNING: payload file not set, but payload is configured in %s\n',
            scriptPath);
        }

        return callback(null, context);
      }
    },
    function checkTimersBug(context, callback) {
      // Node.js versions 6.8.1 - 7.2.0 have a bug which can cause the process
      // to hang when rampTo is used.
      // Ref: https://github.com/shoreditch-ops/artillery/issues/210
      //
      // TODO: Add a check for upper end of the affected range once Node.js
      // with the fix is out.
      // Ref: https://github.com/nodejs/node/issues/9756

      // 6.8.1 and 7.0.0 and 7.2.0 but NOT 6.8.0
      let nv = process.version;
      let mj = parseInt(nv.substring(1, 2), 10);
      let mn = parseInt(nv.substring(3, 4), 10);
      let pt = parseInt(nv.substring(5, 6), 10);
      if (mj === 7 ||
          (mj === 6 && mn > 8) ||
          (mj === 6 && mn === 8 && pt > 0)) {
        let usesRampTo = _.some(context.script.config.phases, function(phase) {
          return !(_.isUndefined(phase.rampTo));
        });

        if (usesRampTo) {
          console.error(chalk.bold.red(`
You are running Node.js ${nv} which is affected by a bug that can cause
Artillery to hang indefinitely when a rampTo arrival phase is used.

Use Node.js v6.8.0 or lower to avoid the issue.

For more details see: https://github.com/nodejs/node/issues/9756
`));
        }
      }

      return callback(null, context);
    },
    function checkIfXPathIsUsed(context, callback) {
      // FIXME: This should probably be in core.
      let xmlInstalled = null;
      try {
        xmlInstalled = require('artillery-xml-capture');<commit_msg>Warn about rampTo only for Node <7.3.0<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

'use strict';


const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const util = require('util');
const async = require('async');
const csv = require('csv-parse');
const cli = require('cli');
const YAML = require('yaml-js');
const moment = require('moment');
const chalk = require('chalk');
const defaultOptions = require('rc')('artillery');
const validate = require('artillery-core').validate;
const debug = require('debug')('commands:run');

const createRunner = require('../runner');

module.exports = run;

function run(scriptPath, options) {
  let logfile;

  // is the destination a directory that exists?
  let isDir = false;
  if (options.output && options.output !== defaultOptions.output) {
    try {
      isDir = fs.statSync(options.output).isDirectory();
    } catch (err) {
      // ENOENT, don't need to do anything
    }
  }

  const defaultFormat = '[artillery_report_]YMMDD_HHmmSS[.json]';
  if (!isDir && options.output) {
    // -o is set with a filename (existing or not)
    logfile = options.output;
  } else if (!isDir && !options.output) {
    // no -o set
    logfile = moment().format(defaultOptions.output ||
                              defaultFormat);
  } else {
    // -o is set with a directory
    logfile = path.join(options.output, moment().format(defaultOptions.output ||
                                                        defaultFormat));
  }

  function log() {
    if (options.quiet) { return; }
    console.log.apply(console, arguments);
  }

  async.waterfall([
    function readScript(callback) {

      fs.readFile(scriptPath, 'utf-8', function(err, data) {

        if (err) {
          const msg = util.format('File not found: %s', scriptPath);
          return callback(new Error(msg), null);
        }

        let script;
        let fileformat;
        try {
          if (/\.ya?ml$/.test(path.extname(scriptPath))) {
            fileformat = 'YAML';
            script = YAML.load(data);
          } else {
            fileformat = 'JSON';
            script = JSON.parse(data);
          }
        } catch (e) {
          const msg2 = `File ${scriptPath} does not appear to be valid ${fileformat}: (${e.message})`;
          return callback(new Error(msg2), null);
        }

        if (options.target && script.config) {
          script.config.target = options.target;
        }

        if (!script.config.target && !options.environment) {
          const msg4 = 'No target specified and no environment chosen';
          return callback(new Error(msg4), null);
        }

        let validation = validate(script);
        if (!validation.valid) {
          log(validation.errors);
          return callback(new Error('Test script validation error'), null);
        }

        return callback(null, {script: script});
      });
    },
    function readPayload(context, callback) {

      if (context.script.config.payload && _.isArray(context.script.config.payload)) {
        async.map(context.script.config.payload,
          function(item, callback2) {
            let absoluteScriptPath = path.resolve(process.cwd(), scriptPath);
            let payloadFile = path.resolve(path.dirname(absoluteScriptPath), item.path);

            let data = fs.readFileSync(payloadFile, 'utf-8');
            csv(data, function(err, parsedData) {
              item.data = parsedData;
              return callback2(err, item);
            });
          },
          function(err, results) {
            if (err) {
              return callback(err, null);
            }
            context.payload = results;
            return callback(null, context);
          });
      } else if (context.script.config.payload &&
                 (_.isObject(context.script.config.payload) ||
                 options.payload)) {

        //use config if path set else use command line
        let csvdata;
        if (context.script.config.payload.path) {
          let absoluteScriptPath = path.resolve(process.cwd(), scriptPath);
          let payloadFile = path.resolve(path.dirname(absoluteScriptPath), context.script.config.payload.path);
          csvdata = fs.readFileSync(payloadFile, 'utf-8');
        } else {
          csvdata = fs.readFileSync(options.payload, 'utf-8');
        }
        csv(csvdata, function(err, payload) {

          if (err) {
            const msg3 = util.format(
              'File %s does not appear to be valid CSV', options.payload);
            return callback(new Error(msg3), null);
          }

          context.payload = payload;

          return callback(null, context);
        });
      } else {
        if (context.script.config.payload) {
          log(
            'WARNING: payload file not set, but payload is configured in %s\n',
            scriptPath);
        }

        return callback(null, context);
      }
    },
    function checkTimersBug(context, callback) {
      // Node.js versions 6.8.1 - 7.2.0 have a bug which can cause the process
      // to hang when rampTo is used.
      // Ref: https://github.com/shoreditch-ops/artillery/issues/210

      // 6.8.1 and 7.0.0 and 7.2.0 but NOT 6.8.0
      let nv = process.version;
      let mj = parseInt(nv.substring(1, 2), 10);
      let mn = parseInt(nv.substring(3, 4), 10);
      let pt = parseInt(nv.substring(5, 6), 10);
      if (mj === 7 && mn < 3 ||
          (mj === 6 && mn > 8) ||
          (mj === 6 && mn === 8 && pt > 0)) {
        let usesRampTo = _.some(context.script.config.phases, function(phase) {
          return !(_.isUndefined(phase.rampTo));
        });

        if (usesRampTo) {
          console.error(chalk.bold.red(`
You are running Node.js ${nv} which is affected by a bug that can cause
Artillery to hang indefinitely when a rampTo arrival phase is used.

Use Node.js v6.8.0 or lower to avoid the issue.

For more details see: https://github.com/nodejs/node/issues/9756
`));
        }
      }

      return callback(null, context);
    },
    function checkIfXPathIsUsed(context, callback) {
      // FIXME: This should probably be in core.
      let xmlInstalled = null;
      try {
        xmlInstalled = require('artillery-xml-capture');
{'fraction_non_alphanumeric': 0.13057889462730285, 'fraction_numerical': 0.010174978802127496, 'mean_word_length': 2.142926356589147, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5961124', 'n_tokens_mistral': 3798, 'n_tokens_neox': 3663, 'n_words': 1019}