Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Define stages dev and qa #22

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions runtime-nodejs/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
!.gitignore
!package.json
!package-lock.json
!src
!jest.*.js
!specs
!waiting-for-specs
15 changes: 15 additions & 0 deletions runtime-nodejs/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es2021": true,
"node": true,
"jest": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12
},
"rules": {
}
};
4 changes: 2 additions & 2 deletions runtime-nodejs/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM yolean/node:8fa14c0f3581c0e225072e303d6e011e861582cc@sha256:25830c903c575a26a88a2c20ddd7b221886063eef17590a80cf9c3cbed168f44 \
FROM yolean/node:682b35452193e841717e6749a7de1df6e1ac4f6a@sha256:2ae6c3cfd92c8298cb57ce304c18d58523759e92ae92f668e8d1de97fa9876fb \
as unittest

ENV CI=true
Expand All @@ -13,7 +13,7 @@ RUN [ "npm", "ci", "--ignore-scripts" ]
COPY --chown=nonroot:nogroup . .
RUN [ "npm", "test" ]

FROM yolean/node:8fa14c0f3581c0e225072e303d6e011e861582cc@sha256:25830c903c575a26a88a2c20ddd7b221886063eef17590a80cf9c3cbed168f44
FROM yolean/node:682b35452193e841717e6749a7de1df6e1ac4f6a@sha256:2ae6c3cfd92c8298cb57ce304c18d58523759e92ae92f668e8d1de97fa9876fb

ENV CI=true
COPY --chown=nonroot:nogroup package*.json .gitignore /usr/src/
Expand Down
179 changes: 25 additions & 154 deletions runtime-nodejs/jest.kubernetes-assertions-reporter.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const fs = require('fs');
const http = require('http');

const PORT = process.env.PORT ? parseInt(process.env.PORT) : 9091;
const RERUN_WAIT = parseInt(process.env.RERUN_WAIT);
const ASSERT_IS_DEV = process.env.ASSERT_IS_DEV === 'true';

const client = require('prom-client');
const MetricsReporter = require('./src/MetricsReporter');
const MetricsServer = require('./src/MetricsServer');
const Reruns = require('./src/Reruns');
const SpecFilesTracker = require('./src/SpecFilesTracker');
const register = client.register;

const assertions_failed = new client.Gauge({
Expand Down Expand Up @@ -43,166 +44,36 @@ const assert_files_seen = new client.Gauge({
help: 'Unique spec file paths that have been seen in onTestResult'
});

class SpecFilesTracker {

constructor() {
this._pathsSeen = {};
}

pathSeen(path, { numFailingTests }) {
if (this._pathsSeen.hasOwnProperty(path)) {
const delta = numFailingTests - this._pathsSeen[path].numFailingTests;
if (delta > 0) assertions_failed.inc(delta);
if (delta < 0) assertions_failed.dec(-delta);
} else {
this._pathsSeen[path] = {};
assert_files_seen.inc();
if (numFailingTests > 0) assertions_failed.inc(numFailingTests);
console.log('Path reported for the first time:', path);
}
this._pathsSeen[path].numFailingTests = numFailingTests;
}

modify(path) {
const insignificant = '\n';
fs.appendFile(path, insignificant, 'utf8', (err) => {
if (err) throw err;
//console.log('Modified', path);
});
}

modifyAll() {
const modify = this.modify.bind(this);
const paths = Object.keys(this._pathsSeen);
console.log('Files will be modified to trigger rerun:', paths);
paths.map(path => {
// We prefer to do this as concurrent as possible, to trigger a single run, so don't wait
modify(path);
});
}

}

const tracker = new SpecFilesTracker();

/*
* The idea ...
* We'll only get good specs if the development experience is attractive
* which is why runtime-nodejs prioritizes the use case skaffold dev with sync
* Until https://github.com/facebook/jest/issues/5048 https://github.com/facebook/jest/issues/8868 are fixed
* ... or we want to give something like https://medium.com/web-developers-path/how-to-run-jest-programmatically-in-node-js-jest-javascript-api-492a8bc250de a try
* ... or we find a way to emulate interactive watch keypresses
* it'll mean that we run watch always and that the unattended run mode will be a bit of a hack
* The basic requirement is that tests are rerun.
* We rerun all tests, not only failed, because when it comes to infra things go up and down.
*/
class Reruns {

constructor({ tracker, intervalMs }) {
console.log('Activating reruns with interval (ms)', intervalMs);
this._intervalMs = intervalMs;
this._timeout = null;
}

onRunComplete() {
this._timeout !== null && clearTimeout(this._timeout);
if (!ASSERT_IS_DEV && RERUN_WAIT) {
this._timeout = setTimeout(() => {
tracker.modifyAll();
}, this._intervalMs);
}
}

}

const reruns = new Reruns({
tracker,
intervalMs: RERUN_WAIT * 1000
rerunWaitMs: RERUN_WAIT * 1000,
assertIsDev: ASSERT_IS_DEV
});

class MetricsServer {

constructor({ port, getMetrics }) {
this.port = port;
this.getMetrics = getMetrics;
}

serveMetrics(res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(this.getMetrics());
}

serveRerun(res) {
console.log('Rerun endpoint called');
tracker.modifyAll();
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('{}');
}

start() {
this.server = http.createServer((req, res) => {
//console.log('req', req.url, req.headers);
if ('/metrics' == req.url) return this.serveMetrics(res);
if ('POST' == req.method && '/rerun' == req.url) return this.serveRerun(res);
res.writeHead(404, { 'Content-Length': '0' });
res.end();
});
this.server.on('clientError', (err, socket) => {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
this.server.listen(this.port, '0.0.0.0');
console.log('Server listening on port', this.port);
}

stop() {
this.server.close();
}

}

const server = new MetricsServer({
port: PORT,
getMetrics: () => register.metrics()
getMetrics: () => register.metrics(),
tracker
});

server.start();

class MetricsReporter {

constructor(globalConfig, options) {
this._globalConfig = globalConfig;
this._options = options;
// Can't change how Jest instantiates the reporter, so get this one from global
this._tracker = tracker;
}

onRunStart() {
//console.log('onRunStart', arguments);
}

onTestStart() {
//console.log('onTestStart', arguments);
}

onRunComplete(contexts, results) {
//console.log('onRunComplete', contexts, results);
const { testResults } = results;
for (let i = 0; i < testResults.length; i++) {
const { testFilePath, numFailingTests } = testResults[i];
this._tracker.pathSeen(testFilePath, { numFailingTests });
}
test_suites_run.set(results.numTotalTestSuites);
test_suites_run_total.inc(results.numTotalTestSuites);
tests_run.set(results.numTotalTests);
tests_run_total.inc(results.numTotalTests);
assertions_failed_total.inc(results.numFailedTests);
if (!this._globalConfig.watch && !this._globalConfig.watchAll) {
//console.log('Not a watch run. Exiting');
server.stop();
}
reruns.onRunComplete();
}

}

module.exports = MetricsReporter;
module.exports = function (globalConfig, options) {
return new MetricsReporter({
isWatching: !globalConfig.watch && !globalConfig.watchAll,
tracker,
metrics: {
assertions_failed,
assertions_failed_total,
tests_run,
tests_run_total,
test_suites_run,
test_suites_run_total,
assert_files_seen,
},
reruns,
server
})
};
Loading