diff --git a/.travis.yml b/.travis.yml index 5864afd100865..1e76764065a73 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: node_js -sudo: required # See http://docs.travis-ci.com/user/trusty-ci-environment/ +sudo: false dist: trusty node_js: - "lts/*" @@ -14,17 +14,14 @@ notifications: webhooks: - http://savage.nonblocking.io:8080/savage/travis before_install: - - export CHROME_BIN=google-chrome-stable - - export DISPLAY=:99.0 - - unset _JAVA_OPTIONS # JVM heap sizes break closure compiler. #11203. - - sh -e /etc/init.d/xvfb start + #- export CHROME_BIN=google-chrome-stable + #- export DISPLAY=:99.0 + #- unset _JAVA_OPTIONS # JVM heap sizes break closure compiler. #11203. + #- sh -e /etc/init.d/xvfb start - curl -o- -L https://yarnpkg.com/install.sh | bash - export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" -# before_script: -# - pip install --user protobuf -# script: node build-system/pr-check.js -# after_script: -# - build-system/sauce_connect/stop_sauce_connect.sh + - pip install urllib3[secure] + - pip install gsutil --user branches: only: - master @@ -32,53 +29,72 @@ branches: - canary - /^amp-release-.*$/ - /^revert-.*$/ -addons: - chrome: stable - hosts: - - ads.localhost - - iframe.localhost - # Requested by some tests because they need a valid font host, - # but should not resolve in tests. - - fonts.googleapis.com - apt: - packages: - - protobuf-compiler - - python-protobuf stages: - build - test jobs: + fast_finish: true + allowed_failures: + - stage: test + name: "Remote (Sauce Labs) Tests" + script: + - node build-system/pr-check/remote-test.js + after_script: + - build-system/sauce_connect/stop_sauce_connect.sh include: - stage: build name: "Build" script: - - node build-system/pr-check/build-job.js #TODO(estherkim) - # - stage: build - # name: "Checks" - # script: - # - node build-system/pr-check/checks-job.js #TODO(estherkim) - # - stage: build - # name: "Validator" - # before_script: - # - pip install --user protobuf #required by validator - # script: - # - node build-system/pr-check/validator-job.js #TODO(estherkim) - # - stage: test - # name: "Dist, Bundle Size, Single Pass Tests" - # script: - # - node build-system/pr-check/dist-tests-job.js #TODO(estherkim) - # - stage: test - # name: "Visual Diff Tests" - # script: - # - node build-system/pr-check/visual-diff-tests-job.js #TODO(estherkim) - # - stage: test - # name: "Local Tests" - # script: - # - node build-system/pr-check/local-tests-job.js #TODO(estherkim) - # - stage: test - # name: "Remote (Sauce Labs) Tests" - # script: - # - node build-system/pr-check/remote-tests-job.js #TODO(estherkim) + - node build-system/pr-check/build.js + - stage: build + name: "Checks" + script: + - node build-system/pr-check/checks.js + - stage: build + name: "Validator" + before_script: + - pip install --user protobuf #required by validator + script: + - node build-system/pr-check/validator.js + addons: + apt: + packages: + - protobuf-compiler + - python-protobuf + - stage: test + name: "Dist, Bundle Size, Single Pass Tests" + script: + - node build-system/pr-check/dist-test.js + - stage: test + name: "Visual Diff Tests" + script: + - node build-system/pr-check/visual-diff-test.js + - stage: test + name: "Local Tests" + before_install: + - export CHROME_BIN=google-chrome-stable + - export DISPLAY=:99.0 + # Explicitly add yarn and gsutil install because this section overrides before_install parent section + - curl -o- -L https://yarnpkg.com/install.sh | bash + - export PATH="$HOME/.yarn/bin:$HOME/.config/yarn/global/node_modules/.bin:$PATH" + - pip install urllib3[secure] + - pip install gsutil --user + addons: + chrome: stable + hosts: + - ads.localhost + - iframe.localhost + # Requested by some tests because they need a valid font host, + # but should not resolve in tests. + - fonts.googleapis.com + script: + - node build-system/pr-check/local-test.js + - stage: test + name: "Remote (Sauce Labs) Tests" + script: + - node build-system/pr-check/remote-test.js + after_script: + - build-system/sauce_connect/stop_sauce_connect.sh cache: yarn: true directories: diff --git a/build-system/pr-check/build-job.js b/build-system/pr-check/build-job.js deleted file mode 100644 index 91df21a86ac0f..0000000000000 --- a/build-system/pr-check/build-job.js +++ /dev/null @@ -1,204 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -'use strict'; - -/** - * @fileoverview This file is executed by Travis (configured via - * .travis.yml in the root directory) and is the main driver script - * for running tests. Execution herein is entirely synchronous, that - * is, commands are executed on after the other (see the exec - * function). Should a command fail, this script will then also fail. - * This script attempts to introduce some granularity for our - * presubmit checking, via the determineBuildTargets method. - */ -const colors = require('ansi-colors'); -const command = require('./command'); -const { - determineBuildTargets, - isFlagConfig, -} = require('./build-target'); -const { - gitBranchName, - gitDiffColor, - gitDiffCommitLog, - gitDiffNameOnlyMaster, - gitDiffStatMaster, - gitMergeBaseMaster, - gitTravisMasterBaseline, - shortSha, -} = require('../git'); -const { - isTravisBuild, - isTravisPullRequestBuild, - isTravisPushBuild, - travisPullRequestSha, -} = require('../travis'); - -const {getStderr} = require('../exec'); - -const fileLogPrefix = colors.bold(colors.yellow(fileName)); -const fileName = 'build-job.js'; -/** - * Starts a timer to measure the execution time of the given function. - * @param {string} functionName - * @return {DOMHighResTimeStamp} - */ -function startTimer(functionName) { - const startTime = Date.now(); - console.log( - '\n' + fileLogPrefix, 'Running', colors.cyan(functionName) + '...'); - return startTime; -} - -/** - * Stops the timer for the given function and prints the execution time. - * @param {string} functionName - * @param {DOMHighResTimeStamp} startTime - * @return {number} - */ -function stopTimer(functionName, startTime) { - const endTime = Date.now(); - const executionTime = new Date(endTime - startTime); - const mins = executionTime.getMinutes(); - const secs = executionTime.getSeconds(); - console.log( - fileLogPrefix, 'Done running', colors.cyan(functionName), - 'Total time:', colors.green(mins + 'm ' + secs + 's')); -} - -/** - * Prints a summary of files changed by, and commits included in the PR. - */ -function printChangeSummary() { - if (isTravisBuild()) { - console.log(fileLogPrefix, colors.cyan('origin/master'), - 'is currently at commit', - colors.cyan(shortSha(gitTravisMasterBaseline()))); - console.log(fileLogPrefix, - 'Testing the following changes at commit', - colors.cyan(shortSha(travisPullRequestSha()))); - } - - const filesChanged = gitDiffStatMaster(); - console.log(filesChanged); - - const branchPoint = gitMergeBaseMaster(); - console.log(fileLogPrefix, 'Commit log since branch', - colors.cyan(gitBranchName()), 'was forked from', - colors.cyan('master'), 'at', colors.cyan(shortSha(branchPoint)) + ':'); - console.log(gitDiffCommitLog() + '\n'); -} - -/** - * Makes sure package.json and yarn.lock are in sync. - */ -function runYarnIntegrityCheck() { - const yarnIntegrityCheck = getStderr('yarn check --integrity').trim(); - if (yarnIntegrityCheck.includes('error')) { - console.error(fileLogPrefix, colors.red('ERROR:'), - 'Found the following', colors.cyan('yarn'), 'errors:\n' + - colors.cyan(yarnIntegrityCheck)); - console.error(fileLogPrefix, colors.red('ERROR:'), - 'Updates to', colors.cyan('package.json'), - 'must be accompanied by a corresponding update to', - colors.cyan('yarn.lock')); - console.error(fileLogPrefix, colors.yellow('NOTE:'), - 'To update', colors.cyan('yarn.lock'), 'after changing', - colors.cyan('package.json') + ',', 'run', - '"' + colors.cyan('yarn install') + '"', - 'and include the updated', colors.cyan('yarn.lock'), - 'in your PR.'); - process.exit(1); - } -} - -/** - * Makes sure that yarn.lock was properly updated. - */ -function runYarnLockfileCheck() { - const localChanges = gitDiffColor(); - if (localChanges.includes('yarn.lock')) { - console.error(fileLogPrefix, colors.red('ERROR:'), - 'This PR did not properly update', colors.cyan('yarn.lock') + '.'); - console.error(fileLogPrefix, colors.yellow('NOTE:'), - 'To fix this, sync your branch to', colors.cyan('upstream/master') + - ', run', colors.cyan('gulp update-packages') + - ', and push a new commit containing the changes.'); - console.error(fileLogPrefix, 'Expected changes:'); - console.log(localChanges); - process.exit(1); - } -} - -/** - * The main method for the script execution which much like a C main function - * receives the command line arguments and returns an exit status. - * @return {number} - */ -function main() { - const startTime = startTimer(fileName); - - // Make sure package.json and yarn.lock are in sync and up-to-date. - runYarnIntegrityCheck(); - runYarnLockfileCheck(); - - console.log( - fileLogPrefix, 'Running build shard', - colors.cyan(process.env.BUILD_SHARD), - '\n'); - - printChangeSummary(); - const files = gitDiffNameOnlyMaster(); - const buildTargets = determineBuildTargets(files); - - // Exit early if flag-config files are mixed with runtime files. - if (buildTargets.has('FLAG_CONFIG') && buildTargets.has('RUNTIME')) { - console.log(fileLogPrefix, colors.red('ERROR:'), - 'Looks like your PR contains', - colors.cyan('{prod|canary}-config.json'), - 'in addition to some other files. Config and code are not kept in', - 'sync, and config needs to be backwards compatible with code for at', - 'least two weeks. See #8188'); - const nonFlagConfigFiles = files.filter(file => !isFlagConfig(file)); - console.log(fileLogPrefix, colors.red('ERROR:'), - 'Please move these files to a separate PR:', - colors.cyan(nonFlagConfigFiles.join(', '))); - stopTimer(fileName, startTime); - process.exit(1); - } - - console.log( - fileLogPrefix, 'Detected build targets:', - colors.cyan(Array.from(buildTargets).sort().join(', '))); - - if (isTravisPullRequestBuild()) { - command.updatePackages(); - if (buildTargets.has('BUILD_SYSTEM') || - buildTargets.has('RUNTIME')) { - command.testBuildSystem(); - } - } else if (isTravisPushBuild()) { - command.updatePackages(); - command.testBuildSystem(); - command.cleanBuild(); - command.buildRuntime(); - } - - stopTimer(fileName, startTime); - return 0; -} - -process.exit(main()); diff --git a/build-system/pr-check/build-target.js b/build-system/pr-check/build-target.js index 102782cf59bd0..45806e2111302 100644 --- a/build-system/pr-check/build-target.js +++ b/build-system/pr-check/build-target.js @@ -24,9 +24,10 @@ * This script attempts to introduce some granularity for our * presubmit checking, via the determineBuildTargets method. */ -const config = require('./config'); +const config = require('../config'); const minimatch = require('minimatch'); const path = require('path'); +const {gitDiffNameOnlyMaster} = require('../git'); /** * Determines whether the given file belongs to the Validator webui, @@ -186,10 +187,11 @@ function isFlagConfig(filePath) { /** * Determines the targets that will be executed by the main method of * this script. The order within this function matters. - * @param {!Array} filePaths * @return {!Set} */ -function determineBuildTargets(filePaths) { +function determineBuildTargets() { + const filePaths = gitDiffNameOnlyMaster(); + if (filePaths.length == 0) { return new Set([ 'BUILD_SYSTEM', diff --git a/build-system/pr-check/build.js b/build-system/pr-check/build.js new file mode 100644 index 0000000000000..b39150d2f798e --- /dev/null +++ b/build-system/pr-check/build.js @@ -0,0 +1,141 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + * This script builds the AMP runtime. + * This is run during the CI stage = build; job = build. + */ + +//TODO(estherkim): move to util file +const colors = require('ansi-colors'); +const { + gitBranchName, + gitDiffColor, + gitDiffCommitLog, + gitDiffStatMaster, + gitMergeBaseMaster, + gitTravisMasterBaseline, + shortSha, +} = require('../git'); +const { + isTravisBuild, + travisPullRequestSha, +} = require('../travis'); +const {determineBuildTargets} = require('./build-target'); +const {getStderr} = require('../exec'); +const {startTimer, stopTimer, timedExecOrDie, zipBuildOutput} = require('./utils'); + +const FILENAME = 'build.js'; +const FILELOGPREFIX = colors.bold(colors.yellow(`${FILENAME}:`)); + +/** + * Prints a summary of files changed by, and commits included in the PR. + */ +function printChangeSummary_() { + if (isTravisBuild()) { + console.log(FILELOGPREFIX, colors.cyan('origin/master'), + 'is currently at commit', + colors.cyan(shortSha(gitTravisMasterBaseline()))); + console.log(FILELOGPREFIX, + 'Testing the following changes at commit', + colors.cyan(shortSha(travisPullRequestSha()))); + } + + const filesChanged = gitDiffStatMaster(); + console.log(filesChanged); + + const branchPoint = gitMergeBaseMaster(); + console.log(FILELOGPREFIX, 'Commit log since branch', + colors.cyan(gitBranchName()), 'was forked from', + colors.cyan('master'), 'at', colors.cyan(shortSha(branchPoint)) + ':'); + console.log(gitDiffCommitLog() + '\n'); +} + +/** + * Makes sure package.json and yarn.lock are in sync. + * @private + */ +function runYarnIntegrityCheck_() { + const yarnIntegrityCheck = getStderr('yarn check --integrity').trim(); + if (yarnIntegrityCheck.includes('error')) { + console.error(FILELOGPREFIX, colors.red('ERROR:'), + 'Found the following', colors.cyan('yarn'), 'errors:\n' + + colors.cyan(yarnIntegrityCheck)); + console.error(FILELOGPREFIX, colors.red('ERROR:'), + 'Updates to', colors.cyan('package.json'), + 'must be accompanied by a corresponding update to', + colors.cyan('yarn.lock')); + console.error(FILELOGPREFIX, colors.yellow('NOTE:'), + 'To update', colors.cyan('yarn.lock'), 'after changing', + colors.cyan('package.json') + ',', 'run', + '"' + colors.cyan('yarn install') + '"', + 'and include the updated', colors.cyan('yarn.lock'), + 'in your PR.'); + process.exit(1); + } +} + +/** + * Makes sure that yarn.lock was properly updated. + * @private + */ +function runYarnLockfileCheck_() { + const localChanges = gitDiffColor(); + if (localChanges.includes('yarn.lock')) { + console.error(FILELOGPREFIX, colors.red('ERROR:'), + 'This PR did not properly update', colors.cyan('yarn.lock') + '.'); + console.error(FILELOGPREFIX, colors.yellow('NOTE:'), + 'To fix this, sync your branch to', colors.cyan('upstream/master') + + ', run', colors.cyan('gulp update-packages') + + ', and push a new commit containing the changes.'); + console.error(FILELOGPREFIX, 'Expected changes:'); + console.log(localChanges); + process.exit(1); + } +} + +function main() { + const startTime = startTimer(FILENAME); + + // Make sure package.json and yarn.lock are in sync and up-to-date. + runYarnIntegrityCheck_(); + runYarnLockfileCheck_(); + printChangeSummary_(); + const buildTargets = determineBuildTargets(); + + if (buildTargets.has('RUNTIME') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('BUILD_SYSTEM')) { + + timedExecOrDie('gulp update-packages'); + timedExecOrDie('gulp css'); + timedExecOrDie('gulp build --fortesting'); + + zipBuildOutput(); + } else { + console.log('Skipping build job because this commit does ' + + 'not affect the runtime, build system, or test files'); + } + + stopTimer(FILENAME, startTime); + return 0; +} + +process.exit(main()); + diff --git a/build-system/pr-check/checks.js b/build-system/pr-check/checks.js new file mode 100644 index 0000000000000..1c93faf1b66d7 --- /dev/null +++ b/build-system/pr-check/checks.js @@ -0,0 +1,73 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + * This script performs quick checks on the source code + * prior to running unit and integration tests. + * This is run during the CI stage = build; job = checks. + */ + +const {determineBuildTargets} = require('./build-target'); +const {isTravisPushBuild} = require('../travis'); +const {startTimer, stopTimer, timedExecOrDie} = require('./utils'); + +const FILENAME = 'checks.js'; + +function main() { + const startTime = startTimer(FILENAME); + const buildTargets = determineBuildTargets(); + + timedExecOrDie('gulp update-packages'); + timedExecOrDie('gulp presubmit'); + timedExecOrDie('gulp lint'); + + if (isTravisPushBuild()) { + timedExecOrDie('gulp ava'); + timedExecOrDie('node node_modules/jest/bin/jest.js'); + timedExecOrDie('gulp check-types'); + timedExecOrDie('gulp caches-json'); + timedExecOrDie('gulp json-syntax'); + timedExecOrDie('gulp dep-check'); + } + else { + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM')) { + timedExecOrDie('gulp ava'); + timedExecOrDie('node node_modules/jest/bin/jest.js'); + } + + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST') || + buildTargets.has('INTEGRATION_TEST')) { + timedExecOrDie('gulp check-types'); + timedExecOrDie('gulp caches-json'); + timedExecOrDie('gulp json-syntax'); + timedExecOrDie('gulp dep-check'); + } + + if (buildTargets.has('DOCS')) { + timedExecOrDie('gulp check-links'); + } + } + + stopTimer(FILENAME, startTime); + return 0; +} + +process.exit(main()); diff --git a/build-system/pr-check/command.js b/build-system/pr-check/command.js deleted file mode 100644 index 4d4cd71fc85ca..0000000000000 --- a/build-system/pr-check/command.js +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Copyright 2019 The AMP HTML Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS-IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -'use strict'; - -/** - * @fileoverview This file is executed by Travis (configured via - * .travis.yml in the root directory) and is the main driver script - * for running tests. Execution herein is entirely synchronous, that - * is, commands are executed on after the other (see the exec - * function). Should a command fail, this script will then also fail. - * This script attempts to introduce some granularity for our - * presubmit checking, via the determineBuildTargets method. - */ -const argv = require('minimist')(process.argv.slice(2)); -const atob = require('atob'); -const colors = require('ansi-colors'); -const {execOrDie, exec, getStdout} = require('./exec'); -const fileLogPrefix = colors.bold(colors.yellow(fileName)); -const fileName = 'command.js'; -function startTimer(functionName) { - const startTime = Date.now(); - console.log( - '\n' + fileLogPrefix, 'Running', colors.cyan(functionName) + '...'); - return startTime; -} - -/** - * Stops the timer for the given function and prints the execution time. - * @param {string} functionName - * @param {DOMHighResTimeStamp} startTime - * @return {number} - */ -function stopTimer(functionName, startTime) { - const endTime = Date.now(); - const executionTime = new Date(endTime - startTime); - const mins = executionTime.getMinutes(); - const secs = executionTime.getSeconds(); - console.log( - fileLogPrefix, 'Done running', colors.cyan(functionName), - 'Total time:', colors.green(mins + 'm ' + secs + 's')); -} - -/** - * Executes the provided command and times it. Errors, if any, are printed. - * @param {string} cmd - * @return {} Process info. - */ -function timedExec(cmd) { - const startTime = startTimer(cmd); - const p = exec(cmd); - stopTimer(cmd, startTime); - return p; -} - -/** - * Executes the provided command and times it. The program terminates in case of - * failure. - * @param {string} cmd - */ -function timedExecOrDie(cmd) { - const startTime = startTimer(cmd); - execOrDie(cmd); - stopTimer(cmd, startTime); -} - -function startSauceConnect() { - process.env['SAUCE_USERNAME'] = 'amphtml'; - process.env['SAUCE_ACCESS_KEY'] = getStdout('curl --silent ' + - 'https://amphtml-sauce-token-dealer.appspot.com/getJwtToken').trim(); - const startScCmd = 'build-system/sauce_connect/start_sauce_connect.sh'; - console.log('\n' + fileLogPrefix, - 'Starting Sauce Connect Proxy:', colors.cyan(startScCmd)); - execOrDie(startScCmd); -} - -function stopSauceConnect() { - const stopScCmd = 'build-system/sauce_connect/stop_sauce_connect.sh'; - console.log('\n' + fileLogPrefix, - 'Stopping Sauce Connect Proxy:', colors.cyan(stopScCmd)); - execOrDie(stopScCmd); -} - -const command = { - testBuildSystem: function() { - timedExecOrDie('gulp ava'); - timedExecOrDie('node node_modules/jest/bin/jest.js'); - }, - testDocumentLinks: function() { - timedExecOrDie('gulp check-links'); - }, - cleanBuild: function() { - timedExecOrDie('gulp clean'); - }, - runLintCheck: function() { - timedExecOrDie('gulp lint'); - }, - runJsonCheck: function() { - timedExecOrDie('gulp caches-json'); - timedExecOrDie('gulp json-syntax'); - }, - buildCss: function() { - timedExecOrDie('gulp css'); - }, - buildRuntime: function() { - timedExecOrDie('gulp build --fortesting'); - }, - buildRuntimeMinified: function(extensions) { - let cmd = 'gulp dist --fortesting'; - if (!extensions) { - cmd = cmd + ' --noextensions'; - } - timedExecOrDie(cmd); - }, - runBundleSizeCheck: function(action) { - timedExecOrDie(`gulp bundle-size --on_${action}_build`); - }, - runDepAndTypeChecks: function() { - timedExecOrDie('gulp dep-check'); - timedExecOrDie('gulp check-types'); - }, - runUnitTests: function() { - let cmd = 'gulp test --unit --nobuild'; - if (argv.files) { - cmd = cmd + ' --files ' + argv.files; - } - // Unit tests with Travis' default chromium in coverage mode. - timedExecOrDie(cmd + ' --headless --coverage'); - - cmd = cmd + ' --saucelabs_lite'; - startSauceConnect(); - timedExecOrDie(cmd); - stopSauceConnect(); - }, - runUnitTestsOnLocalChanges: function() { - timedExecOrDie('gulp test --nobuild --headless --local-changes'); - }, - runDevDashboardTests: function() { - timedExecOrDie('gulp test --dev_dashboard --nobuild'); - }, - runIntegrationTests: function(compiled, coverage) { - // Integration tests on chrome, or on all saucelabs browsers if set up - let cmd = 'gulp test --integration --nobuild'; - if (argv.files) { - cmd = cmd + ' --files ' + argv.files; - } - if (compiled) { - cmd += ' --compiled'; - } - - if (coverage) { - // TODO(choumx, #19658): --headless disabled for integration tests on - // Travis until Chrome 72. - timedExecOrDie(cmd + ' --coverage'); - } else { - startSauceConnect(); - timedExecOrDie(cmd + ' --saucelabs'); - stopSauceConnect(); - } - }, - runSinglePassCompiledIntegrationTests: function() { - timedExecOrDie('rm -R dist'); - timedExecOrDie('gulp dist --fortesting --single_pass --pseudo_names'); - // TODO(choumx, #19658): --headless disabled for integration tests on - // Travis until Chrome 72. - timedExecOrDie('gulp test --integration --nobuild ' - + '--compiled --single_pass'); - timedExecOrDie('rm -R dist'); - }, - runVisualDiffTests: function(opt_mode) { - process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); - let cmd = 'gulp visual-diff --nobuild'; - if (opt_mode === 'empty') { - cmd += ' --empty'; - } else if (opt_mode === 'master') { - cmd += ' --master'; - } - const {status} = timedExec(cmd); - if (status != 0) { - console.error(fileLogPrefix, colors.red('ERROR:'), - 'Found errors while running', colors.cyan(cmd)); - } - }, - runPresubmitTests: function() { - timedExecOrDie('gulp presubmit'); - }, - buildValidatorWebUI: function() { - timedExecOrDie('gulp validator-webui'); - }, - buildValidator: function() { - timedExecOrDie('gulp validator'); - }, - updatePackages: function() { - timedExecOrDie('gulp update-packages'); - }, -}; - -module.exports = { - command, -}; diff --git a/build-system/pr-check/dist-test.js b/build-system/pr-check/dist-test.js new file mode 100644 index 0000000000000..08c95e4b3afc9 --- /dev/null +++ b/build-system/pr-check/dist-test.js @@ -0,0 +1,72 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + * This script runs the bundle size check and single pass test. + * This is run during the CI stage = test; job = dist tests. + */ + +const {determineBuildTargets} = require('./build-target'); +const {isTravisPushBuild} = require('../travis'); +const {startTimer, stopTimer, timedExecOrDie, unzipBuildOutput} = require('./utils'); + +const FILENAME = 'dist-test.js'; + +function runSinglePassTest_() { + timedExecOrDie('rm -R dist'); + timedExecOrDie('gulp dist --fortesting --single_pass --psuedonames'); + timedExecOrDie('gulp test --integration' + + '--nobuild --compiled --single_pass'); + timedExecOrDie('rm -R dist'); +} + +function main() { + const startTime = startTimer(FILENAME); + const buildTargets = determineBuildTargets(); + + if (isTravisPushBuild()) { + timedExecOrDie('gulp dist --fortesting --noextensions'); + timedExecOrDie('gulp bundle-size --on-push-build'); + runSinglePassTest_(); + } + else { + let runTests = buildTargets.has('RUNTIME'); + + unzipBuildOutput(); //TODO(estherkim): does this belong here? + timedExecOrDie('gulp dist --fortesting --noextensions'); + timedExecOrDie('gulp bundle-size ' + + `${buildTargets.has('RUNTIME') ? '--on_pr_build' : '--on_skipped_build'}`); + + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST')) { + runSinglePassTest_(); + runTests = true; + } + + if (!runTests) { + console.log('Skipping dist tests because this commit does ' + + 'not affect the runtime, build system, or integration test files.'); + } + } + + stopTimer(FILENAME, startTime); + return 0; +} + +process.exit(main()); diff --git a/build-system/pr-check/local-test.js b/build-system/pr-check/local-test.js new file mode 100644 index 0000000000000..28124c83a28cd --- /dev/null +++ b/build-system/pr-check/local-test.js @@ -0,0 +1,82 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + *This script runs the unit and integration tests on a local Travis VM. + * This is run during the CI stage = test; job = local tests. + */ + +const {determineBuildTargets} = require('./build-target'); +const {isTravisPushBuild} = require('../travis'); +const {startTimer, stopTimer, timedExecOrDie, unzipBuildOutput} = require('./utils'); + +const FILENAME = 'local-test.js'; + +function main() { + const startTime = startTimer(FILENAME); + const buildTargets = determineBuildTargets(); + unzipBuildOutput(); + if (isTravisPushBuild()) { + timedExecOrDie('gulp test --integration --nobuild --coverage'); + timedExecOrDie('gulp test --unit --nobuild --headless --coverage'); + timedExecOrDie('gulp test --dev_dashboard --nobuild'); + //TODO(estherkim): turn on when stabilized :) + //timedExecOrDie('gulp e2e --nobuild'); + } + else { + let runTests = false; + + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('UNIT_TEST')) { + timedExecOrDie('gulp test --nobuild --headless --local-changes'); + runTests = true; + } + + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST')) { + timedExecOrDie('gulp test --integraton --nobuild --headless --coverage'); + runTests = true; + } + + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM')) { + timedExecOrDie('gulp test --unit --nobuild --headless --coverage'); + //TODO(estherkim): turn on when stabilized :) + //timedExecOrDie('gulp e2e --nobuild'); + runTests = true; + } + + if (buildTargets.has('DEV_DASHBOARD')) { + timedExecOrDie('gulp test --dev_dashboard --nobuild'); + runTests = true; + } + + if (!runTests) { + console.log('Skipping unit and integration tests because ' + + 'this commit not affect the runtime, build system, ' + + 'unit test files, integration test files, or the dev dashboard.'); + } + } + + stopTimer(FILENAME, startTime); + return 0; +} + +process.exit(main()); diff --git a/build-system/pr-check/remote-test.js b/build-system/pr-check/remote-test.js new file mode 100644 index 0000000000000..fe9f669fda88a --- /dev/null +++ b/build-system/pr-check/remote-test.js @@ -0,0 +1,73 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + * This script kicks off the unit and integration tests on Sauce Labs. + * This is run during the CI stage = test; job = remote tests. + */ + +const {determineBuildTargets} = require('./build-target'); +const {isTravisPushBuild} = require('../travis'); +const {startTimer, + stopTimer, + startSauceConnect, + stopSauceConnect, + timedExecOrDie, + unzipBuildOutput} = require('./utils'); + +const FILENAME = 'remote-test.js'; + +function main() { + const startTime = startTimer(FILENAME); + const buildTargets = determineBuildTargets(); + unzipBuildOutput(); + startSauceConnect(FILENAME); + + if (isTravisPushBuild()) { + timedExecOrDie('gulp test --unit --nobuild --saucelabs_lite'); + timedExecOrDie('gulp test --integration --nobuild --compiled --saucelabs'); + } + else { + let runTests = false; + + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM')) { + timedExecOrDie('gulp test --unit --nobuild --saucelabs_lite'); + runTests = true; + } + + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST')) { + timedExecOrDie('gulp test --integration --nobuild --saucelabs'); + runTests = true; + } + + if (!runTests) { + console.log('Skipping Sauce Labs unit and integration tests because ' + + 'this commit not affect the runtime, build system, ' + + 'or integration test files.'); + } + } + + stopSauceConnect(FILENAME); + stopTimer(FILENAME, startTime); + return 0; +} + +process.exit(main()); diff --git a/build-system/pr-check/utils.js b/build-system/pr-check/utils.js new file mode 100644 index 0000000000000..35aa7bd7b78ec --- /dev/null +++ b/build-system/pr-check/utils.js @@ -0,0 +1,140 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +const colors = require('ansi-colors'); +const {execOrDie, exec, getStdout} = require('../exec'); +const {travisBuildNumber} = require('../travis'); +const BUILD_OUTPUT_FILE = `amp_build_${travisBuildNumber()}.zip`; +const BUILD_OUTPUT_DIRS = 'build/ dist/ dist.3p/'; +const BUILD_OUTPUT_STORAGE_LOCATION = 'gs://amp-travis-builds'; + +/** + * Starts connection to Sauce Labs after getting account credentials + * @param {string} functionName + */ +function startSauceConnect(functionName) { + process.env['SAUCE_USERNAME'] = 'amphtml'; + process.env['SAUCE_ACCESS_KEY'] = getStdout('curl --silent ' + + 'https://amphtml-sauce-token-dealer.appspot.com/getJwtToken').trim(); + const startScCmd = 'build-system/sauce_connect/start_sauce_connect.sh'; + const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); + console.log('\n' + fileLogPrefix, + 'Starting Sauce Connect Proxy:', colors.cyan(startScCmd)); + execOrDie(startScCmd); +} + +/** + * Stops connection to Sauce Labs + * @param {string} functionName + */ +function stopSauceConnect(functionName) { + const stopScCmd = 'build-system/sauce_connect/stop_sauce_connect.sh'; + const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); + console.log('\n' + fileLogPrefix, + 'Stopping Sauce Connect Proxy:', colors.cyan(stopScCmd)); + execOrDie(stopScCmd); +} + +/** + * Starts a timer to measure the execution time of the given function. + * @param {string} functionName + * @return {DOMHighResTimeStamp} + */ +function startTimer(functionName) { + const startTime = Date.now(); + const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); + console.log( + '\n' + fileLogPrefix, 'Running', colors.cyan(functionName) + '...'); + return startTime; +} + +/** + * Stops the timer for the given function and prints the execution time. + * @param {string} functionName + * @param {DOMHighResTimeStamp} startTime + * @return {number} + */ +function stopTimer(functionName, startTime) { + const endTime = Date.now(); + const executionTime = new Date(endTime - startTime); + const mins = executionTime.getMinutes(); + const secs = executionTime.getSeconds(); + const fileLogPrefix = colors.bold(colors.yellow(`${functionName}:`)); + console.log( + fileLogPrefix, 'Done running', colors.cyan(functionName), + 'Total time:', colors.green(mins + 'm ' + secs + 's')); +} + +/** + * Executes the provided command and times it. Errors, if any, are printed. + * @param {string} cmd + * @return {} Process info. + */ +function timedExec(cmd) { + const startTime = startTimer(cmd); + const p = exec(cmd); + stopTimer(cmd, startTime); + return p; +} + +/** + * Executes the provided command and times it. The program terminates in case of + * failure. + * @param {string} cmd + */ +function timedExecOrDie(cmd) { + const startTime = startTimer(cmd); + execOrDie(cmd); + stopTimer(cmd, startTime); +} + + +function unzipBuildOutput() { + timedExecOrDie('gsutil cp ' + + `${BUILD_OUTPUT_STORAGE_LOCATION}/${BUILD_OUTPUT_FILE} ` + + `${BUILD_OUTPUT_FILE}`); + timedExecOrDie(`ls ${BUILD_OUTPUT_FILE}`); + timedExecOrDie( + `log="$(unzip -o ${BUILD_OUTPUT_FILE})" && ` + + 'echo travis_fold:start:unzip_results && echo ${log} && ' + + 'echo travis_fold:end:unzip_results'); + timedExecOrDie( + `log="$(ls -la ${BUILD_OUTPUT_DIRS})" && ` + + 'echo travis_fold:start:verify_unzip_results && echo ${log} && ' + + 'echo travis_fold:end:verify_unzip_results'); +} + +function zipBuildOutput() { + timedExecOrDie( + `log="$(zip -r ${BUILD_OUTPUT_FILE} ${BUILD_OUTPUT_DIRS})" && ` + + 'echo travis_fold:start:zip_results && echo ${log} && ' + + 'echo travis_fold:end:zip_results'); + timedExecOrDie(`gsutil -m cp -r ${BUILD_OUTPUT_FILE} ` + + `${BUILD_OUTPUT_STORAGE_LOCATION}`); +} + + +module.exports = { + startTimer, + stopTimer, + startSauceConnect, + stopSauceConnect, + timedExec, + timedExecOrDie, + unzipBuildOutput, + zipBuildOutput, +}; diff --git a/build-system/pr-check/validator.js b/build-system/pr-check/validator.js new file mode 100644 index 0000000000000..a6f8699c8121d --- /dev/null +++ b/build-system/pr-check/validator.js @@ -0,0 +1,53 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + * This script runs validator tests. + * This is run during the CI stage = build; job = validator. + */ + +const {determineBuildTargets} = require('./build-target'); +const {isTravisPushBuild} = require('../travis'); +const {startTimer, stopTimer, timedExecOrDie} = require('./utils'); + +const FILENAME = 'validator.js'; + +function main() { + const startTime = startTimer(FILENAME); + const buildTargets = determineBuildTargets(); + + if (isTravisPushBuild()) { + timedExecOrDie('gulp validator'); + timedExecOrDie('gulp validator-webui'); + } + else if (buildTargets.has('VALIDATOR')) { + timedExecOrDie('gulp validator'); + } + else if (buildTargets.has('VALIDATOR_WEBUI')) { + timedExecOrDie('gulp validator-webui'); + } + else { + console.log('Skipping validator job because this commit does ' + + 'not affect the validator or validator web UI.'); + } + + stopTimer(FILENAME, startTime); + return 0; +} + +process.exit(main()); diff --git a/build-system/pr-check/visual-diff-test.js b/build-system/pr-check/visual-diff-test.js new file mode 100644 index 0000000000000..6dc57ddea4f0b --- /dev/null +++ b/build-system/pr-check/visual-diff-test.js @@ -0,0 +1,62 @@ +/** + * Copyright 2019 The AMP HTML Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +'use strict'; + +/** + * @fileoverview + * This script runs the visual diff tests. + * This is run during the CI stage = test; job = visual diff tests. + */ + +const atob = require('atob'); +const {determineBuildTargets} = require('./build-target'); +const {isTravisPushBuild} = require('../travis'); +const {startTimer, stopTimer, timedExecOrDie, unzipBuildOutput} = require('./utils'); + +const FILENAME = 'visual-diff-test.js'; + +function main() { + const startTime = startTimer(FILENAME); + const buildTargets = determineBuildTargets(); + + if (isTravisPushBuild()) { + unzipBuildOutput(); + process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); + timedExecOrDie('gulp visual-diff --nobuild --master'); + } + else { + if (buildTargets.has('RUNTIME') || + buildTargets.has('BUILD_SYSTEM') || + buildTargets.has('INTEGRATION_TEST') || + buildTargets.has('VISUAL_DIFF') || + buildTargets.has('FLAG_CONFIG')) { + unzipBuildOutput(); + process.env['PERCY_TOKEN'] = atob(process.env.PERCY_TOKEN_ENCODED); + timedExecOrDie('gulp visual-diff --nobuild'); + } + else { + timedExecOrDie('gulp visual-diff --nobuild --empty'); + console.log('Skipping visual diff tests because this commit does ' + + 'not affect the runtime, build system, integration test files, ' + + 'visual diff test files, or flag config files.'); + } + } + + stopTimer(FILENAME, startTime); + return 0; +} + +process.exit(main()); diff --git a/build-system/tasks/presubmit-checks.js b/build-system/tasks/presubmit-checks.js index 384a9a0e1fd6d..a467fa462ed85 100644 --- a/build-system/tasks/presubmit-checks.js +++ b/build-system/tasks/presubmit-checks.js @@ -104,6 +104,14 @@ const forbiddenTerms = { 'build-system/app.js', 'build-system/amp4test.js', 'build-system/check-package-manager.js', + 'build-system/pr-check/build.js', + 'build-system/pr-check/checks.js', + 'build-system/pr-check/dist-test.js', + 'build-system/pr-check/local-test.js', + 'build-system/pr-check/remote-test.js', + 'build-system/pr-check/utils.js', + 'build-system/pr-check/validator.js', + 'build-system/pr-check/visual-diff-test.js', 'validator/nodejs/index.js', // NodeJs only. 'validator/engine/parse-css.js', 'validator/engine/validator-in-browser.js', diff --git a/build-system/travis.js b/build-system/travis.js index 5519d6caacac5..b8d023338440e 100644 --- a/build-system/travis.js +++ b/build-system/travis.js @@ -50,6 +50,18 @@ exports.isTravisPushBuild = function() { process.env.TRAVIS_EVENT_TYPE === 'push'; }; +/** + * Returns the build number of the ongoing Travis build. + * @return {string} + */ +exports.travisBuildNumber = function() { + if (!exports.isTravisBuild()) { + log(red('ERROR:'), 'This is not a Travis build. Cannot get', + cyan('process.env.TRAVIS_BUILD_NUMBER') + '.'); + } + return process.env.TRAVIS_BUILD_NUMBER; +}; + /** * Returns the job number of the ongoing Travis build. * @return {string}