attester/attester

View on GitHub
lib/test-campaign/coverage.js

Summary

Maintainability
A
1 hr
Test Coverage
/*
 * Copyright 2012 Amadeus s.a.s. Licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

var fs = require('fs');
var pathUtils = require('path');
var coverage = require('node-coverage');
var instrument = coverage.instrument;
var coverageReport = coverage.report;

var FileSet = require('../util/files/fileset.js');

var merge = require('../util/merge.js');

var emptyResult = function () {
    return {
        name: "",
        run: {},
        runFunctions: []
    };
};

var Coverage = function (inputCfg, logger, rootDirectory) {
    this.logger = logger;
    var hasFilesToInstrument = !!inputCfg.files;
    var cfg = {
        files: {
            includes: [],
            excludes: []
        },
        infoFiles: []
    };
    merge(cfg, inputCfg);
    this._staticInfo = {};
    this._updatedStaticInfo = false;
    this._result = null;
    this._infoFiles = cfg.infoFiles.map(function (filePath) {
        return pathUtils.resolve(rootDirectory, filePath);
    });
    if (hasFilesToInstrument) {
        cfg.files.rootDirectory = pathUtils.resolve(rootDirectory, cfg.files.rootDirectory || "");
        this._fileSet = new FileSet(cfg.files);
        this._cache = {};
    }
};

function callcallbacks(callbacks, err, res) {
    for (var i = 0, l = callbacks.length; i < l; i++) {
        callbacks[i](err, res);
    }
}

function osIndependentFileName(fileName) {
    return fileName.split(pathUtils.sep).join("/");
}

Coverage.prototype.addResult = function (report) {
    var processedCoverage = coverageReport.generateAll(report, this._staticInfo);
    if (this._result) {
        this._result = coverageReport.mergeReports([this._result, processedCoverage]);
    } else {
        this._result = processedCoverage;
    }
};

Coverage.prototype.addStaticInfo = function (staticInfo) {
    if (Object.keys(staticInfo).length > 0) {
        Object.assign(this._staticInfo, staticInfo);
        this._updatedStaticInfo = true;
    }
};

Coverage.prototype.getResult = function () {
    if (this._updatedStaticInfo) {
        this.addResult(emptyResult());
    }
    return this._result;
};

Coverage.prototype.instrumentAll = function (endCallback) {
    var self = this;
    var fileSet = this._fileSet;
    var waitingCallbacks = 1;
    var callback = function () {
        waitingCallbacks--;
        if (waitingCallbacks !== 0) {
            return;
        }
        endCallback();
    };
    fileSet.iterate(function (relativePath) {
        waitingCallbacks++;
        var absolutePath = fileSet.getAbsolutePath(relativePath);
        relativePath = osIndependentFileName(relativePath);
        self.instrument(absolutePath, relativePath, callback);
    }, callback);
};

Coverage.prototype.readInfoFile = function (infoFile, endCallback) {
    var self = this;
    fs.readFile(infoFile, 'utf-8', function (err, data) {
        try {
            if (!err) {
                var infoFileContent = JSON.parse(data);
                self.addStaticInfo(infoFileContent);
            }
        } catch (error) {
            err = error || new Error('Unknown error while processing ' + infoFile);
        } finally {
            if (err) {
                self.logger.logError('Error while loading coverage information from %s: %s', [infoFile, err.stack || err]);
            }
            endCallback(err);
        }
    });
};

Coverage.prototype.init = function (endCallback) {
    var waitingCallbacks = 1;
    var callback = function () {
        waitingCallbacks--;
        if (waitingCallbacks === 0) {
            endCallback();
        }
    };
    this._infoFiles.forEach(function (infoFile) {
        waitingCallbacks++;
        this.readInfoFile(infoFile, callback);
    }, this);
    if (this._fileSet) {
        waitingCallbacks++;
        this.instrumentAll(callback);
    }
    callback();
};

Coverage.prototype.instrument = function (absoluteFileName, displayFileName, callback) {
    var self = this;
    var cacheEntry = this._cache[displayFileName];
    if (cacheEntry) {
        if (cacheEntry.callbacks) {
            cacheEntry.callbacks.push(callback);
        } else {
            callback(cacheEntry.err, cacheEntry.clientCode);
        }
    } else {
        var allCallbacks = [callback];
        cacheEntry = {
            callbacks: allCallbacks
        };
        this._cache[displayFileName] = cacheEntry;
        fs.readFile(absoluteFileName, 'utf-8', function (err, data) {
            cacheEntry.callbacks = null;
            if (err) {
                cacheEntry.err = err;
                return callcallbacks(allCallbacks, err, null);
            }
            var result = instrument(displayFileName, data, {
                conditions: true,
                functions: true,
                staticInfo: false,
                submit: false
            });
            cacheEntry.clientCode = result.clientCode;
            if (result.staticInfo) {
                var staticInfo = {};
                staticInfo[displayFileName] = result.staticInfo;
                self.addStaticInfo(staticInfo);
            }
            callcallbacks(allCallbacks, null, result.clientCode);
        });
    }
};

Coverage.prototype.instrumentedSender = function () {
    var self = this;
    return this._fileSet ? function (req, res, next) {
        var resolvedPath = req.resolvedPath;
        if (resolvedPath) {
            var absolutePath = resolvedPath.absolutePath;
            var fileSet = self._fileSet;
            var relativePath = fileSet.getRelativePath(absolutePath);
            relativePath = osIndependentFileName(relativePath);
            if (self._cache[relativePath] || fileSet.isFileNameIncluded(relativePath)) {
                self.instrument(absolutePath, relativePath, function (err, data) {
                    if (err) {
                        return next();
                    }
                    res.setHeader('Content-type', 'text/javascript');
                    res.setHeader('Expires', new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toUTCString());
                    res.end(data);
                });
                return;
            }
        }
        return next();
    } : function (req, res, next) {
        return next();
    };
};

module.exports = Coverage;