diff --git a/package.json b/package.json index 0dbc78598bd5..d78b923ee5a4 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "@angular/forms": "^2.2.0", "@angular/http": "^2.2.0", "@angular/platform-browser": "^2.2.0", - "@types/es6-promise": "0.0.32", "core-js": "^2.4.1", "google-cloud": "^0.45.1", "image-diff": "^1.6.3", diff --git a/tools/gulp/task_helpers.ts b/tools/gulp/task_helpers.ts index f2aa4dabbb77..51c5a58781f5 100644 --- a/tools/gulp/task_helpers.ts +++ b/tools/gulp/task_helpers.ts @@ -3,7 +3,6 @@ import * as fs from 'fs'; import * as gulp from 'gulp'; import * as gulpTs from 'gulp-typescript'; import * as path from 'path'; - import {NPM_VENDOR_FILES, PROJECT_ROOT, DIST_ROOT, SASS_AUTOPREFIXER_OPTIONS} from './constants'; @@ -213,7 +212,7 @@ export function sequenceTask(...args: any[]) { } /** Opens a connection to the firebase realtime database. */ -export function openFirebaseDatabase() { +export function openFirebaseDashboardDatabase() { // Initialize the Firebase application with admin credentials. // Credentials need to be for a Service Account, which can be created in the Firebase console. firebaseAdmin.initializeApp({ @@ -235,6 +234,11 @@ export function isTravisPushBuild() { return process.env['TRAVIS_PULL_REQUEST'] === 'false'; } +/** Decode the token for Travis to use. */ +function decode(str: string): string { + return (str || '').split('\\n').reverse().join('\\n').replace(/\\n/g, '\n'); +} + /** Open Google Cloud Storage for screenshots */ export function openScreenshotsCloudStorage() { // Enable Storage @@ -242,7 +246,7 @@ export function openScreenshotsCloudStorage() { projectId: 'material2-screenshots', credentials: { client_email: 'firebase-adminsdk-t4209@material2-screenshots.iam.gserviceaccount.com', - private_key: (process.env['MATERIAL2_SCREENSHOT_FIREBASE_KEY'] || '').replace(/\\n/g, '\n') + private_key: decode(process.env['MATERIAL2_SCREENSHOT_FIREBASE_KEY']) }, }); @@ -251,7 +255,7 @@ export function openScreenshotsCloudStorage() { } /** Opens a connection to the firebase realtime database for screenshots. */ -export function openScreenshotsFirebaseDatabase() { +export function openFirebaseScreenshotsDatabase() { // Initialize the Firebase application with admin credentials. // Credentials need to be for a Service Account, which can be created in the Firebase console. let screenshotApp = firebaseAdmin.initializeApp({ @@ -260,7 +264,7 @@ export function openScreenshotsFirebaseDatabase() { client_email: 'firebase-adminsdk-t4209@material2-screenshots.iam.gserviceaccount.com', // In Travis CI the private key will be incorrect because the line-breaks are escaped. // The line-breaks need to persist in the service account private key. - private_key: (process.env['MATERIAL2_SCREENSHOT_FIREBASE_KEY'] || '').replace(/\\n/g, '\n') + private_key: decode(process.env['MATERIAL2_SCREENSHOT_FIREBASE_KEY']) }), databaseURL: 'https://material2-screenshots.firebaseio.com' }, 'material2-screenshots'); diff --git a/tools/gulp/tasks/payload.ts b/tools/gulp/tasks/payload.ts index ec83a652f259..45ec8bb25238 100644 --- a/tools/gulp/tasks/payload.ts +++ b/tools/gulp/tasks/payload.ts @@ -2,7 +2,7 @@ import {task} from 'gulp'; import {join} from 'path'; import {statSync, readFileSync} from 'fs'; import {DIST_COMPONENTS_ROOT} from '../constants'; -import {openFirebaseDatabase, isTravisPushBuild} from '../task_helpers'; +import {openFirebaseDashboardDatabase, isTravisPushBuild} from '../task_helpers'; import {spawnSync} from 'child_process'; // Those imports lack types. @@ -48,7 +48,7 @@ function getUglifiedSize(filePath: string) { /** Publishes the given results to the firebase database. */ function publishResults(results: any) { let latestSha = spawnSync('git', ['rev-parse', 'HEAD']).stdout.toString().trim(); - let database = openFirebaseDatabase(); + let database = openFirebaseDashboardDatabase(); // Write the results to the payloads object with the latest Git SHA as key. return database.ref('payloads').child(latestSha).set(results) diff --git a/tools/gulp/tasks/screenshots.ts b/tools/gulp/tasks/screenshots.ts index 7c602811a907..9a600d7b016b 100644 --- a/tools/gulp/tasks/screenshots.ts +++ b/tools/gulp/tasks/screenshots.ts @@ -1,6 +1,8 @@ import {task} from 'gulp'; import {readdirSync, statSync, existsSync, mkdirSync} from 'fs'; -import {openScreenshotsCloudStorage, openScreenshotsFirebaseDatabase} from '../task_helpers'; +import {openScreenshotsCloudStorage, openFirebaseScreenshotsDatabase} from '../task_helpers'; +import * as path from 'path'; +import * as admin from 'firebase-admin'; const imageDiff = require('image-diff'); const SCREENSHOT_DIR = './screenshots'; @@ -11,38 +13,48 @@ const FIREBASE_REPORT = 'screenshot/reports'; task('screenshots', () => { let prNumber = process.env['TRAVIS_PULL_REQUEST']; if (prNumber) { - let database = openScreenshotsFirebaseDatabase(); - return getFilenameList(database) - .then((filenames: string[]) => { - return downloadReferenceScreenshots(filenames, database) - .then((results: any) => { - return compareScreenshots(filenames, database, prNumber); - }); - }) - .then((results: boolean) => { - return database.ref(FIREBASE_REPORT).child(`${prNumber}/result`).set(results); - }) - .then(() => database.ref(FIREBASE_REPORT).child(`${prNumber}/commit`).set(process.env['TRAVIS_COMMIT'])) - .then(() => setFilenameList(database, prNumber)) + let database = openFirebaseScreenshotsDatabase(); + return getScreenFilenames(database) + .then((filenames: string[]) => downloadAllGolds(filenames, database, prNumber)) + .then((results: boolean) => updateResult(database, prNumber, results)) + .then(() => setScreenFilenames(database, prNumber)) .then(() => uploadScreenshots(prNumber, 'diff')) .then(() => uploadScreenshots(prNumber, 'test')) + .then(() => updateCommit(database, prNumber)) .then(() => database.goOffline(), () => database.goOffline()); } }); +function updateFileResult(database: admin.database.Database, prNumber: string, + filenameKey: string, result: boolean): admin.Promise{ + return database.ref(FIREBASE_REPORT).child(`${prNumber}/results/${filenameKey}`).set(result); +} + +function updateResult(database: admin.database.Database, prNumber: string, + result: boolean): admin.Promise { + return database.ref(FIREBASE_REPORT).child(`${prNumber}/result`).set(result); +} + +function updateCommit(database: admin.database.Database, + prNumber: string): admin.Promise { + return database.ref(FIREBASE_REPORT).child(`${prNumber}/commit`) + .set(process.env['TRAVIS_COMMIT']); +} + /** Get a list of filenames from firebase database. */ -function getFilenameList(database: any) : Promise { - return database.ref(FIREBASE_FILELIST).once('value').then(function(snapshots: any) { +function getScreenFilenames(database: admin.database.Database): admin.Promise { + return database.ref(FIREBASE_FILELIST).once('value') + .then((snapshots: admin.database.DataSnapshot) => { return snapshots.val(); }); } -/** Upload a list of filenames to firebase database as reference. */ -function setFilenameList(database: any, - reportKey?: string): Promise { +/** Upload a list of filenames to firebase database as gold. */ +function setScreenFilenames(database: admin.database.Database, + reportKey?: string): admin.Promise { let filenames: string[] = []; - readdirSync(SCREENSHOT_DIR).map(function(file) { - let fullName = SCREENSHOT_DIR + '/' + file; + readdirSync(SCREENSHOT_DIR).map((file: string) => { + let fullName = path.join(SCREENSHOT_DIR, file); let key = file.replace('.screenshot.png', ''); if (!statSync(fullName).isDirectory() && key) { filenames.push(file); @@ -54,23 +66,31 @@ function setFilenameList(database: any, return filelistDatabase.set(filenames); } -/** Upload screenshots to google cloud storage. */ +/** + * Upload screenshots to google cloud storage. + * @param {string} reportKey - The key used in firebase. Here it is the PR number. + * If there's no reportKey, we will upload images to 'golds/' folder + * @param {string} mode - Can be 'test' or 'diff' or null. + * If the images are the test results, mode should be 'test'. + * If the images are the diff images generated, mode should be 'diff'. + * For golds mode should be null. + */ function uploadScreenshots(reportKey?: string, mode?: 'test' | 'diff') { let bucket = openScreenshotsCloudStorage(); - let promises: Promise[] = []; + let promises: admin.Promise[] = []; let localDir = mode == 'diff' ? `${SCREENSHOT_DIR}/diff` : SCREENSHOT_DIR; - readdirSync(localDir).map(function(file) { - let fileName = localDir + '/' + file; + readdirSync(localDir).map((file: string) => { + let fileName = path.join(localDir, file); let key = file.replace('.screenshot.png', ''); let destination = (mode == null || !reportKey) ? - `references/${file}` : `screenshots/${reportKey}/${mode}/${file}`; + `golds/${file}` : `screenshots/${reportKey}/${mode}/${file}`; if (!statSync(fileName).isDirectory() && key) { promises.push(bucket.upload(fileName, { destination: destination })); } }); - return Promise.all(promises); + return admin.Promise.all(promises); } /** Check whether the directory exists. If not then create one. */ @@ -80,57 +100,50 @@ function _makeDir(dirName: string) { } } -/** Download references screenshots. */ -function downloadReferenceScreenshots( - filenames: string[], database: any): Promise { - _makeDir(`${SCREENSHOT_DIR}/references`); +/** Download golds screenshots. */ +function downloadAllGolds( + filenames: string[], database: admin.database.Database, + reportKey: string): admin.Promise { + _makeDir(`${SCREENSHOT_DIR}/golds`); - return Promise.all(filenames.map((filename: string) => { - return _downloadReferenceScreenshot(filename); - })); + return admin.Promise.all(filenames.map((filename: string) => { + return downloadGold(filename).then(() => diffScreenshot(filename, database, reportKey)); + })).then((results: boolean[]) => results.every((value: boolean) => value == true)); } -/** Download one reference screenshot */ -function _downloadReferenceScreenshot(filename: string): Promise { +/** Download one gold screenshot */ +function downloadGold(filename: string): Promise { let bucket = openScreenshotsCloudStorage(); - return bucket.file(`references/${filename}`).download({ - destination: `${SCREENSHOT_DIR}/references/${filename}` + return bucket.file(`golds/${filename}`).download({ + destination: `${SCREENSHOT_DIR}/golds/${filename}` }); } -/** Compare the test result and the reference. */ -function compareScreenshots(filenames: string[], database: any, reportKey: string): Promise { - return Promise.all(filenames.map((filename) => - _compareScreenshot(filename, database, reportKey))) - .then((results: any) => results.every((value: boolean) => value == true)); -} - -function _compareScreenshot(filename: string, database: any, - reportKey: string): Promise { - let expectedUrl = `${SCREENSHOT_DIR}/references/${filename}`; - let actualUrl = `${SCREENSHOT_DIR}/${filename}`; +function diffScreenshot(filename: string, database: admin.database.Database, + reportKey: string): admin.Promise { + // TODO(tinayuangao): Run the downloads and diffs in parallel. + let goldUrl = `${SCREENSHOT_DIR}/golds/${filename}`; + let pullRequestUrl = `${SCREENSHOT_DIR}/${filename}`; let diffUrl = `${SCREENSHOT_DIR}/diff/${filename}`; let filenameKey = filename.replace('.screenshot.png', ''); - if (existsSync(expectedUrl) && existsSync(actualUrl)) { - return new Promise(function(resolve, reject) { + if (existsSync(goldUrl) && existsSync(pullRequestUrl)) { + return new admin.Promise((resolve: any, reject: any) => { imageDiff({ - actualImage: actualUrl, - expectedImage: expectedUrl, + actualImage: pullRequestUrl, + expectedImage: goldUrl, diffImage: diffUrl, - }, function (err: any, imagesAreSame: boolean) { + }, (err: any, imagesAreSame: boolean) => { if (err) { console.log(err); imagesAreSame = false; reject(err); } resolve(imagesAreSame); - return database.ref(FIREBASE_REPORT).child(`${reportKey}/results/${filenameKey}`) - .set(imagesAreSame); + return updateFileResult(database, reportKey, filenameKey, imagesAreSame); }); }); } else { - return database.ref(FIREBASE_REPORT).child(`${reportKey}/results/${filenameKey}`) - .set(false).then(() => false); + return updateFileResult(database, reportKey, filenameKey, false).then(() => false); } }