UNPKG

6.31 kBJavaScriptView Raw
1/**
2 * This task instruments our source code with istanbul, runs the test suite
3 * on the instrumented source and collects the coverage data. It then creates
4 * test coverage reports.
5 *
6 * TODO This can be improved in style. We should possibly rewrite it and use
7 * async.waterfall.
8 */
9
10var fs = require('fs-extra');
11var istanbul = require('istanbul');
12var path = require('path');
13var glob = require('glob');
14
15var runTestsuite = require('./test').runTests;
16
17// setup some paths
18var dir = path.join(__dirname, '../src');
19var backupDir = path.join(__dirname, '../src-backup');
20var instrumentedDir = path.join(__dirname, '../src-instrumented');
21var coverageDir = path.join(__dirname, '../coverage');
22
23fs.mkdirSync(coverageDir);
24
25// The main players in the coverage generation via istanbul
26var instrumenter = new istanbul.Instrumenter();
27var reporter = new istanbul.Reporter(false, coverageDir);
28var collector = new istanbul.Collector();
29
30// General options used for the resource shuffling / directory copying
31var copyOpts = {
32 // Overwrite existing file or directory
33 clobber: true,
34 // Preserve the mtime and atime when copying files
35 preserveTimestamps: true
36};
37
38/**
39 * A small utility method printing out log messages.
40 * @param {string} msg The message.
41 */
42var log = function(msg) {
43 process.stdout.write(msg + '\n');
44};
45
46/**
47 * Creates folders for backup and instrumentation and copies the contents of the
48 * current src folder into them.
49 */
50var setupBackupAndInstrumentationDir = function() {
51 if (!fs.existsSync(backupDir)) {
52 log('• create directory for backup of src: ' + backupDir);
53 fs.mkdirSync(backupDir);
54 }
55
56 if (!fs.existsSync(instrumentedDir)) {
57 log('• create directory for instrumented src: ' + instrumentedDir);
58 fs.mkdirSync(instrumentedDir);
59 }
60
61 log('• copy src files to backup folder');
62 fs.copySync(dir, backupDir, copyOpts);
63 log('• copy src files to instrumentation folder');
64 fs.copySync(dir, instrumentedDir, copyOpts);
65};
66
67/**
68 * Reverts the changes done in setupBackupAndInstrumentationDir, copies the
69 * backup over the src directory and removes the instrumentation and backup
70 * directory.
71 */
72var revertBackupAndInstrumentationDir = function() {
73 log('• copy original src back to src folder');
74 fs.copySync(backupDir, dir, copyOpts);
75 log('• delete backup directory');
76 fs.removeSync(backupDir);
77 log('• delete instrumentation directory');
78 fs.removeSync(instrumentedDir);
79};
80
81/**
82 * Callback for when runTestsuite() has finished.
83 */
84var collectAndWriteCoverageData = function() {
85 log('• collect data from coverage *.json files');
86
87 var coverageFiles = [
88 path.join(__dirname, '..', 'coverage', 'coverage.json'),
89 path.join(__dirname, '..', 'coverage', 'coverage-rendering.json')
90 ];
91 coverageFiles.forEach(function(coverageFile) {
92 if (fs.existsSync(coverageFile)) {
93 log(' • collect data from ' + path.basename(coverageFile));
94 var coverageJson = JSON.parse(fs.readFileSync(coverageFile, 'utf8'));
95 collector.add(coverageJson);
96 }
97 });
98
99 reporter.addAll(['lcovonly', 'html']);
100
101 revertBackupAndInstrumentationDir();
102
103 log('• write report from collected data');
104 reporter.write(collector, true, function() {
105 process.exit(0);
106 });
107};
108
109/**
110 * Runs the rendering test by spawning a call to `make test-rendering`. The
111 * `make`-call sets up certain things so that the rendering tests can actually
112 * run, which is why we call it this way.
113 *
114 * @param {Function} callback The callback to invoke once `make` has exited.
115 * Will receive the exit code.
116 */
117var runRenderingTestsuite = function(callback) {
118 var spawn = require('child_process').spawn;
119 var child = spawn('make', ['test-rendering'], {stdio: 'inherit'});
120 child.on('exit', function(code) {
121 callback(code);
122 });
123};
124
125/**
126 * Derive output file name from input file name, by replacing the *last*
127 * occurrence of `/src/` by `/src-instrumented/`
128 *
129 * @param {String} file The input filename.
130 * @return {String} file The output filename.
131 */
132var outputFilenameByFilename = function(file) {
133 var search = '/src/';
134 var replace = '/src-instrumented/';
135 var re = new RegExp(search, 'g');
136 var m, match;
137 while ((m = re.exec(file)) !== null) {
138 match = m;
139 }
140 var idx = match.index;
141 var outfile = file.substr(0, idx) + replace +
142 file.substr(idx + search.length);
143
144 return outfile;
145};
146
147/**
148 * Will instrument all JavaScript files that are passed as second parameter.
149 * This is the callback to the glob call.
150 * @param {Error} err Any error.
151 * @param {Array.<string>} files List of file paths.
152 */
153var foundAllJavaScriptSourceFiles = function(err, files) {
154 if (err) {
155 process.stderr.write(err.message + '\n');
156 process.exit(1);
157 }
158 log('• instrumenting every src file');
159 var cnt = 0;
160 files.forEach(function(file) {
161 cnt++;
162 var content = fs.readFileSync(file, 'utf-8');
163 var outfile = outputFilenameByFilename(file);
164 var instrumented = instrumenter.instrumentSync(content, file);
165 fs.writeFileSync(outfile, instrumented);
166 if (cnt % 10 === 0) {
167 log(' • instrumented ' + cnt + ' files');
168 }
169 });
170 log(' • done. ' + cnt + ' files instrumented');
171 log('• copy instrumented src back to src folder');
172
173 fs.copySync(instrumentedDir, dir, copyOpts);
174
175 log('• run test suites on instrumented code');
176
177 log(' • run rendering test suite');
178 runRenderingTestsuite(function(codeRendering) {
179 if (codeRendering === 0) {
180 log(' • run standard test suite');
181 runTestsuite({coverage: true, reporter: 'dot'}, function(code) {
182 if (code === 0) {
183 collectAndWriteCoverageData();
184 } else {
185 process.stderr.write('Trouble running the standard testsuite\n');
186 process.exit(1);
187 }
188 });
189 } else {
190 process.stderr.write('Trouble running the rendering testsuite\n');
191 process.exit(1);
192 }
193 });
194};
195
196/**
197 * Our main method, first it sets up certain directory, and then it starts the
198 * coverage process by gathering all JavaScript files and then instrumenting
199 * them.
200 */
201var main = function() {
202 setupBackupAndInstrumentationDir();
203 glob(dir + '/**/*.js', {}, foundAllJavaScriptSourceFiles);
204};
205
206if (require.main === module) {
207 main();
208}
209
210module.exports = main;