Cellarise/istanbul-reporter-clover-limits

View on GitHub
tasks/test.js

Summary

Maintainability
F
6 days
Test Coverage
"use strict";
/**
 * A module to add gulp tasks which run test steps.
 * @exports tasks/testTasks
 * @param {Gulp} gulp - The gulp module
 * @param {Object} context - An object containing the following properties:
 * @param {String} context.cwd - The current working directory
 * @param {Object} context.package - The package.json for the module
 * @param {Array} context.argv - The arguments past to the gulp task
 * @param {bunyan} context.logger - A logger matching the bunyan API
 */
module.exports = function testTasks(gulp, context) {
  var mocha = require("gulp-mocha");
  var mkdirp = require("mkdirp");
  var gutil = require("gulp-util");
  var glob = require("glob");
  var path = require("path");
  var fs = require("fs");
  var R = require("ramda");
  var istanbul = require("gulp-istanbul");
  var babel = require("babel-core/register");
  var logger = context.logger;
  var COVERAGE_VAR = "__cpmCoverage__";

  var lowerCaseFirstLetter = function lowerCaseFirstLetter(str) {
    return str.slice(0, 1).toLowerCase() + str.slice(1);
  };
  var mergeFileCoverage = function mergeFileCoverage(first, second) {
    var ret = JSON.parse(JSON.stringify(first)), i;
    delete ret.l; //remove derived info
    Object.keys(second.s).forEach(function processkeys(k) {
      ret.s[k] += second.s[k];
    });
    Object.keys(second.f).forEach(function processkeys(k) {
      ret.f[k] += second.f[k];
    });
    Object.keys(second.b).forEach(function processkeys(k) {
      var retArray = ret.b[k],
        secondArray = second.b[k];
      for (i = 0; i < retArray.length; i += 1) {
        retArray[i] += secondArray[i];
      }
    });
    return ret;
  };
  var processCoverage = function processCoverage(coverageData) {
    if (!global.__cpmCoverage__) {
      global.__cpmCoverage__ = {};
    }
    R.mapObjIndexed(
      function mapMergedCov(fileCov, filePath) {
        if (global.__cpmCoverage__.hasOwnProperty(filePath)
          && !coverageData.hasOwnProperty(filePath)) {
          //nothing
        } else if (!global.__cpmCoverage__.hasOwnProperty(filePath)
          && coverageData.hasOwnProperty(filePath)) {
          global.__cpmCoverage__[filePath] = coverageData[filePath];
        } else {
          global.__cpmCoverage__[filePath] =
            mergeFileCoverage(global.__cpmCoverage__[filePath], coverageData[filePath]);
        }
      },
      R.merge(coverageData, global.__cpmCoverage__)
    );
  };

  var handleError = function handleError(err) {
    logger.error(err.toString());
    if (process.env.CI) {
      throw new gutil.PluginError({
        "plugin": "Gulp Mocha",
        "message": err.toString()
      });
    }
    this.emit("end"); //jshint ignore:line
  };


  var test = function test(reporter, outputCoverageReports) {
    var cwd = context.cwd;
    var pkg = context.package;
    var directories = pkg.directories;
    var sourceGlobStr = directories.lib + "/**/*.js";
    var scriptPath;
    var outputDir = path.join(cwd, directories.reports, "code-coverage"
      + (process.env.SELENIUM_PORT ? "-" + process.env.SELENIUM_PORT : ""));

    //make sure Temp folder exists before test
    mkdirp.sync(path.join(cwd, "Temp"));

    //require all library scripts to ensure istanbul picks up
    R.forEach(function eachSourceGlobStrFN(value) {
      scriptPath = path.resolve(process.cwd(), value);
      try {
        require(scriptPath); // Make sure all files are loaded to get accurate coverage data
        logger.info("Loaded: " + scriptPath);
      } catch (err) {
        logger.warn("Could not load: " + scriptPath);
      }
    }, glob.sync(sourceGlobStr));

    //set YADDA_FEATURE_GLOB if argv[2]
    if (context.argv.length === 2) {
      process.env.YADDA_FEATURE_GLOB = context.argv[1];
      logger.info("Set process.env.YADDA_FEATURE_GLOB=" + process.env.YADDA_FEATURE_GLOB);
    }

    if (outputCoverageReports) {
      return gulp.src(path.resolve(process.cwd(), directories.test + "/test.js"), {"read": false})
        .pipe(mocha({
          "compilers": {
            "js": babel
          },
          "bail": process.env.hasOwnProperty("bamboo_working_directory"),
          "reporter": reporter,
          "timeout": 600000
        }))
        .on("error", handleError)
        .pipe(istanbul.writeReports({
          "dir": outputDir,
          "coverageVariable": COVERAGE_VAR,
          "reporters": ["html", "lcov", require("istanbul-reporter-clover-limits"), "json-summary"],
          "reportOpts": {
            "dir": outputDir,
            "watermarks": pkg.config.coverage.watermarks
          }
        }))
        .pipe(istanbul.enforceThresholds({
          "coverageVariable": COVERAGE_VAR,
          "thresholds": {
            "each": {
              "statements": pkg.config.coverage.watermarks.statements[0],
              "branches": pkg.config.coverage.watermarks.branches[0],
              "lines": pkg.config.coverage.watermarks.lines[0],
              "functions": pkg.config.coverage.watermarks.functions[0]
            }
          }
        }));
    }
    return gulp.src(path.resolve(process.cwd(), directories.test + "/test.js"), {"read": false})
      .pipe(mocha({
        "compilers": {
          "js": babel
        },
        "bail": true,
        "reporter": reporter,
        "timeout": 600000
      }))
      .on("error", function onError(err) {
        //logger.error(err.toString());
        throw new gutil.PluginError({
          "plugin": "Gulp Mocha",
          "message": err.toString()
        });
      });
  };

  /**
   * A gulp build task to instrument files.
   * Istanbul will override the node require() function to redirect to the instrumented files.
   * @member {Gulp} instrument
   * @return {through2} stream
   */
  gulp.task("instrument", function instrumentTask() {
    var pkg = context.package;
    var directories = pkg.directories;
    var sourceGlobStr = directories.lib + "/**/*.js";
    /**
     * Istanbul code coverage will not work if there are tasks containing local references.
     * For example, var x = require("../../lib/index");
     * Note: that if gulpfile.js contains `gulp.loadTasks(__dirname);` then all tasks will be loaded
     * in gulp modules and the tasks directory.
     * Make sure all these tasks do not require local references as defined above.
     */
    return gulp.src(sourceGlobStr)
      .pipe(istanbul({
        "coverageVariable": COVERAGE_VAR,
        "includeUntested": true
      }))
      .pipe(istanbul.hookRequire()); // Force `require` to return covered files
    // Covering files - note: finish event called when finished (not end event)
  });

  /**
   * A gulp build task to run test steps and calculate test coverage.
   * Test steps results will be output using mocha-bamboo-reporter-bgo reporter.
   * This task executes the Instrument task as a prerequisite.
   * @member {Gulp} test_cover
   * @return {through2} stream
   */
  gulp.task("test_cover", ["instrument"], function testCoverTask() {
    var cwd = context.cwd;
    var pkg = context.package;
    var directories = pkg.directories;
    var MOCHA_FILE_NAME = 'unit-mocha-tests' + (process.env.SELENIUM_PORT ? "-" + process.env.SELENIUM_PORT : "");

    //results file path for mocha-bamboo-reporter-bgo
    process.env.MOCHA_FILE = path.join(cwd, directories.reports, MOCHA_FILE_NAME + ".json");
    //make sure the Reports directory exists - required for mocha-bamboo-reporter-bgo
    mkdirp.sync(path.join(cwd, directories.reports));
    if (process.env.CI) {
      return test("spec", true);
    }
    return test("mocha-bamboo-reporter-bgo", true);
  });

  /**
   * A gulp build task to run test steps and calculate test coverage (but not output test coverage to prevent
   * gulp-istanbul issues with webdriverIO).
   * Test steps results will be output using mocha-bamboo-reporter-bgo reporter.
   * This task executes the Instrument task as a prerequisite.
   * @member {Gulp} test_cover
   * @return {through2} stream
   */
  gulp.task("test_cover_no_cov_report", function testCoverNoCovReportTask() {
    var cwd = context.cwd;
    var pkg = context.package;
    var directories = pkg.directories;
    var MOCHA_FILE_NAME = 'unit-mocha-tests' + (process.env.SELENIUM_PORT ? "-" + process.env.SELENIUM_PORT : "");

    //results file path for mocha-bamboo-reporter-bgo
    process.env.MOCHA_FILE = path.join(cwd, directories.reports, MOCHA_FILE_NAME + ".json");
    //make sure the Reports directory exists - required for mocha-bamboo-reporter-bgo
    mkdirp.sync(path.join(cwd, directories.reports));
    if (process.env.CI) {
      return test("spec", false);
    }
    return test("mocha-bamboo-reporter-bgo", false);
  });

  /**
   * A gulp build task to run test steps and calculate test coverage (but not output test coverage to prevent
   * gulp-istanbul issues with webdriverIO).
   * Test steps results will be output using mocha-bamboo-reporter-bgo reporter.
   * This task executes the Instrument task as a prerequisite.
   * @member {Gulp} test_cover
   * @return {through2} stream
   */
  gulp.task("test_cover_save_cov", ["test_cover_no_cov_report"], function testCoverTask(cb) {
    var cwd = context.cwd;
    var cwdForwardSlash = lowerCaseFirstLetter(cwd).replace("/", "\\");
    var pkg = context.package;
    var directories = pkg.directories;
    var outputDir = path.join(cwd, directories.reports, "code-coverage");
    var localPathCoverage = R.pipe(
      R.toPairs,
      R.map(function mapObjPair(objPair) {
        var filePath = lowerCaseFirstLetter(objPair[0]);
        return [filePath.replace(cwdForwardSlash, ""), objPair[1]];
      }),
      R.fromPairs
    )(global[COVERAGE_VAR]);
    //make sure outputDir exists and save the raw coverage file for future use
    mkdirp.sync(outputDir);
    fs.writeFile(
      path.join(outputDir, 'coverage' + (process.env.SELENIUM_PORT ? "-" + process.env.SELENIUM_PORT : "") + '.json'),
      JSON.stringify(localPathCoverage), 'utf8',
      cb
    );
  });

  /**
   * A gulp build task to write coverage.
   * @member {Gulp} write_coverage
   * @return {through2} stream
   */
  gulp.task("write_coverage", function testWriteCoverage() {
    var cwd = context.cwd;
    var pkg = context.package;
    var directories = pkg.directories;
    var outputDir = path.join(cwd, directories.reports, "code-coverage");
    var outputDirs = [
      path.join(cwd, directories.reports, "1", "code-coverage"),
      path.join(cwd, directories.reports, "2",  "code-coverage"),
      path.join(cwd, directories.reports, "3",  "code-coverage"),
      path.join(cwd, directories.reports, "4",  "code-coverage"),
      path.join(cwd, directories.reports, "5",  "code-coverage"),
      path.join(cwd, directories.reports, "6",  "code-coverage"),
      path.join(cwd, directories.reports, "7",  "code-coverage"),
      path.join(cwd, directories.reports, "8",  "code-coverage"),
      path.join(cwd, directories.reports, "9",  "code-coverage"),
      path.join(cwd, directories.reports, "10",  "code-coverage"),
      path.join(cwd, directories.reports, "11",  "code-coverage"),
      path.join(cwd, directories.reports, "12",  "code-coverage"),
      path.join(cwd, directories.reports, "13",  "code-coverage"),
      path.join(cwd, directories.reports, "14",  "code-coverage"),
      path.join(cwd, directories.reports, "15",  "code-coverage"),
      path.join(cwd, directories.reports, "16",  "code-coverage"),
      path.join(cwd, directories.reports, "17",  "code-coverage"),
      path.join(cwd, directories.reports, "18",  "code-coverage"),
      path.join(cwd, directories.reports, "19",  "code-coverage"),
      path.join(cwd, directories.reports, "20",  "code-coverage"),
      path.join(cwd, directories.reports, "NFR", "code-coverage")
    ];
    var coverageFileNames = [
      'coverage-4441.json',
      'coverage-4442.json',
      'coverage-4443.json'
    ];
    var fileContents;
    //read all coverage files and add to global[COVERAGE_VAR]
    R.forEach(
      function forEachOutputDir(reportDir) {
        R.forEach(
          function forEachFile(fileName) {
            try {
              fileContents = fs.readFileSync(path.join(reportDir, fileName));
              logger.info("Loaded: " + reportDir + fileName);
              //add find and replace for bamboo build server remote agents
              processCoverage(
                JSON.parse(fileContents
                  .toString('utf-8')
                  .replace(
                    /(C:|D:|E:|c:|d:|e:)\\\\bamboo.*?\\\\xml-data\\\\build-dir\\\\.*?NHVR.*?\\\\/g,
                    cwd.replace(/\\/g, "\\\\") + "\\\\"
                  )
                )
              );
            } catch (err) {
              logger.info("Write coverage failed for: " + reportDir + fileName);
              return false;
            }
          },
          coverageFileNames
        );
      },
      outputDirs
    );

    //clean coverage
    delete global[COVERAGE_VAR].class;
    delete global[COVERAGE_VAR].hCode;
    delete global[COVERAGE_VAR].sessionId;
    delete global[COVERAGE_VAR].state;
    delete global[COVERAGE_VAR].status;
    delete global[COVERAGE_VAR].value;

    //copy path to file key
    global[COVERAGE_VAR] = R.pipe(
      R.toPairs,
      R.map(function mapObjPair(objPair) {
        var filePath = lowerCaseFirstLetter(objPair[0]);
        if (objPair[1].path) {
          filePath = lowerCaseFirstLetter(objPair[1].path);
        }
        return [filePath, objPair[1]];
      }),
      R.fromPairs
    )(global[COVERAGE_VAR]);


    return gulp.src(outputDir, {"read": false})
      .pipe(istanbul.writeReports({
        "dir": outputDir,
        "coverageVariable": COVERAGE_VAR,
        "reporters": ["html", "lcov", require("istanbul-reporter-clover-limits"), "json-summary"],
        "reportOpts": {
          "dir": outputDir,
          "watermarks": pkg.config.coverage.watermarks
        }
      }))
      .pipe(istanbul.enforceThresholds({
        "coverageVariable": COVERAGE_VAR,
        "thresholds": {
          "each": {
            "statements": pkg.config.coverage.watermarks.statements[0],
            "branches": pkg.config.coverage.watermarks.branches[0],
            "lines": pkg.config.coverage.watermarks.lines[0],
            "functions": pkg.config.coverage.watermarks.functions[0]
          }
        }
      }));
  });

  /**
   * A gulp build task to run test steps and calculate test coverage.
   * Test steps results will be output using spec reporter.
   * @member {Gulp} test
   * @return {through2} stream
   */
  gulp.task("test", function testTask() {
    return test("spec", true);
  });
};