1 |
|
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 |
|
25 |
|
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 |
|
56 |
|
57 |
|
58 |
|
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 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function buildArgs(sonarOptions, data) {
|
77 |
|
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 |
|
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 |
|
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 |
|
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 |
|
168 | mergeCoverageReports: function (callback) {
|
169 | grunt.verbose.writeln('Merging Coverage reports');
|
170 | coverage.merge(data.paths, coverageResultFile, 'coverage');
|
171 | callback(null, 200);
|
172 | },
|
173 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 | })();
|