Skip to content

Commit

Permalink
Serialization of CT state, see #88
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanolson committed Apr 16, 2020
1 parent 3930f28 commit 77f38e4
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 25 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ node_modules/
*.sublime-workspace
.eslintcache
screenshots/
logs/*.txt
logs/*.txt
.continuous-testing-state.json
21 changes: 14 additions & 7 deletions js/report/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,19 @@ const reportProperty = new Property( {
};
req.onerror = function() {
setTimeout( reportLoop, 20000 );
reportProperty.reset();

// Show the most recent results anyway?
// reportProperty.reset();
};
req.open( 'get', options.server + '/aquaserver/report', true );
req.send();
})();






const rootNode = new Node();
const display = new Display( rootNode, {
passiveEvents: true
Expand Down Expand Up @@ -110,21 +117,21 @@ reportProperty.link( report => {

const snapshotsTestNodes = _.flatten( report.snapshots.map( ( snapshot, i ) => {
return report.testNames.map( ( names, j ) => {
const test = _.find( snapshot.tests, test => _.isEqual( names, test.names ) );
const test = snapshot.tests[ j ];

const background = new Rectangle( 0, 0, maxSnapshotLabelWidth, maxTestLabelHeight, {
x: maxTestLabelWidth + padding + i * ( maxSnapshotLabelWidth + padding ),
y: maxSnapshotLabelHeight + padding + j * ( maxTestLabelHeight + padding )
} );

if ( test ) {
if ( test.passCount > 0 && test.failCount === 0 ) {
if ( typeof test.y === 'number' ) {
if ( test.y > 0 && test.n === 0 ) {
background.fill = passColor;
}
else if ( test.passCount === 0 && test.failCount > 0 ) {
else if ( test.y === 0 && test.n > 0 ) {
background.fill = failColor;
}
else if ( test.passCount === 0 && test.failCount === 0 ) {
else if ( test.y === 0 && test.n === 0 ) {
background.fill = untestedColor;
}
else {
Expand Down Expand Up @@ -157,6 +164,6 @@ reportProperty.link( report => {

display.initializeEvents();
display.updateOnRequestAnimationFrame( dt => {
display.width = Math.ceil( rootNode.width );
display.width = window.innerWidth;
display.height = Math.ceil( rootNode.height );
} );
55 changes: 51 additions & 4 deletions js/server/Snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,6 @@ class Snapshot {
// @public {Array.<string>}
this.repos = getRepoList( 'active-repos' );

// @public {Array.<string>}
this.npmInstalledRepos = [];

// @public {Object} - maps repo {string} => sha {string}
this.shas = {};
for ( const repo of this.repos ) {
Expand All @@ -89,9 +86,21 @@ class Snapshot {
* @public
*/
async remove() {
this.exists = false;

await deleteDirectory( this.directory );
}

this.exists = false;
/**
* Finds a given test by its names.
* @public
*
* @param {Array.<string>} names
* @returns {Test|null}
*/
findTest( names ) {
// TODO: can increase performance with different lookups
return _.find( this.tests, test => _.isEqual( test.names, names ) );
}

/**
Expand All @@ -114,6 +123,44 @@ class Snapshot {
getAvailableBrowserTests( es5Only ) {
return this.tests.filter( test => test.isBrowserAvailable( es5Only ) );
}

/**
* Creates a pojo-style object for saving/restoring
*
* @returns {Object}
*/
serialize() {
return {
rootDir: this.rootDir,
timestamp: this.timestamp,
name: this.name,
exists: this.exists,
directory: this.directory,
repos: this.repos,
shas: this.shas,
tests: this.tests.map( test => test.serialize() )
};
}

/**
* Creates the in-memory representation from the serialized form
*
* @param {Object} serialization
* @returns {Snapshot}
*/
static deserialize( serialization ) {
const snapshot = new Snapshot( serialization.rootDir, () => {} );

snapshot.timestamp = serialization.timestamp;
snapshot.name = serialization.name;
snapshot.exists = serialization.exists;
snapshot.directory = serialization.directory;
snapshot.repos = serialization.repos;
snapshot.shas = serialization.shas;
snapshot.tests = serialization.tests.map( testSerialization => Test.deserialize( snapshot, testSerialization ) );

return snapshot;
}
}

module.exports = Snapshot;
37 changes: 37 additions & 0 deletions js/server/Test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ class Test {
// @public {Snapshot}
this.snapshot = snapshot;

// @private {Object} - Saved for future serialization
this.description = description;

// @public {Array.<string>}
this.names = description.test;

Expand Down Expand Up @@ -186,6 +189,40 @@ class Test {
url: url
};
}

/**
* Creates a pojo-style object for saving/restoring
*
* @returns {Object}
*/
serialize() {
return {
description: this.description,
results: this.results.map( testResult => testResult.serialize() ),
complete: this.complete,
success: this.success,
count: this.count
};
}

/**
* Creates the in-memory representation from the serialized form
*
* @param {Snapshot} snapshot
* @param {Object} serialization
* @returns {Test}
*/
static deserialize( snapshot, serialization ) {
const test = new Test( snapshot, serialization.description );

test.complete = serialization.complete;
test.success = serialization.success;
test.count = serialization.count;

test.results = serialization.results.map( resultSerialization => TestResult.deserialize( test, resultSerialization ) );

return test;
}
}

module.exports = Test;
23 changes: 23 additions & 0 deletions js/server/TestResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,29 @@ class TestResult {
// @public {string|null}
this.message = message || null;
}

/**
* Creates a pojo-style object for saving/restoring
*
* @returns {Object}
*/
serialize() {
return {
passed: this.passed,
message: this.message
};
}

/**
* Creates the in-memory representation from the serialized form
*
* @param {Test} test
* @param {Object} serialization
* @returns {TestResult}
*/
static deserialize( test, serialization ) {
return new TestResult( test, serialization.passed, serialization.message );
}
}

module.exports = TestResult;
65 changes: 52 additions & 13 deletions js/server/continuous-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,25 @@ const jsonHeaders = {
};

// {Array.<Snapshot>} All of our snapshots
const snapshots = [];
let snapshots = [];

let reportJSON = '{}';

// root of your GitHub working copy, relative to the name of the directory that the currently-executing script resides in
const rootDir = path.normalize( __dirname + '/../../../' ); // eslint-disable-line no-undef

const saveFile = `${rootDir}/aqua/.continuous-testing-state.json`;
const saveToFile = () => {
fs.writeFileSync( saveFile, JSON.stringify( {
snapshots: snapshots.map( snapshot => snapshot.serialize() )
}, null, 2 ), 'utf-8' );
};
const loadFromFile = () => {
if ( fs.existsSync( saveFile ) ) {
snapshots = JSON.parse( fs.readFileSync( saveFile, 'utf-8' ) ).snapshots.map( Snapshot.deserialize );
}
};

// Gets update with the current status
let snapshotStatus = 'Starting up';
const setSnapshotStatus = str => {
Expand Down Expand Up @@ -171,6 +183,7 @@ const startServer = () => {
else {
testFail( test, message );
}
saveToFile();
}
}
else {
Expand Down Expand Up @@ -226,7 +239,7 @@ const cycleSnapshots = async () => {
if ( staleRepos.length ) {
wasStale = true;

setSnapshotStatus( `Stale repos: ${staleRepos.join( ', ' )}, pulling/npm` );
setSnapshotStatus( `Stale repos (pulling/npm): ${staleRepos.join( ', ' )}` );

for ( const repo of staleRepos ) {
await gitPull( repo );
Expand Down Expand Up @@ -258,11 +271,14 @@ const cycleSnapshots = async () => {
snapshots.pop();
}

saveToFile();

setSnapshotStatus( 'Removing old snapshot files' );
const numActiveSnapshots = 3;
for ( const snapshot of snapshots.slice( numActiveSnapshots ) ) {
if ( snapshot.exists ) {
await snapshot.remove();
saveToFile();
}
}
}
Expand Down Expand Up @@ -301,6 +317,7 @@ const localTaskCycle = async () => {

if ( test.type === 'lint' ) {
test.complete = true;
saveToFile();
try {
const output = await execute( gruntCommand, [ 'lint' ], `../${test.repo}` );

Expand All @@ -309,9 +326,11 @@ const localTaskCycle = async () => {
catch ( e ) {
testFail( test, `Build failed with status code ${e.code}:\n${e.stdout}\n${e.stderr}`.trim() );
}
saveToFile();
}
else if ( test.type === 'build' ) {
test.complete = true;
saveToFile();
try {
const output = await execute( gruntCommand, [ `--brands=${test.brands.join( ',' )}`, '--lint=false' ], `../${test.repo}` );

Expand All @@ -321,6 +340,7 @@ const localTaskCycle = async () => {
catch ( e ) {
testFail( test, `Build failed with status code ${e.code}:\n${e.stdout}\n${e.stderr}`.trim() );
}
saveToFile();
}
else {
// uhhh, don't know what happened? Don't loop here without sleeping
Expand All @@ -336,25 +356,37 @@ const localTaskCycle = async () => {
const reportTaskCycle = async () => {
while ( true ) { // eslint-disable-line
try {
const testNames = _.sortBy( _.uniqWith( _.flatten( snapshots.map( snapshot => snapshot.tests.map( test => test.names ) ) ), _.isEqual ), names => names.toString() );
const report = {
snapshots: snapshots.map( snapshot => {
return {
timestamp: snapshot.timestamp,
shas: snapshot.shas,
tests: snapshot.tests.map( test => {
const passedTestResults = test.results.filter( testResult => testResult.passed );
const failedTestResults = test.results.filter( testResult => !testResult.passed );
return {
names: test.names,
passCount: passedTestResults.length,
failCount: failedTestResults.length,
passMessages: _.uniq( passedTestResults.map( testResult => testResult.message ).filter( _.identity ) ),
failMessages: _.uniq( failedTestResults.map( testResult => testResult.message ).filter( _.identity ) )
};

// TODO: would sparse arrays be better here? probably, but slower lookup
tests: testNames.map( names => {
const test = snapshot.findTest( names );
if ( test ) {
const passedTestResults = test.results.filter( testResult => testResult.passed );
const failedTestResults = test.results.filter( testResult => !testResult.passed );
const failMessages = _.uniq( failedTestResults.map( testResult => testResult.message ).filter( _.identity ) );

const result = {
y: passedTestResults.length,
n: failedTestResults.length
};
if ( failMessages.length ) {
result.m = failMessages;
}
return result;
}
else {
return {};
}
} )
};
} ),
testNames: _.sortBy( _.uniqWith( _.flatten( snapshots.map( snapshot => snapshot.tests.map( test => test.names ) ) ), _.isEqual ), names => names.toString() )
testNames: testNames
};

reportJSON = JSON.stringify( report );
Expand All @@ -369,6 +401,13 @@ const reportTaskCycle = async () => {

const numberLocal = Number.parseInt( process.argv[ 2 ], 10 ) || 1;

try {
loadFromFile();
}
catch ( e ) {
winston.error( `error loading from file: ${e}` );
}

startServer();
cycleSnapshots();
reportTaskCycle();
Expand Down

0 comments on commit 77f38e4

Please sign in to comment.