Skip to content

Commit b2927ef

Browse files
committed
Merge branch 'master' of https://github.com/cortex-lab/matlab-ci
2 parents 09f2dab + b969ede commit b2927ef

File tree

2 files changed

+208
-0
lines changed

2 files changed

+208
-0
lines changed

README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# MATLAB-ci
2+
3+
A small set of modules written in Node.js for running automated tests of MATLAB code in response to GitHub events. Also submits code coverage to the Coveralls API.
4+
5+
## Getting Started
6+
7+
Run the install script to install all dependencies, then create your .env file containing your App's tokens, secrets, etc.
8+
9+
### Prerequisites
10+
11+
Requires MATLAB 2017a or later and Node.js. The following Node.js modules are required:
12+
13+
```
14+
npm install --save express dotenv @octokit/app @octokit/request ...
15+
github-webhook-handler smee-client xml2js
16+
```
17+
18+
### Installing
19+
20+
Make sure runAllTests.m is on your MATLAB paths
21+
22+
## Running the tests
23+
24+
TODO
25+
26+
### Break down into end to end tests
27+
28+
Explain what these tests test and why
29+
30+
```
31+
Give an example
32+
```
33+
34+
### And coding style tests
35+
36+
Explain what these tests test and why
37+
38+
```
39+
Give an example
40+
```
41+
42+
## Deployment
43+
44+
To work properly you need to create install a Github app on your target repository and download the private key. Update your .env file like so:
45+
46+
```
47+
GITHUB_PRIVATE_KEY=path\to\private-key.pem
48+
GITHUB_APP_IDENTIFIER=1234
49+
GITHUB_WEBHOOK_SECRET=
50+
WEBHOOK_PROXY_URL=https://smee.io/abcd
51+
RIGBOX_REPO_PATH=C:\Path\To\Code\Repo
52+
COVERALLS_TOKEN=
53+
```
54+
55+
To run at startup create a batch file with the following command:
56+
57+
```batch
58+
cmd /k node -r dotenv/config dotenv_config_path=/Path/To/Env/Vars ./Path/To/index.js
59+
```
60+
61+
Create a shortcut in your startup folder ([Windows-logo] + [R] in Windows-10 and enter the command `shell:startup`)
62+
63+
## Built With
64+
65+
* [Coveralls](coveralls.io) - Code coverage
66+
67+
## Contributing
68+
69+
Please read [CONTRIBUTING.md](https://gist.github.com/PurpleBooth/b24679402957c63ec426) for details on our code of conduct, and the process for submitting pull requests to us.
70+
71+
## Versioning
72+
73+
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags).
74+
75+
## Authors
76+
77+
* **Miles Wells**
78+
79+
## License
80+
81+
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details

coverage.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* Coverage module loads a Cobertura Format XML file containing code coverage information and reformats it into an
3+
* object for POSTing to the Coveralls API.
4+
* @module ./coverage
5+
* @example
6+
* // create queue
7+
* const queue = new require(./queue.js).Queue()
8+
* queue.process((job, done) => {
9+
* console.log('Job with id ' + job.id + ' is being processed');
10+
* setTimeout(done, 3000);
11+
* });
12+
* var data = {key: 'value'};
13+
* queue.add(data);
14+
* @requires module:dotenv For loading in API token
15+
* @requires module:xml2js For converting XML to object
16+
* @version 0.9.0
17+
* @author Miles Wells [<k1o0@3tk.co>]
18+
* @license Apache-2.0
19+
*/
20+
21+
var fs = require('fs'),
22+
xml2js = require('xml2js'),
23+
crypto = require('crypto'),
24+
assert = require('assert').strict,
25+
parser = new xml2js.Parser(),
26+
timestamp, md5;
27+
var timestamp;
28+
var token = process.env.COVERALLS_TOKEN;
29+
30+
/**
31+
* Formates list of classes from XML file and return object formatted for the Coveralls API.
32+
* @see {@link https://docs.coveralls.io/api-reference|Coveralls API docs}
33+
* @param {Array} classList - An array of class objects from the loaded XML file.
34+
* @param {String} path - The root path of the code repository.
35+
* @param {String} sha - The commit SHA for this coverage test.
36+
* @returns {Object} Object containing array of source code files and their code coverage
37+
* @todo Generalize path default
38+
*/
39+
const formatCoverage = function(classList, path, sha) {
40+
var job = {};
41+
var sourceFiles = [];
42+
typeof path != "undefined" ? 'C:\\Users\\User\\' : path; // default FIXME
43+
// For each class, create file object containing array of lines covered and add to sourceFile array
44+
classList.forEach( async c => {
45+
let file = {}; // Initialize file object
46+
var digest = md5(path + c.$.filename); // Create digest and line count for file
47+
let lines = new Array(digest.count).fill(null); // Initialize line array the size of source code file
48+
c.lines[0].line.forEach(ln => {
49+
let n = Number(ln.$.number);
50+
if (n <= digest.count) {lines[n] = Number(ln.$.hits) }
51+
});
52+
// create source file object
53+
file.name = c.$.filename;
54+
file.source_digest = digest.hash;
55+
file.coverage = lines; // file.coverage[0] == line 1
56+
sourceFiles.push(file);
57+
});
58+
59+
job.repo_token = token; // env secret token?
60+
job.service_name = 'continuous-integration/ZTEST';
61+
// The associated pull request ID of the build. Used for updating the status and/or commenting.
62+
job.service_pull_request = '';
63+
job.source_files = sourceFiles;
64+
job.commit_sha = sha;
65+
job.run_at = timestamp; // "2013-02-18 00:52:48 -0800"
66+
return job;
67+
}
68+
69+
/**
70+
* Loads a code coverage XML file in Cobertura Format and returns payload object for Coveralls API
71+
* @see {@link https://docs.coveralls.io/api-reference|Coveralls API docs}
72+
* @param {String} path - Path to the XML file containing coverage information.
73+
* @param {String} sha - The commit SHA for this coverage test
74+
* @returns {Object} Object containing array of source code files and their code coverage
75+
* @todo Remove assert
76+
* @todo Generalize ignoring of submodules
77+
* @see {@link https://github.com/cobertura/cobertura/wiki|Cobertura Wiki}
78+
*/
79+
const coverage = function(path, sha) {
80+
fs.readFile(path, function(err, data) { // Read in XML file
81+
parser.parseString(data, function (err, result) { // Parse XML
82+
const rigboxPath = result.coverage.sources[0].source[0]; // Extract root code path
83+
assert(rigboxPath.endsWith('Rigbox\\'), 'Incorrect source code repository')
84+
timestamp = new Date(result.coverage.$.timestamp*1000); // Convert UNIX timestamp to Date object
85+
let classes = []; // Initialize classes array
86+
87+
const packages = result.coverage.packages[0].package;
88+
packages.forEach(package => { classes.push(package.classes[0].class) }); // Get all classes
89+
classes = classes.reduce((acc, val) => acc.concat(val), []); // Flatten
90+
//console.log(classes.length);
91+
92+
// The submodules
93+
var alyx_matlab = [];
94+
var signals = [];
95+
var npy_matlab = [];
96+
var wheelAnalysis = [];
97+
// Sort into piles
98+
classes = classes.filter(function (e) {
99+
if (e.$.filename.search(/(tests\\|_.*test|docs\\)/i) != -1) {return false;} // Filter out tests and docs
100+
if (!Array.isArray(e.lines[0].line)) {return false;} // Filter out files with no functional lines
101+
if (e.$.filename.startsWith('alyx-matlab\\')) {alyx_matlab.push(e); return false;}
102+
if (e.$.filename.startsWith('signals\\')) {signals.push(e); return false;}
103+
if (e.$.filename.startsWith('npy-matlab\\')) {npy_matlab.push(e); return false;}
104+
if (e.$.filename.startsWith('wheelAnalysis\\')) {wheelAnalysis.push(e); return false;}
105+
else {return true};
106+
});
107+
//console.log(obj.source_files[0].coverage.length);
108+
return obj = formatCoverage(classes, rigboxPath);
109+
});
110+
});
111+
};
112+
113+
/**
114+
* Loads file containing source code, returns a hash and line count
115+
* @param {String} path - Path to the source code file.
116+
* @returns {Object} key `Hash` contains MD5 digest string of file; `count` contains number of lines in source file
117+
*/
118+
const md5 = function(path) {
119+
var hash = crypto.createHash('md5'); // Creating hash object
120+
var buf = fs.readFileSync(path, 'utf-8'); // Read in file
121+
var count = buf.split(/\r\n|\r|\n/).length; // Count the number of lines
122+
hash.update(buf, 'utf-8'); // Update hash
123+
return {hash: hash.digest('hex'), count: count};
124+
};
125+
126+
// Export Coverage
127+
module.exports = coverage;

0 commit comments

Comments
 (0)