//////////////////////////////////////////////////////////////////////////////// /// @brief JSUnity wrapper /// /// @file /// /// DISCLAIMER /// /// Copyright 2010-2012 triagens GmbH, Cologne, Germany /// /// 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. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Dr. Frank Celler /// @author Copyright 2012, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// var internal = require("internal"); var console = require("console"); internal.loadFile("jsunity/jsunity"); jsUnity.log = console; //////////////////////////////////////////////////////////////////////////////// /// @page jsUnity Using jsUnity and node-jscoverage /// /// The AvocadoDB contains a wrapper for /// jsUnity, a lightyweight universal /// JavAScript unit testing framework. /// /// @section jsUnityRunningTest Running jsUnity Tests /// /// Assume that you have a test file containing /// /// @verbinclude jsunity1 /// /// Then you can run the test suite using @FN{runTest} /// /// @verbinclude jsunity2 /// /// @section jsUnityRunningCoverage Running jsUnity Tests with Coverage /// /// You can use the coverage tool /// @LIT{node-jscoverage}. /// /// Assume that your file live in a directory called @LIT{lib}. Use /// /// @CODE{node-jscoverage lib lib-cov} /// /// to create a copy of the JavaScript files with coverage information. /// Start the AvocadoDB with these files and use @FN{runCoverage} instead /// of @FN{runTest}. //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup Expresso /// @{ /// /// based on: /// /// Expresso /// Copyright(c) TJ Holowaychuk /// (MIT Licensed) //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief pad the given string to the maximum width provided //////////////////////////////////////////////////////////////////////////////// function lpad (str, width) { str = String(str); var n = width - str.length; if (n < 1) { return str; } while (n--) { str = ' ' + str; } return str; } //////////////////////////////////////////////////////////////////////////////// /// @brief pad the given string to the maximum width provided //////////////////////////////////////////////////////////////////////////////// function rpad (str, width) { str = String(str); var n = width - str.length; if (n < 1) { return str; } while (n--) { str = str + ' '; } return str; } //////////////////////////////////////////////////////////////////////////////// /// Total coverage for the given file data. //////////////////////////////////////////////////////////////////////////////// function coverage (data, val) { var n = 0; for (var i = 0, len = data.length; i < len; ++i) { if (data[i] !== undefined && data[i] == val) { ++n; } } return n; } //////////////////////////////////////////////////////////////////////////////// /// @brief populate code coverage data //////////////////////////////////////////////////////////////////////////////// function populateCoverage (cov) { var boring = false; cov.LOC = 0; cov.SLOC = 0; cov.totalFiles = 0; cov.totalHits = 0; cov.totalMisses = 0; cov.coverage = 0; for (var name in cov) { var file = cov[name]; if (Array.isArray(file)) { ++cov.totalFiles; if (! file.source) { file.source = []; } cov.totalHits += file.totalHits = coverage(file, true); cov.totalMisses += file.totalMisses = coverage(file, false); file.totalLines = file.totalHits + file.totalMisses; cov.SLOC += file.SLOC = file.totalLines; cov.LOC += file.LOC = file.source.length; file.coverage = (file.totalHits / file.totalLines) * 100; var width = file.source.length.toString().length; file.sourceLines = file.source.map(function(line, i) { ++i; var hits = lpad(file[i] === 0 ? 0 : (file[i] || ' '), 3); if (! boring) { if (file[i] === 0) { hits = '\x1b[31m' + hits + '\x1b[0m'; line = '\x1b[41m' + line + '\x1b[0m'; } else { hits = '\x1b[32m' + hits + '\x1b[0m'; } } return '\n ' + lpad(i, width) + ' | ' + hits + ' | ' + line; }).join(''); } } cov.coverage = (cov.totalHits / cov.SLOC) * 100; } //////////////////////////////////////////////////////////////////////////////// /// @brief report test coverage in tabular format //////////////////////////////////////////////////////////////////////////////// function reportCoverage (cov, files) { print('\n Test Coverage\n'); var sep = ' +------------------------------------------+----------+------+------+--------+'; var lastSep = ' +----------+------+------+--------+'; var line; print(sep); print(' | filename | coverage | LOC | SLOC | missed |'); print(sep); for (var name in cov) { var file = cov[name]; if (Array.isArray(file)) { line = ''; line += ' | ' + rpad(name, 40); line += ' | ' + lpad(file.coverage.toFixed(2), 8); line += ' | ' + lpad(file.LOC, 4); line += ' | ' + lpad(file.SLOC, 4); line += ' | ' + lpad(file.totalMisses, 6); line += ' |'; print(line); } } print(sep); line = ''; line += ' ' + rpad('', 40); line += ' | ' + lpad(cov.coverage.toFixed(2), 8); line += ' | ' + lpad(cov.LOC, 4); line += ' | ' + lpad(cov.SLOC, 4); line += ' | ' + lpad(cov.totalMisses, 6); line += ' |'; print(line); print(lastSep); var match = null; if (files == true) { match = function(name) { return name.match(/\.js$/); } } else if (files != null) { var fl = {}; if (typeof files == "string") { fl[files] = true; } else { for (var i = 0; i < files.length; ++i) { fl[files[i]] = true; } } match = function(name) { return name in fl; } } if (match != null) { for (var name in cov) { if (match(name)) { var file = cov[name]; if (file.coverage < 100) { print('\n ' + name + ':'); print(file.sourceLines); print('\n'); } } } } } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- public functions // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup JSUnity /// @{ //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// /// @brief runs a JSUnity test file //////////////////////////////////////////////////////////////////////////////// function runTest (path) { var content; var f; try { content = SYS_READ(path); } catch (err) { console.error("cannot load test file '" + path + "'"); return; } content = "(function(jsUnity){jsUnity.attachAssertions();" + content + "})"; f = SYS_EXECUTE(content, undefined, path); if (f == undefined) { throw "cannot create context function"; } f(exports.jsUnity); } //////////////////////////////////////////////////////////////////////////////// /// @brief runs a JSUnity test file with coverage //////////////////////////////////////////////////////////////////////////////// function runCoverage (path, files) { runTest(path); populateCoverage(_$jscoverage); reportCoverage(_$jscoverage, files); } //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------------------- // --SECTION-- MODULE EXPORTS // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// /// @addtogroup JSUnity /// @{ //////////////////////////////////////////////////////////////////////////////// exports.jsUnity = jsUnity; exports.runTest = runTest; exports.runCoverage = runCoverage; //////////////////////////////////////////////////////////////////////////////// /// @} //////////////////////////////////////////////////////////////////////////////// // Local Variables: // mode: outline-minor // outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" // End: