Skip to content
This repository was archived by the owner on Aug 14, 2020. It is now read-only.

Commit b2264e2

Browse files
Add support for projects that use xctestPlan files (#8)
xctestrun files have a different structure Code coverage must be enabled independently per testplan since -enableCodeCoverage YES in xcodebuild doen't work with testplans
1 parent 42c43d1 commit b2264e2

File tree

2 files changed

+160
-18
lines changed

2 files changed

+160
-18
lines changed

dist/index.js

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ const fetch = __webpack_require__(735);
5252
const fs = __webpack_require__(747);
5353
const semver = __webpack_require__(105);
5454
const os = __webpack_require__(87);
55+
const path = __webpack_require__(622);
56+
5557

5658
const temp = os.tmpdir();
5759
const SCOPE_DSN = 'SCOPE_DSN';
@@ -100,7 +102,10 @@ async function run() {
100102
if (!fs.existsSync(scopeDir)){
101103
fs.mkdirSync(scopeDir);
102104
}
103-
createXCConfigFile(configFilePath)
105+
createXCConfigFile(configFilePath);
106+
107+
//enableCodeCoverage in xcodebuild doesn't work with test plans, configure them before
108+
configureTestPlansForCoverage(projectParameter, scheme)
104109

105110
//download scope
106111
await downloadLatestScope();
@@ -112,28 +117,42 @@ async function run() {
112117

113118
uploadSymbols(projectParameter, scheme, dsn);
114119

120+
//Fol all testruns that are configured
121+
let testRuns = await getXCTestRuns();
122+
let testError;
123+
124+
for( const testRun of testRuns ) {
115125
//modify xctestrun with Scope variables
116-
let testRun = await getXCTestRun();
126+
117127
let plutilExportCommand = 'plutil -convert json -o ' + testrunJson + ' ' + testRun;
118128
await exec.exec(plutilExportCommand, null, null );
119129

120130
let jsonString = fs.readFileSync(testrunJson, "utf8");
121131
const testTargets = JSON.parse(jsonString);
122132

123-
124133
for( const target of Object.keys(testTargets) ) {
125134
if( target.charAt(0) !== '_' ) {
135+
if( target['TestingEnvironmentVariables']) {
126136
await insertEnvVariables(testRun, target, dsn)
137+
} else if ( target === 'TestConfigurations') {
138+
let configurationNumber = 0;
139+
for( const configuration of testTargets['TestConfigurations'] ) {
140+
let testNumber = 0;
141+
for( const test of configuration['TestTargets'] ) {
142+
await insertEnvVariables(testRun, target + '.' + configurationNumber + '.' + 'TestTargets' + '.' + testNumber, dsn)
143+
}
144+
}
145+
}
127146
}
128147
}
129148
//run tests
130149
let testCommand = 'xcodebuild test-without-building -enableCodeCoverage YES -xctestrun ' + testRun + ' -destination \"' + destination + '\"';
131-
let testError;
132150
try {
133151
await exec.exec(testCommand, null, null);
134152
} catch (error) {
135153
testError = error.message
136154
}
155+
}
137156

138157
//build command settings
139158
let buildCommandSettings = 'xcodebuild -showBuildSettings -json -configuration '+ configuration + ' build-for-testing -xcconfig ' + configFilePath + ' ' + projectParameter +
@@ -200,7 +219,7 @@ async function getXCodeProj() {
200219

201220
async function generateProjectFromSPM() {
202221
if( fs.existsSync('Package_iOS.swift')) {
203-
fs.renameSync('Package.swift', 'Package_orig.swift')
222+
fs.renameSync('Package.swift', 'Package_orig.swift');
204223
fs.renameSync('Package_iOS.swift', 'Package.swift')
205224
}
206225
let generateProjectCommand = 'swift package generate-xcodeproj';
@@ -308,18 +327,21 @@ function uploadSymbols(projectParameter, scheme, dsn) {
308327
})
309328
}
310329

311-
async function getXCTestRun() {
330+
async function getXCTestRuns() {
312331
let myOutput = '';
313-
let testRun = '';
332+
let testRuns = [''];
314333
const options = {};
315334
options.listeners = {
316335
stdout: (data) => {
317336
myOutput += data.toString();
318-
testRun = myOutput.split("\n").find(function(file) { return file.match(/\.xctestrun$/); });
337+
testRuns = myOutput.split("\n").filter(function(file) { return file.match(/\.xctestrun$/); });
319338
}
320339
};
321340
await exec.exec('ls ' + xctestDir, null, options);
322-
return xctestDir + testRun
341+
testRuns.forEach(function(part, index, theArray) {
342+
theArray[index] = xctestDir + part;
343+
});
344+
return testRuns
323345
}
324346

325347
async function runScopeCoverageWithSettings(buildSettings, dsn) {
@@ -357,6 +379,55 @@ async function insertEnvVariable( name, value, file, target) {
357379
}
358380
}
359381

382+
async function configureTestPlansForCoverage( projectParameter, scheme ) {
383+
//Check if project is configured with test plans
384+
let showTestPlansCommand = 'xcodebuild -showTestPlans -json ' + projectParameter + ' -scheme ' + scheme;
385+
let auxOutput = '';
386+
const options = {};
387+
options.listeners = {
388+
stdout: (data) => {
389+
auxOutput += data.toString();
390+
}
391+
};
392+
await exec.exec(showTestPlansCommand, null, options);
393+
const showTestPlans = JSON.parse(auxOutput);
394+
if( showTestPlans.testPlans === null ) {
395+
return;
396+
}
397+
398+
//If uses testplan configure to use code coverage
399+
let file_list = recFindByExt('.','xctestplan');
400+
for(let testPlanFile of file_list ){
401+
let rawdata = fs.readFileSync(testPlanFile);
402+
let testPlan = JSON.parse(rawdata);
403+
testPlan.defaultOptions.codeCoverage = true;
404+
fs.writeFileSync(testPlanFile, JSON.stringify(testPlan));
405+
}
406+
}
407+
408+
function recFindByExt(base,ext,files,result)
409+
{
410+
files = files || fs.readdirSync(base);
411+
result = result || [];
412+
413+
files.forEach(
414+
function (file) {
415+
var newbase = path.join(base,file);
416+
if ( fs.statSync(newbase).isDirectory() )
417+
{
418+
result = recFindByExt(newbase,ext,fs.readdirSync(newbase),result)
419+
}
420+
else
421+
{
422+
if ( file.substr(-1*(ext.length+1)) === '.' + ext )
423+
{
424+
result.push(newbase)
425+
}
426+
}
427+
}
428+
);
429+
return result
430+
}
360431

361432
run();
362433

index.js

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ const fetch = require('node-fetch');
44
const fs = require('fs');
55
const semver = require('semver');
66
const os = require('os');
7+
const path = require('path');
8+
79

810
const temp = os.tmpdir();
911
const SCOPE_DSN = 'SCOPE_DSN';
@@ -52,7 +54,10 @@ async function run() {
5254
if (!fs.existsSync(scopeDir)){
5355
fs.mkdirSync(scopeDir);
5456
}
55-
createXCConfigFile(configFilePath)
57+
createXCConfigFile(configFilePath);
58+
59+
//enableCodeCoverage in xcodebuild doesn't work with test plans, configure them before
60+
configureTestPlansForCoverage(projectParameter, scheme)
5661

5762
//download scope
5863
await downloadLatestScope();
@@ -64,28 +69,42 @@ async function run() {
6469

6570
uploadSymbols(projectParameter, scheme, dsn);
6671

72+
//Fol all testruns that are configured
73+
let testRuns = await getXCTestRuns();
74+
let testError;
75+
76+
for( const testRun of testRuns ) {
6777
//modify xctestrun with Scope variables
68-
let testRun = await getXCTestRun();
78+
6979
let plutilExportCommand = 'plutil -convert json -o ' + testrunJson + ' ' + testRun;
7080
await exec.exec(plutilExportCommand, null, null );
7181

7282
let jsonString = fs.readFileSync(testrunJson, "utf8");
7383
const testTargets = JSON.parse(jsonString);
7484

75-
7685
for( const target of Object.keys(testTargets) ) {
7786
if( target.charAt(0) !== '_' ) {
87+
if( target['TestingEnvironmentVariables']) {
7888
await insertEnvVariables(testRun, target, dsn)
89+
} else if ( target === 'TestConfigurations') {
90+
let configurationNumber = 0;
91+
for( const configuration of testTargets['TestConfigurations'] ) {
92+
let testNumber = 0;
93+
for( const test of configuration['TestTargets'] ) {
94+
await insertEnvVariables(testRun, target + '.' + configurationNumber + '.' + 'TestTargets' + '.' + testNumber, dsn)
95+
}
96+
}
97+
}
7998
}
8099
}
81100
//run tests
82101
let testCommand = 'xcodebuild test-without-building -enableCodeCoverage YES -xctestrun ' + testRun + ' -destination \"' + destination + '\"';
83-
let testError;
84102
try {
85103
await exec.exec(testCommand, null, null);
86104
} catch (error) {
87105
testError = error.message
88106
}
107+
}
89108

90109
//build command settings
91110
let buildCommandSettings = 'xcodebuild -showBuildSettings -json -configuration '+ configuration + ' build-for-testing -xcconfig ' + configFilePath + ' ' + projectParameter +
@@ -152,7 +171,7 @@ async function getXCodeProj() {
152171

153172
async function generateProjectFromSPM() {
154173
if( fs.existsSync('Package_iOS.swift')) {
155-
fs.renameSync('Package.swift', 'Package_orig.swift')
174+
fs.renameSync('Package.swift', 'Package_orig.swift');
156175
fs.renameSync('Package_iOS.swift', 'Package.swift')
157176
}
158177
let generateProjectCommand = 'swift package generate-xcodeproj';
@@ -260,18 +279,21 @@ function uploadSymbols(projectParameter, scheme, dsn) {
260279
})
261280
}
262281

263-
async function getXCTestRun() {
282+
async function getXCTestRuns() {
264283
let myOutput = '';
265-
let testRun = '';
284+
let testRuns = [''];
266285
const options = {};
267286
options.listeners = {
268287
stdout: (data) => {
269288
myOutput += data.toString();
270-
testRun = myOutput.split("\n").find(function(file) { return file.match(/\.xctestrun$/); });
289+
testRuns = myOutput.split("\n").filter(function(file) { return file.match(/\.xctestrun$/); });
271290
}
272291
};
273292
await exec.exec('ls ' + xctestDir, null, options);
274-
return xctestDir + testRun
293+
testRuns.forEach(function(part, index, theArray) {
294+
theArray[index] = xctestDir + part;
295+
});
296+
return testRuns
275297
}
276298

277299
async function runScopeCoverageWithSettings(buildSettings, dsn) {
@@ -309,5 +331,54 @@ async function insertEnvVariable( name, value, file, target) {
309331
}
310332
}
311333

334+
async function configureTestPlansForCoverage( projectParameter, scheme ) {
335+
//Check if project is configured with test plans
336+
let showTestPlansCommand = 'xcodebuild -showTestPlans -json ' + projectParameter + ' -scheme ' + scheme;
337+
let auxOutput = '';
338+
const options = {};
339+
options.listeners = {
340+
stdout: (data) => {
341+
auxOutput += data.toString();
342+
}
343+
};
344+
await exec.exec(showTestPlansCommand, null, options);
345+
const showTestPlans = JSON.parse(auxOutput);
346+
if( showTestPlans.testPlans === null ) {
347+
return;
348+
}
349+
350+
//If uses testplan configure to use code coverage
351+
let file_list = recFindByExt('.','xctestplan');
352+
for(let testPlanFile of file_list ){
353+
let rawdata = fs.readFileSync(testPlanFile);
354+
let testPlan = JSON.parse(rawdata);
355+
testPlan.defaultOptions.codeCoverage = true;
356+
fs.writeFileSync(testPlanFile, JSON.stringify(testPlan));
357+
}
358+
}
359+
360+
function recFindByExt(base,ext,files,result)
361+
{
362+
files = files || fs.readdirSync(base);
363+
result = result || [];
364+
365+
files.forEach(
366+
function (file) {
367+
var newbase = path.join(base,file);
368+
if ( fs.statSync(newbase).isDirectory() )
369+
{
370+
result = recFindByExt(newbase,ext,fs.readdirSync(newbase),result)
371+
}
372+
else
373+
{
374+
if ( file.substr(-1*(ext.length+1)) === '.' + ext )
375+
{
376+
result.push(newbase)
377+
}
378+
}
379+
}
380+
);
381+
return result
382+
}
312383

313384
run();

0 commit comments

Comments
 (0)