UNPKG

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