Skip to content

Commit 05b229d

Browse files
committed
Exposed getRepoPath and listSubmodules; added test to listSubmodules; refactored coverage
1 parent 7c2d0b1 commit 05b229d

File tree

10 files changed

+183
-115
lines changed

10 files changed

+183
-115
lines changed

coverage.js

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var token = process.env.COVERALLS_TOKEN;
3333
* Loads file containing source code, returns a hash and line count
3434
* @param {String} path - Path to the source code file.
3535
* @returns {Object} key `Hash` contains MD5 digest string of file; `count` contains number of lines in source file
36+
* @todo Make asynchronous
3637
*/
3738
function md5(path) {
3839
var hash = crypto.createHash('md5'); // Creating hash object
@@ -58,12 +59,13 @@ function md5(path) {
5859
function formatCoverage(classList, srcPath, sha) {
5960
var job = {};
6061
var sourceFiles = [];
62+
var digest;
6163
srcPath = typeof srcPath != "undefined" ? srcPath : process.env.HOMEPATH; // default to home dir
6264
// For each class, create file object containing array of lines covered and add to sourceFile array
6365
classList.forEach( async c => {
6466
let file = {}; // Initialize file object
6567
let fullPath = c.$.filename.startsWith(srcPath)? c.$.filename : path.join(srcPath, c.$.filename);
66-
var digest = md5(fullPath); // Create digest and line count for file // FIXME use path lib
68+
digest = md5(fullPath); // Create digest and line count for file
6769
let lines = new Array(digest.count).fill(null); // Initialize line array the size of source code file
6870
c.lines[0].line.forEach(ln => {
6971
let n = Number(ln.$.number);
@@ -92,19 +94,18 @@ function formatCoverage(classList, srcPath, sha) {
9294
* @param {String} path - Path to the XML file containing coverage information.
9395
* @param {String} sha - The commit SHA for this coverage test
9496
* @param {String} repo - The repo to which the commit belongs
97+
* @param {Array} submodules - A list of submodules for separating coverage into
9598
* @param {function} callback - The callback function to run when complete
96-
* @todo Generalize ignoring of submodules
9799
* @see {@link https://github.com/cobertura/cobertura/wiki|Cobertura Wiki}
98100
*/
99-
function coverage(path, repo, sha, callback) {
101+
function coverage(path, repo, sha, submodules, callback) {
100102
cb = callback; // @fixme Making callback global feels hacky
101103
fs.readFile(path, function(err, data) { // Read in XML file
102-
// @fixme deal with file not found errors
103-
if (err) {throw err}
104+
if (err) {throw err} // @fixme deal with file not found errors
104105
parser.parseString(data, function (err, result) { // Parse XML
105106
// Extract root code path
106107
const rootPath = (result.coverage.sources[0].source[0] || process.env.REPO_PATH).replace(/[\/|\\]+$/, '')
107-
assert(rootPath.toLowerCase().endsWith(repo || process.env.REPO_NAME), 'Incorrect source code repository')
108+
assert(rootPath.endsWith(process.env.REPO_NAME), 'Incorrect source code repository')
108109
timestamp = new Date(result.coverage.$.timestamp*1000); // Convert UNIX timestamp to Date object
109110
let classes = []; // Initialize classes array
110111

@@ -113,19 +114,22 @@ function coverage(path, repo, sha, callback) {
113114
classes = classes.reduce((acc, val) => acc.concat(val), []); // Flatten
114115

115116
// The submodules
116-
var modules = {'main' : [], 'alyx-matlab' : [], 'signals' : [], 'npy-matlab' : [], 'wheelAnalysis' : []};
117+
const byModule = {'main' : []};
118+
submodules.forEach((x) => { byModule[x] = []; }); // initialize submodules
119+
117120
// Sort into piles
118-
modules['main'] = classes.filter(function (e) {
121+
byModule['main'] = classes.filter(function (e) {
119122
if (e.$.filename.search(/(tests\\|_.*test|docs\\)/i) !== -1) {return false;} // Filter out tests and docs
120123
if (!Array.isArray(e.lines[0].line)) {return false;} // Filter out files with no functional lines
121-
if (e.$.filename.startsWith('alyx-matlab\\')) {modules['alyx-matlab'].push(e); return false;}
122-
if (e.$.filename.startsWith('signals\\')) {modules.signals.push(e); return false;}
123-
if (e.$.filename.startsWith('npy-matlab\\')) {modules['npy-matlab'].push(e); return false;}
124-
if (e.$.filename.startsWith('wheelAnalysis\\')) {modules.wheelAnalysis.push(e); return false;}
125-
else {return true}
124+
for (let submodule of submodules) {
125+
if (e.$.filename.startsWith(submodule)) {
126+
byModule[submodule].push(e); return false;
127+
}
128+
}
129+
return true;
126130
});
127131
// Select module
128-
modules = modules[repo.toLowerCase()] || modules['main'];
132+
let modules = byModule[repo] || byModule['main'];
129133
formatCoverage(modules, rootPath, callback);
130134
});
131135
});

lib.js

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const path = require('path');
77
const createDebug = require('debug');
88
const localtunnel = require('localtunnel');
99
const kill = require('tree-kill');
10+
const shell = require('shelljs');
1011

1112
const config = require('./config/config').settings;
1213
const Coverage = require('./coverage');
@@ -175,19 +176,6 @@ function partial(func) {
175176
};
176177
}
177178

178-
function chain(func) {
179-
return function curried(...args) {
180-
if (args.length >= func.length) {
181-
return func.apply(this, args);
182-
} else {
183-
return function(...args2) {
184-
return curried.apply(this, args2.concat(args));
185-
}
186-
}
187-
};
188-
}
189-
190-
191179

192180
/**
193181
* Check if job already has record, if so, update from record and finish, otherwise call tests function.
@@ -235,6 +223,42 @@ const openTunnel = async () => {
235223
}
236224

237225

226+
/**
227+
* Lists the submodules within a Git repository. If none are found null is returned.
228+
* @param {String} repoPath - The path of the repository
229+
* @returns {Array} A list of submodule names, or null if none were found
230+
*/
231+
function listSubmodules(repoPath) {
232+
if (!shell.which('git')) { throw new Error('Git not found on path'); }
233+
shell.pushd(repoPath);
234+
let listModules = 'git config --file .gitmodules --get-regexp path';
235+
const modules = shell.exec(listModules)
236+
shell.popd();
237+
return (!modules.code && modules.stdout !== '')? modules.match(/(?<=submodule.)[\w-]+/g) : [];
238+
}
239+
240+
241+
/**
242+
* Get the corresponding repository path for a given repo. The function first checks the settings.
243+
* If the `repos` field doesn't exist, the path in ENV is used. If the name is not a key in the
244+
* `repos` object then we check each repo path for submodules and return the first matching
245+
* submodule path. Otherwise returns null.
246+
* @param {String} name - The name of the repository
247+
* @returns {String} The repository path if found
248+
*/
249+
function getRepoPath(name) {
250+
if (!config.repos) { return process.env['REPO_PATH']; } // Legacy, to remove
251+
if (config.repos[name]) { return config.repos[name]; } // Found path, return
252+
const modules = listSubmodules(process.env['REPO_PATH']);
253+
let repoPath = process.env['REPO_PATH'];
254+
if (modules && modules.includes(name)) {
255+
// If the repo is a submodule, modify path
256+
repoPath += (path.sep + name);
257+
}
258+
return repoPath; // No modules matched, return default
259+
}
260+
261+
238262
/**
239263
* Starts a timer with a callback to kill the job's process.
240264
* @param {Object} job - The Job with an associated process in the data field.
@@ -263,8 +287,9 @@ function computeCoverage(job) {
263287
return;
264288
}
265289
console.log('Updating coverage for job #' + job.id)
266-
let xmlPath = path.join(config.dataPath, 'reports', job.data.sha, 'CoverageResults.xml')
267-
Coverage(xmlPath, job.data.repo, job.data.sha, obj => {
290+
const xmlPath = path.join(config.dataPath, 'reports', job.data.sha, 'CoverageResults.xml')
291+
const modules = listSubmodules(process.env.REPO_PATH);
292+
Coverage(xmlPath, job.data.repo, job.data.sha, modules, obj => {
268293
// Digest and save percentage coverage
269294
let misses = 0, hits = 0;
270295
for (let file of obj.source_files) {
@@ -397,7 +422,7 @@ function getBadgeData(data) {
397422
record = Array.isArray(record) ? record.pop() : record; // in case of duplicates, take last
398423
switch (data.context) {
399424
case 'status':
400-
if (record['status'] === 'error' || !record['coverage']) {
425+
if (record['status'] === 'error') {
401426
report['message'] = 'unknown';
402427
report['color'] = 'orange';
403428
} else {
@@ -433,5 +458,5 @@ class APIError extends Error {
433458
module.exports = {
434459
ensureArray, loadTestRecords, compareCoverage, computeCoverage, getBadgeData, log, shortID,
435460
openTunnel, APIError, queue, partial, startJobTimer, updateJobFromRecord, shortCircuit, isSHA,
436-
fullpath, strToBool, saveTestRecords
461+
fullpath, strToBool, saveTestRecords, listSubmodules, getRepoPath
437462
}

runAllTests.m

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,8 @@ function runAllTests(id, repo, logDir)
4949
all_tests = all_tests(startsWith({all_tests.Name}, 'Alyx', 'IgnoreCase', true));
5050
elseif strcmp(repo, 'alyx-matlab')
5151
all_tests = alyx_tests;
52-
root = fullfile(root, repo);
5352
elseif strcmp(repo, 'signals')
5453
all_tests = signals_tests;
55-
root = fullfile(root, repo);
5654
end
5755

5856
% Filter out performance tests

serve.js

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ function runTests(job) {
222222

223223
// Go ahead with tests
224224
const sha = job.data['sha'];
225-
const repoPath = getRepoPath(job.data.repo);
225+
const repoPath = lib.getRepoPath(job.data.repo);
226226
const logName = path.join(config.dataPath, 'reports', sha, `std_output-${lib.shortID(sha)}.log`);
227227
let fcn = lib.fullpath(config.test_function);
228228
debug('starting test child process %s', fcn);
@@ -278,7 +278,7 @@ function runTests(job) {
278278

279279
function prepareEnv(job, callback) {
280280
log('Preparing environment for job #%g', job.id)
281-
const repoPath = getRepoPath(job.data.repo);
281+
const repoPath = lib.getRepoPath(job.data.repo);
282282
switch (config.setup_function) {
283283
case undefined:
284284
// run some basic git commands
@@ -342,41 +342,6 @@ function checkout(repoPath, ref) {
342342
}
343343

344344

345-
/**
346-
* Lists the submodules within a Git repository. If none are found null is returned.
347-
* @param {String} repoPath - The path of the repository
348-
* @returns {Array} A list of submodule names, or null if none were found
349-
*/
350-
function listSubmodules(repoPath) {
351-
if (!shell.which('git')) { throw new Error('Git not found on path'); }
352-
shell.pushd(repoPath);
353-
let listModules = 'git config --file .gitmodules --get-regexp path';
354-
const modules = shell.exec(listModules)
355-
shell.popd();
356-
return (!modules.code && modules.stdout !== '')? modules.match(/(?<=submodule.)[\w-]+/g) : null;
357-
}
358-
359-
/**
360-
* Get the corresponding repository path for a given repo. The function first checks the settings.
361-
* If the `repos` field doesn't exist, the path in ENV is used. If the name is not a key in the
362-
* `repos` object then we check each repo path for submodules and return the first matching
363-
* submodule path. Otherwise returns null.
364-
* @param {String} name - The name of the repository
365-
* @returns {String} The repository path if found
366-
*/
367-
function getRepoPath(name) {
368-
if (!config.repos) { return process.env['REPO_PATH']; } // Legacy, to remove
369-
if (config.repos[name]) { return config.repos[name]; } // Found path, return
370-
const modules = listSubmodules(process.env['REPO_PATH']);
371-
let repoPath = process.env['REPO_PATH'];
372-
if (modules && modules.includes(name)) {
373-
// If the repo is a submodule, modify path
374-
repoPath += (path.sep + name);
375-
}
376-
return repoPath; // No modules matched, return default
377-
}
378-
379-
380345
///////////////////// OTHER /////////////////////
381346

382347
/**
@@ -578,6 +543,5 @@ queue.on('finish', (err, job) => { // On job end post result to API
578543
});
579544

580545
module.exports = {
581-
updateStatus, srv, handler, setAccessToken, prepareEnv,
582-
runTests, eventCallback, getRepoPath, listSubmodules
546+
updateStatus, srv, handler, setAccessToken, prepareEnv, runTests, eventCallback
583547
}

test/coverage.test.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ describe('Test coverage parser:', function() {
1818
let md5 = '385a5d56850127317c317b0f66e91078';
1919
let code = 'line1\nline2\n\rline3\n\rline4';
2020
testable = function(obj, done) {
21-
expect([291, 63]).to.include(obj.source_files.length);
21+
expect([496, 63]).to.include(obj.source_files.length);
2222
let file = obj.source_files[0];
2323
expect(file).to.have.all.keys('name', 'source_digest', 'coverage');
2424
expect(file['source_digest']).to.eq(md5);
@@ -27,21 +27,19 @@ describe('Test coverage parser:', function() {
2727
sandbox = sinon.createSandbox();
2828
sandbox
2929
.stub(fs, 'readFileSync')
30-
.withArgs(sinon.match((x) => x.replace('\\', '/').startsWith('C:/Rigbox')))
31-
.returns(code)
32-
.withArgs(sinon.match((x) => x.replace('\\', '/').startsWith('C:/ibllib')))
30+
.withArgs(sinon.match((x) => x.replace('\\', '/').startsWith('C:/Hello-World')))
3331
.returns(code);
3432
fs.readFileSync.callThrough();
3533
})
3634

3735
it('Check loading MATLAB', function (done) {
3836
let xmlPath = path.resolve('test', 'fixtures', 'CoverageResults.mat.xml')
39-
Coverage(xmlPath, 'rigbox', dummy_id, obj => testable(obj, done) );
37+
Coverage(xmlPath, 'Hello-World', dummy_id, [], obj => testable(obj, done) );
4038
});
4139

4240
it('Check loading Python', function (done) {
4341
let xmlPath = path.resolve('test', 'fixtures', 'CoverageResults.py.xml')
44-
Coverage(xmlPath, 'ibllib', dummy_id, obj => testable(obj, done) );
42+
Coverage(xmlPath, 'Hello-World', dummy_id, [], obj => testable(obj, done) );
4543
});
4644

4745
afterEach(function () { sandbox.restore(); });

test/fixtures/CoverageResults.mat.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
timestamp="1603119812.882"
77
version="">
88
<sources>
9-
<source>C:\Rigbox\</source>
9+
<source>C:\Hello-World</source>
1010
</sources>
1111
<packages>
1212
<package branch-rate="NaN" complexity="NaN" line-rate="0.31707" name="">

test/fixtures/CoverageResults.py.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
44
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
55
<sources>
6-
<source>C:/ibllib/</source>
6+
<source>C:\Hello-World</source>
77
</sources>
88
<packages>
99
<package branch-rate="0" complexity="0" line-rate="0.1637" name="C:.Users.User.Documents.Github.ibllib.alf">

0 commit comments

Comments
 (0)