Skip to content

Commit 87e0384

Browse files
authored
Merge pull request #81 from cortex-lab/coveralls
Coveralls
2 parents 7f4f57a + 2f163d6 commit 87e0384

File tree

11 files changed

+874
-54
lines changed

11 files changed

+874
-54
lines changed

.github/workflows/tests.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: CI
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * 0' # every Sunday at midnight
6+
workflow_dispatch: # For manual triggering
7+
push:
8+
branches: [ master ]
9+
pull_request:
10+
branches: [ master, dev ]
11+
12+
env:
13+
DOTENV_CONFIG_PATH: ./test/fixtures/.env.test
14+
15+
jobs:
16+
build:
17+
name: build ${{ matrix.os }}
18+
runs-on: ${{ matrix.os }}
19+
strategy:
20+
fail-fast: false # Whether to stop execution of other instances
21+
max-parallel: 4
22+
matrix:
23+
os: ["ubuntu-latest", "windows-latest"] # , "macos-latest"
24+
steps:
25+
- name: Check out code
26+
uses: actions/checkout@v2
27+
- name: Set up node
28+
uses: actions/setup-node@v3
29+
with:
30+
node-version: 16
31+
- name: Install dependencies
32+
run: npm install
33+
- name: Run tests
34+
run: |
35+
npm run coverage
36+
npx nyc report --reporter=lcovonly --reporter text
37+
- name: Coveralls Parallel
38+
uses: coverallsapp/github-action@master
39+
with:
40+
github-token: ${{ secrets.github_token }}
41+
flag-name: ${{ matrix.os }}
42+
parallel: true
43+
finish:
44+
needs: build
45+
runs-on: ubuntu-latest
46+
steps:
47+
- name: Coveralls Finished
48+
uses: coverallsapp/github-action@master
49+
with:
50+
github-token: ${{ secrets.github_token }}
51+
parallel-finished: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33
node_modules/*
44
.env
55
.pem
6+
coverage/*
7+
.nyc_output/*

CHANGELOG.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
# Changelog
22

3-
## [Latest](https://github.com/cortex-lab/matlab-ci/commits/master) [3.1.0]
3+
## [Latest](https://github.com/cortex-lab/matlab-ci/commits/master) [3.2.0]
4+
5+
## Modified
6+
7+
- if SIGTERM fails to end process(es) in under a minute, SIGKILL is sent
8+
9+
## Added
10+
11+
- git workflow
12+
- set coveralls env vars
13+
14+
## [3.1.0]
415

516
## Modified
617

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LabCI
2-
[![Build Status](https://travis-ci.com/cortex-lab/LabCI.svg?branch=master)](https://travis-ci.com/cortex-lab/matlab-ci)
3-
[![Coverage](https://img.shields.io/badge/coverage-91.91-brightgreen)](https://img.shields.io/badge/coverage-72.35-yellowgreen)
2+
![CI workflow](https://github.com/cortex-lab/LabCI/actions/workflows/tests.yml/badge.svg?branch=main)
3+
[![Coverage](https://img.shields.io/badge/coverage-91.69-brightgreen)](https://img.shields.io/badge/coverage-72.35-yellowgreen)
44

55
A small set of modules written in Node.js for running automated tests of MATLAB and Python code in response to GitHub events. Also submits code coverage to the Coveralls API.
66

@@ -111,6 +111,10 @@ Your test script must do the following:
111111
2. Save the results into the JSON cache file without duplication
112112
3. For code coverage the script must either save the coverage directly, or export a Cobertura formatted XML file.
113113

114+
## Coveralls
115+
Coverage information can be sent to coveralls.io using the [node-coveralls](https://github.com/nickmerwin/node-coveralls) package.
116+
Adding `COVERALLS_REPO_TOKEN` to the .env file will cause the CI to set other dynamic env variables before running a pipeline.
117+
114118
## Built With
115119

116120
* [LocalTunnel](https://localtunnel.me) - A secure tunneling service

lib.js

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -324,20 +324,48 @@ function getRepoPath(name) {
324324
function startJobTimer(job, kill_children = false) {
325325
const timeout = config.timeout || 8 * 60000; // How long to wait for the tests to run
326326
return setTimeout(() => {
327+
let log = _log.extend('job_timer');
327328
console.log('Max test time exceeded');
328329
log(kill_children ? 'Killing all processes' : 'Ending test process');
329330
let pid = job._child.pid;
331+
log('Killing process(es) for job #%g, pid = %d', job.id, pid);
330332
job._child.kill();
331-
if (kill_children) {
332-
kill(pid);
333-
}
333+
if (kill_children) kill(pid);
334+
// Give the processes 1 minute before sending a more aggressive signal
335+
return setTimeout(() => {
336+
if (job._child && job._child.exitCode == null) {
337+
log('Failed to kill job process(es); sending SIGKILL (job #%g, pid = %d)', job.id, pid);
338+
job._child.kill('SIGKILL');
339+
if (kill_children) kill(pid, 'SIGKILL');
340+
}
341+
}, 60000)
334342
}, timeout);
335343
}
336344

337345

346+
/**
347+
* Set dynamic env variables for node-coveralls.
348+
* NB: This does not support submodules.
349+
* @param {Object} job - The Job with an associated process in the data field.
350+
*/
351+
function initCoveralls(job) {
352+
const debug = log.extend('pipeline');
353+
debug('Setting COVERALLS env variables');
354+
process.env.COVERALLS_SERVICE_JOB_ID = job.id;
355+
const envMap = {
356+
'COVERALLS_SERVICE_NAME': job.data.context,
357+
'COVERALLS_GIT_COMMIT': job.data.sha,
358+
'COVERALLS_GIT_BRANCH': job.data.branch,
359+
'CI_PULL_REQUEST': job.data.pull_number
360+
};
361+
for (let key in envMap) { // assign value or delete key
362+
if (envMap[key]) { process.env[key] = envMap[key]; } else { delete process.env[key]; }
363+
}
364+
}
365+
338366
/**
339367
* Build task pipeline. Takes a list of scripts/functions and builds a promise chain.
340-
* @param {Object} job - The path of the repository
368+
* @param {Object} job - The Job with an associated process in the data field.
341369
* @returns {Promise} - The job routine
342370
*/
343371
async function buildRoutine(job) {
@@ -365,6 +393,9 @@ async function buildRoutine(job) {
365393
});
366394
const ops = config.shell ? {'shell': config.shell} : {};
367395

396+
// If environment variable COVERALLS_REPO_TOKEN is not null, set dynamic variables
397+
if (process.env.COVERALLS_REPO_TOKEN) initCoveralls(job);
398+
368399
const init = () => debug('Executing pipeline for job #%g', job.id);
369400
const routine = tasks.reduce(applyTask, Promise.resolve().then(init));
370401
return routine
@@ -411,6 +442,8 @@ async function buildRoutine(job) {
411442
clearTimeout(timer);
412443
})
413444
.on('close', (code, signal) => {
445+
// FIXME Sometime close is not called after a timeout, maybe because
446+
// the IO streams are kept open by some process?
414447
const callback = (code === 0) ? resolve : reject;
415448
const proc = {
416449
code: code,
@@ -419,6 +452,8 @@ async function buildRoutine(job) {
419452
stderr: stderr,
420453
process: child
421454
};
455+
// Ensure there's an exitCode as the second kill timer checks for this
456+
if (child.exitCode === null) child.exitCode = -1;
422457
callback(proc);
423458
});
424459
job.child = child; // Assign the child process to the job
@@ -447,7 +482,7 @@ async function buildRoutine(job) {
447482
message = `${errored.code} - Failed to spawn ${file}`;
448483
}
449484
// Check if the process was killed (we'll assume by the test timeout callback)
450-
} else if (errored.process.killed || errored.signal === 'SIGTERM') {
485+
} else if (errored.process.killed || ['SIGTERM', 'SIGKILL'].includes(errored.signal)) {
451486
message = `Tests stalled after ~${(config.timeout / 60000).toFixed(0)} min`;
452487
} else { // Error raised by process; dig through stdout for reason
453488
debug('error from test function %s', file);
@@ -511,10 +546,10 @@ async function buildRoutine(job) {
511546
*/
512547
function computeCoverage(job) {
513548
if (typeof job.data.coverage !== 'undefined' && job.data.coverage) {
514-
console.log('Coverage already computed for job #' + job.id);
549+
console.log('Coverage already computed for job #%g', job.id);
515550
return;
516551
}
517-
console.log('Updating coverage for job #' + job.id);
552+
console.log('Updating coverage for job #%g', job.id);
518553
const xmlPath = path.join(config.dataPath, 'reports', job.data.sha, 'CoverageResults.xml');
519554
const modules = listSubmodules(process.env.REPO_PATH);
520555
return Coverage(xmlPath, job.data.repo, job.data.sha, modules).then(obj => {
@@ -719,5 +754,5 @@ module.exports = {
719754
ensureArray, loadTestRecords, compareCoverage, computeCoverage, getBadgeData, log, shortID,
720755
openTunnel, APIError, queue, partial, startJobTimer, updateJobFromRecord, shortCircuit, isSHA,
721756
fullpath, strToBool, saveTestRecords, listSubmodules, getRepoPath, addParam, context2routine,
722-
buildRoutine
757+
buildRoutine, initCoveralls
723758
};

0 commit comments

Comments
 (0)