Skip to content

Commit

Permalink
Assorted CT improvements, see #88
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanolson committed Apr 15, 2020
1 parent b88c1e1 commit 08c5610
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 239 deletions.
90 changes: 40 additions & 50 deletions js/CTSnapshot.js → js/Snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,31 @@ const deleteDirectory = require( '../../perennial/js/common/deleteDirectory' );
const execute = require( '../../perennial/js/common/execute' );
const getRepoList = require( '../../perennial/js/common/getRepoList' );
const gitRevParse = require( '../../perennial/js/common/gitRevParse' );
const Test = require( './Test' );
const fs = require( 'fs' );
const _ = require( 'lodash' ); // eslint-disable-line

class CTSnapshot {
class Snapshot {
/**
* Creates this snapshot.
* @public
*
* @param {string} rootDir
* @param {function({string})} setSnapshotStatus
*/
async create( rootDir, setSnapshotStatus ) {

constructor( rootDir, setSnapshotStatus ) {
// @private {string}
this.rootDir = rootDir;

// @private {function}
this.setSnapshotStatus = setSnapshotStatus;
}

/**
* Creates this snapshot.
* @public
*/
async create() {

const timestamp = Date.now();
const snapshotDir = `${rootDir}/ct-snapshots`;
const snapshotDir = `${this.rootDir}/ct-snapshots`;

this.setSnapshotStatus( `Initializing new snapshot: ${timestamp}` );

Expand All @@ -48,16 +52,12 @@ class CTSnapshot {
this.exists = true;

// @public {string}
this.phetDir = `${snapshotDir}/${timestamp}-phet`;
this.phetioDir = `${snapshotDir}/${timestamp}-phet-io`;
this.directory = `${snapshotDir}/${timestamp}`;

if ( !fs.existsSync( snapshotDir ) ) {
await createDirectory( snapshotDir );
}
await createDirectory( this.phetDir );
await createDirectory( this.phetioDir );

this.setSnapshotStatus( 'Copying snapshot files' );
await createDirectory( this.directory );

// @public {Array.<string>}
this.repos = getRepoList( 'active-repos' );
Expand All @@ -72,27 +72,15 @@ class CTSnapshot {
}

for ( const repo of this.repos ) {
await copyDirectory( `${rootDir}/${repo}`, `${this.phetDir}/${repo}`, {} );
await copyDirectory( `${rootDir}/${repo}`, `${this.phetioDir}/${repo}`, {} );
this.setSnapshotStatus( `Copying snapshot files: ${repo}` );
await copyDirectory( `${this.rootDir}/${repo}`, `${this.directory}/${repo}`, {} );
}

// @public {Array.<Object>}
this.tests = JSON.parse( await execute( 'node', [ 'js/listContinuousTests.js' ], '../perennial' ) ).map( test => {
test.snapshot = this;
return test;
} );
this.browserTests = this.tests.filter( test => [ 'sim-test', 'qunit-test', 'pageload-test' ].includes( test.type ) ).map( test => {
test.count = 0;
return test;
} );
this.lintTests = this.tests.filter( test => test.type === 'lint' ).map( test => {
test.complete = false;
return test;
} );
this.buildTests = this.tests.filter( test => test.type === 'build' ).map( test => {
test.complete = false;
test.success = false;
return test;
this.setSnapshotStatus( 'Loading tests from perennial' );

// @public {Array.<Test>}
this.tests = JSON.parse( await execute( 'node', [ 'js/listContinuousTests.js' ], '../perennial' ) ).map( description => {
return new Test( this, description );
} );
}

Expand All @@ -101,29 +89,31 @@ class CTSnapshot {
* @public
*/
async remove() {
await deleteDirectory( this.phetDir );
await deleteDirectory( this.phetioDir );
await deleteDirectory( this.directory );

this.exists = false;
}

/**
* Returns all of the available local tests.
* @public
*
* @returns {Array.<Object>}
*/
getAvailableLocalTests() {
return this.tests.filter( test => test.isLocallyAvailable() );
}

/**
* Returns all of the available browser tests.
* @public
*
* @param {boolean} es5Only
* @returns {Array.<Object>}
*/
getAvailableBrowserTests( es5Only ) {
return this.browserTests.filter( test => {
if ( es5Only && !test.es5 ) {
return false;
}

if ( test.buildDependencies ) {
for ( const dependency of test.buildDependencies ) {
if ( !_.some( this.buildTests, buildTest => buildTest.repo === dependency && buildTest.brand === test.brand && buildTest.success ) ) {
return false;
}
}
}

return true;
} );
return this.tests.filter( test => test.isBrowserAvailable( es5Only ) );
}
}

module.exports = CTSnapshot;
module.exports = Snapshot;
191 changes: 191 additions & 0 deletions js/Test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright 2020, University of Colorado Boulder

/**
* Holds data related to a specific test.
*
* @author Jonathan Olson <[email protected]>
*/

'use strict';

const TestResult = require( './TestResult' );
const assert = require( 'assert' );
const _ = require( 'lodash' ); // eslint-disable-line

// constants
const TEST_TYPES = [
'lint',
'build',
'sim-test',
'qunit-test',
'pageload-test'
];

class Test {
/**
* @param {Snapshot} snapshot
* @param {Object} description - from listContinuousTests.js
*/
constructor( snapshot, description ) {
assert( Array.isArray( description.test ), 'Test descriptions should have a test-name array' );
assert( typeof description.type === 'string', 'Test descriptions should have a type' );
assert( TEST_TYPES.includes( description.type ), `Unknown type: ${description.type}` );

// @public {Snapshot}
this.snapshot = snapshot;

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

// @public {string}
this.type = description.type;

// @public {Array.<TestResult>}
this.results = [];

// @public {string|null}
this.repo = null;

if ( this.type === 'lint' || this.type === 'build' ) {
assert( typeof description.repo === 'string', `${this.type} tests should have a repo` );

this.repo = description.repo;
}

// @public {Array.<string>}
this.brands = null;

if ( this.type === 'build' ) {
assert( Array.isArray( description.brands ), 'build tests should have a brands' );

this.brands = description.brands;
}

// @public {string|null}
this.url = null;

if ( this.type === 'sim-test' || this.type === 'qunit-test' || this.type === 'pageload-test' ) {
assert( typeof description.url === 'string', `${this.type} tests should have a url` );

this.url = description.url;
}

// @public {string|null}
this.queryParameters = null;

if ( description.queryParameters ) {
assert( typeof description.queryParameters === 'string', 'queryParameters should be a string if provided' );
this.queryParameters = description.queryParameters;
}

// @public {boolean} - If false, we won't send this test to browsers that only support es5 (IE11, etc.)
this.es5 = false;

if ( description.es5 ) {
this.es5 = true;
}

// @public {Array.<string>} - The repos that need to be built before this test will be provided
this.buildDependencies = [];

if ( description.buildDependencies ) {
assert( Array.isArray( description.buildDependencies ), 'buildDependencies should be an array' );

this.buildDependencies = description.buildDependencies;
}

// @public {boolean} - For server-side tests run only once
this.complete = false;

// @public {boolean} - For server-side tests run only once, indicating it was successful
this.success = false;

// @public {number} - For browser-side tests, the number of times we have sent this test to a browser
this.count = 0;
}

/**
* Records a test result
* @public
*
* @param {boolean} passed
* @param {string|null} [message]
*/
recordResult( passed, message ) {
this.results.push( new TestResult( this, passed, message ) );
}

/**
* Whether this test can be run locally.
* @public
*
* @returns {boolean}
*/
isLocallyAvailable() {
return !this.complete && ( this.type === 'lint' || this.type === 'build' );
}

/**
* Whether this test can be run in a browser.
* @public
*
* @param {booealn} es5Only
* @returns {boolean}
*/
isBrowserAvailable( es5Only ) {
if ( this.type !== 'sim-test' && this.type !== 'qunit-test' && this.type !== 'pageload-test' ) {
return false;
}

if ( es5Only && !this.es5 ) {
return false;
}

if ( this.buildDependencies ) {
for ( const repo of this.buildDependencies ) {
const buildTest = _.find( this.snapshot.tests, test => test.type === 'build' && test.repo === repo );

if ( !buildTest || !buildTest.success ) {
return false;
}
}
}

return true;
}

/**
* Returns the object sent to the browser for this test.
* @public
*
* @returns {Object}
*/
getObjectForBrowser() {
assert( this.type === 'sim-test' || this.type === 'qunit-test' || this.type === 'pageload-test', 'Needs to be a browser test' );

const baseURL = `../../ct-snapshots/${this.snapshot.timestamp}`;
let url;

if ( this.type === 'sim-test' ) {
url = 'sim-test.html?url=' + encodeURIComponent( `${baseURL}/${this.url}` );

if ( this.queryParameters ) {
url += '&simQueryParameters=' + encodeURIComponent( this.queryParameters );
}
}
else if ( this.type === 'qunit-test' ) {
url = 'qunit-test.html?url=' + encodeURIComponent( `${baseURL}/${this.url}` );
}
else if ( this.type === 'pageload-test' ) {
url = 'pageload-test.html?url=' + encodeURIComponent( `${baseURL}/${this.url}` );
}

return {
snapshotName: this.snapshot.name,
test: this.names,
url: url
};
}
}

module.exports = Test;
33 changes: 33 additions & 0 deletions js/TestResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2020, University of Colorado Boulder

/**
* Holds data related to a specific test result
*
* @author Jonathan Olson <[email protected]>
*/

'use strict';

const assert = require( 'assert' );

class TestResult {
/**
* @param {Test} test
* @param {boolean} passed
* @param {string|null} [message]
*/
constructor( test, passed, message ) {
assert( typeof passed === 'boolean', 'passed should be a boolean' );

// @public {Test}
this.test = test;

// @public {boolean}
this.passed = passed;

// @public {string|null}
this.message = message || null;
}
}

module.exports = TestResult;
Loading

0 comments on commit 08c5610

Please sign in to comment.