UNPKG

12.8 kBJavaScriptView Raw
1/*globals process */
2(function () {
3 'use strict';
4
5 module.exports = function (grunt) {
6 var glob = require('glob'),
7 fs = require('fs-extra'),
8 path = require('path'),
9 async = require('async'),
10 _ = require('lodash'),
11 xunit = require('./xunit.js')(grunt),
12 junit = require('./junit.js')(grunt),
13 coverage = require('./coverage.js')(grunt),
14 defaultOptions = {
15 dynamicAnalysis: 'reuseReports',
16 sourceEncoding: 'UTF-8',
17 language: 'js',
18 defaultOutputDir: '.tmp/sonar/',
19 scmDisabled: true,
20 excludedProperties: []
21 };
22
23 /**
24 * Copy files to the temp directory.
25 * @param g The glob.
26 */
27 function copyFiles(g, defaultOutputDir, targetDir) {
28 var files = glob.sync(g.src.toString(), {cwd: g.cwd, root: '/'});
29 files.forEach(function (file) {
30 var destinationDirectory = defaultOutputDir + path.sep + targetDir;
31 var fileDirectory = path.dirname(file);
32 if (fileDirectory !== '.') {
33 destinationDirectory = destinationDirectory + path.sep + fileDirectory;
34 }
35 fs.mkdirpSync(destinationDirectory);
36 var source = path.resolve(g.cwd, file);
37 var extension = path.extname(file);
38 var destination;
39 if (targetDir === 'test') {
40 var base = path.basename(file, extension);
41 if (extension === '.js') {
42 destination = destinationDirectory + path.sep + path.basename(base.replace(/\./g, '_') + extension);
43 } else if (extension === '.feature') {
44 destination = destinationDirectory + path.sep + path.basename(base.concat(extension).replace(/\./g, '_') + '.js');
45 }
46 } else {
47 destination = destinationDirectory + path.sep + path.basename(file);
48 }
49
50 fs.copySync(source, destination, {replace: true});
51 });
52 }
53
54 /**
55 * Deep merge json objects.
56 * @param original The original object.
57 * @param override The override object.
58 * @return merged The merged object.
59 */
60 function mergeJson(original, override) {
61 return _.merge(original, override, function (a, b, key, aParent, bParent) {
62 if (_.isUndefined(b)) {
63 aParent[key] = undefined;
64 return;
65 }
66 });
67 }
68
69 /**
70 * Builds the arguments that need to be sent to sonar-runner.
71 *
72 * @param sonarOptions The sonar options such as username, password etc.
73 * @param data The data such as project name and project version.
74 * @returns The array of command line options for sonar-runner.
75 */
76 function buildArgs(sonarOptions, data) {
77 // Default arguments
78 var args = [
79 '-Dsonar.sources=src',
80 '-Dsonar.tests=test',
81 '-Dsonar.javascript.jstestdriver.reportsPath=results',
82 '-Dsonar.genericcoverage.unitTestReportPaths=' + 'results' + path.sep + 'TESTS-junit.xml',
83 '-Dsonar.javascript.jstestdriver.itReportsPath=results',
84 '-Dsonar.javascript.lcov.reportPath=' + 'results' + path.sep + 'coverage_report.lcov',
85 '-Dsonar.javascript.lcov.itReportPath=' + 'results' + path.sep + 'it_coverage_report.lcov'
86 ];
87
88 // Add the parameter (-D) only when the 'key' exists in the 'object'
89 function addParameter(prop, object, key) {
90 var value = _.result(object, key);
91 if (value !== undefined && value !== null && sonarOptions.excludedProperties.indexOf(prop) === -1) {
92 args.push('-D' + prop + '=' + value);
93 }
94 }
95
96 addParameter('sonar.host.url', sonarOptions.instance, 'hostUrl');
97 addParameter('sonar.jdbc.url', sonarOptions.instance, 'jdbcUrl');
98 addParameter('sonar.jdbc.username', sonarOptions.instance, 'jdbcUsername');
99 addParameter('sonar.jdbc.password', sonarOptions.instance, 'jdbcPassword');
100 addParameter('sonar.login', sonarOptions.instance, 'login');
101 addParameter('sonar.password', sonarOptions.instance, 'password');
102
103 addParameter('sonar.sourceEncoding', sonarOptions, 'sourceEncoding');
104 addParameter('sonar.language', sonarOptions, 'language');
105 addParameter('sonar.dynamicAnalysis', sonarOptions, 'dynamicAnalysis');
106 addParameter('sonar.projectBaseDir', sonarOptions, 'defaultOutputDir');
107 addParameter('sonar.scm.disabled', sonarOptions, 'scmDisabled');
108
109 addParameter('sonar.projectKey', data.project, 'key');
110 addParameter('sonar.projectName', data.project, 'name');
111 addParameter('sonar.projectVersion', data.project, 'version');
112
113 addParameter('sonar.exclusions', data, 'exclusions');
114 return args;
115 }
116
117 function logArguments(message, opts) {
118 grunt.log.writeln(message, opts.args);
119 _.each(opts.args, function (arg) {
120 grunt.log.writeln(arg);
121 });
122 }
123
124 return {
125 run: function (configuration) {
126 var data = configuration.data;
127
128 if (typeof data.project === 'undefined') {
129 grunt.fail.fatal('No project information has been specified.');
130 }
131
132 if (typeof data.project.key === 'undefined') {
133 grunt.fail.fatal('Missing project key. Allowed characters are alphanumeric, \'-\', \'_\', \'.\' and \':\', with at least one non-digit.');
134 }
135
136 if (typeof data.project.name === 'undefined') {
137 grunt.fail.fatal('Missing project name. Please provide one.');
138 }
139
140 if (typeof data.paths === 'undefined' || data.paths.length === 0) {
141 grunt.fail.fatal('No paths provided. Please provide at least one.');
142 }
143
144 var sonarOptions = mergeJson(defaultOptions, configuration.options({})),
145 done = configuration.async(),
146 resultsDir = sonarOptions.defaultOutputDir + path.sep + 'results' + path.sep,
147 xUnitResultFile = resultsDir + 'TESTS-xunit.xml',
148 jUnitResultFile = resultsDir + 'TESTS-junit.xml',
149 itJUnitResultFile = resultsDir + 'ITESTS-xunit.xml',
150 coverageResultFile = resultsDir + 'coverage_report.lcov',
151 itCoverageResultFile = resultsDir + 'it_coverage_report.lcov';
152
153 async.series({
154 // #1
155 mergeJUnitReports: function (callback) {
156 grunt.verbose.writeln('Merging JUnit reports');
157 xunit.merge(data.paths, xUnitResultFile, 'unit');
158 junit.convert(xUnitResultFile, jUnitResultFile, 'unit');
159 callback(null, 200);
160 },
161 // #2
162 mergeItJUnitReports: function (callback) {
163 grunt.verbose.writeln('Merging Integration JUnit reports');
164 xunit.merge(data.paths, itJUnitResultFile, 'itUnit');
165 callback(null, 200);
166 },
167 // #3
168 mergeCoverageReports: function (callback) {
169 grunt.verbose.writeln('Merging Coverage reports');
170 coverage.merge(data.paths, coverageResultFile, 'coverage');
171 callback(null, 200);
172 },
173 // #4
174 mergeItCoverageReports: function (callback) {
175 grunt.verbose.writeln('Merging Integration Coverage reports');
176 coverage.merge(data.paths, itCoverageResultFile, 'itCoverage');
177 callback(null, 200);
178 },
179 // #5
180 copy: function (callback) {
181 grunt.verbose.writeln('Copying files to working directory [' + sonarOptions.defaultOutputDir + ']');
182 var sourceGlobs = [],
183 testGlobs = [];
184
185 data.paths.forEach(function (p) {
186 var cwd = p.cwd ? p.cwd : '.';
187 copyFiles({
188 cwd: cwd + path.sep + p.src,
189 src: '**/*.*'
190 }, sonarOptions.defaultOutputDir, p.src);
191 copyFiles({
192 cwd: cwd + path.sep + p.test,
193 src: '**/*.js'
194 }, sonarOptions.defaultOutputDir, p.test);
195 copyFiles({
196 cwd: cwd + path.sep + p.test,
197 src: '**/*.feature'
198 }, sonarOptions.defaultOutputDir, p.test);
199 });
200
201 callback(null, 200);
202 },
203
204 // #6
205 publish: function (callback) {
206 var extension = (/^win/.test(process.platform) ? '.bat' : '');
207 var opts = {
208 cmd: 'sonar-runner' + extension,
209 args: buildArgs(sonarOptions, data),
210 opts: {
211 stdio: 'inherit'
212 }
213 };
214
215 var libDir = path.join(__dirname, '..', 'lib');
216 if (grunt.file.exists(libDir)) {
217
218 glob.sync('**/bin/sonar-runner' + extension, {
219 cwd: libDir,
220 root: '/'
221 }).forEach(function (file) {
222 opts.cmd = libDir + path.sep + file;
223 });
224 }
225
226 // Add custom properties
227 if (sonarOptions.runnerProperties) {
228 Object.keys(sonarOptions.runnerProperties).forEach(function (prop) {
229 opts.args.push('-D' + prop + '=' + sonarOptions.runnerProperties[prop]);
230 });
231 }
232
233 // enable debug
234 if (sonarOptions.debug) {
235 opts.args.push('-X');
236 }
237
238 if (!sonarOptions.dryRun) {
239 if (sonarOptions.debug) {
240 logArguments('Sonar will run with the following sonar properties:', opts);
241 }
242 grunt.util.spawn(opts, function (error, result, code) {
243 if (code !== 0) {
244 return grunt.warn('The following error occured while trying to upload to sonar: ' + error);
245 } else {
246 grunt.log.writeln('Uploaded information to sonar.');
247 }
248 callback(null, 200);
249 });
250 } else {
251 grunt.log.subhead('Dry-run');
252 logArguments('Sonar would have been triggered with the following sonar properties:', opts);
253 callback(null, 200);
254 }
255 }
256 },
257 function (err) {
258 if (err !== undefined && err !== null) {
259 grunt.fail.fatal(err);
260 }
261 done();
262 });
263 }
264 };
265 };
266})();