From 5a64a9a7f7974c7416d41b003779ee6d92ca5ebd Mon Sep 17 00:00:00 2001 From: Neil Fraser Date: Fri, 25 Nov 2022 20:45:00 +0100 Subject: [PATCH] fix: Fix the compiler test, and check if it worked. (#6638) * Add tsick.js to rewrite enums. tsc generates JavaScript which is incompatible with the Closure Compiler's advanced optimizations. * Remove unused 'outputCode' variable. * Rename 'run_X_in_browser.js' to 'webdriver.js' The Mocha and generator tests can both be run either manually or via our webdriver. In all cases they run in a browser. These two 'run_X_in_browser.js' files only apply to webdriver, thus they are confusingly named. Also delete completely unused (and broken) `run_all_tests.sh` * Linting improvements to mocha/webdriver.js Still not at 100%. Complains about require/module/process/__dirname not being defined in multiple places. * runTestBlock -> runTestFunction 'Block' means something very different in Blockly. * Removal of `var` from scripts. * Add webdriver test to verify compile test worked. * Resolve conficts with 'develop'. * Address PR comments. --- .eslintignore | 2 +- scripts/gulpfiles/appengine_tasks.js | 22 ++--- scripts/gulpfiles/build_tasks.js | 55 +++++++----- scripts/gulpfiles/config.js | 2 +- scripts/gulpfiles/git_tasks.js | 26 +++--- scripts/gulpfiles/package_tasks.js | 16 ++-- scripts/gulpfiles/release_tasks.js | 46 +++++----- scripts/gulpfiles/test_tasks.js | 89 ++++++++++--------- scripts/tsick.js | 83 +++++++++++++++++ tests/compile/main.js | 22 ++++- tests/compile/webdriver.js | 82 +++++++++++++++++ tests/generators/index.html | 8 +- ..._generators_in_browser.js => webdriver.js} | 11 +-- ...mocha_tests_in_browser.js => webdriver.js} | 39 ++++---- 14 files changed, 349 insertions(+), 154 deletions(-) create mode 100644 scripts/tsick.js create mode 100644 tests/compile/webdriver.js rename tests/generators/{run_generators_in_browser.js => webdriver.js} (91%) rename tests/mocha/{run_mocha_tests_in_browser.js => webdriver.js} (75%) diff --git a/.eslintignore b/.eslintignore index 26e22adb55d..b8e1c93a818 100644 --- a/.eslintignore +++ b/.eslintignore @@ -7,7 +7,7 @@ /tests/compile/* /tests/jsunit/* /tests/generators/* -/tests/mocha/run_mocha_tests_in_browser.js +/tests/mocha/webdriver.js /tests/screenshot/* /tests/test_runner.js /tests/workspace_svg/* diff --git a/scripts/gulpfiles/appengine_tasks.js b/scripts/gulpfiles/appengine_tasks.js index ea5b6a53aca..cb9ff8fed31 100644 --- a/scripts/gulpfiles/appengine_tasks.js +++ b/scripts/gulpfiles/appengine_tasks.js @@ -8,16 +8,16 @@ * @fileoverview Gulp script to deploy Blockly demos on appengine. */ -var gulp = require('gulp'); +const gulp = require('gulp'); -var fs = require('fs'); -var rimraf = require('rimraf'); -var path = require('path'); -var execSync = require('child_process').execSync; +const fs = require('fs'); +const rimraf = require('rimraf'); +const path = require('path'); +const execSync = require('child_process').execSync; const buildTasks = require('./build_tasks.js'); const packageTasks = require('./package_tasks.js'); -var packageJson = require('../../package.json'); +const packageJson = require('../../package.json'); const demoTmpDir = '../_deploy'; const demoStaticTmpDir = '../_deploy/static'; @@ -121,10 +121,10 @@ function deployAndClean(done) { * Constructs a beta demo version name based on the current date. */ function getDemosBetaVersion() { - var date = new Date(); - var mm = date.getMonth() + 1; // Month, 0-11 - var dd = date.getDate(); // Day of the month, 1-31 - var yyyy = date.getFullYear(); + const date = new Date(); + const mm = date.getMonth() + 1; // Month, 0-11 + const dd = date.getDate(); // Day of the month, 1-31 + const yyyy = date.getFullYear(); return `${yyyy}${mm < 10 ? '0' + mm : mm}${dd}-beta`; } @@ -140,7 +140,7 @@ function deployBetaAndClean(done) { /** * Prepares demos. - * + * * Prerequisites (invoked): clean, build */ const prepareDemos = gulp.series( diff --git a/scripts/gulpfiles/build_tasks.js b/scripts/gulpfiles/build_tasks.js index fa1d962f3ba..4358ad8e518 100644 --- a/scripts/gulpfiles/build_tasks.js +++ b/scripts/gulpfiles/build_tasks.js @@ -8,27 +8,27 @@ * @fileoverview Gulp script to build Blockly for Node & NPM. */ -var gulp = require('gulp'); +const gulp = require('gulp'); gulp.replace = require('gulp-replace'); gulp.rename = require('gulp-rename'); gulp.sourcemaps = require('gulp-sourcemaps'); -var path = require('path'); -var fs = require('fs'); +const path = require('path'); +const fs = require('fs'); const {exec, execSync} = require('child_process'); -var through2 = require('through2'); +const through2 = require('through2'); const clangFormat = require('clang-format'); const clangFormatter = require('gulp-clang-format'); -var closureCompiler = require('google-closure-compiler').gulp(); -var closureDeps = require('google-closure-deps'); -var argv = require('yargs').argv; -var rimraf = require('rimraf'); +const closureCompiler = require('google-closure-compiler').gulp(); +const closureDeps = require('google-closure-deps'); +const argv = require('yargs').argv; +const rimraf = require('rimraf'); -var {BUILD_DIR, DEPS_FILE, RELEASE_DIR, TEST_DEPS_FILE, TSC_OUTPUT_DIR, TYPINGS_BUILD_DIR} = require('./config'); -var {getPackageJson} = require('./helper_tasks'); +const {BUILD_DIR, DEPS_FILE, RELEASE_DIR, TEST_DEPS_FILE, TSC_OUTPUT_DIR, TYPINGS_BUILD_DIR} = require('./config'); +const {getPackageJson} = require('./helper_tasks'); -var {posixPath} = require('../helpers'); +const {posixPath} = require('../helpers'); //////////////////////////////////////////////////////////// // Build // @@ -173,9 +173,9 @@ function stripApacheLicense() { } /** - * Closure compiler diagnostic groups we want to be treated as errors. + * Closure Compiler diagnostic groups we want to be treated as errors. * These are effected when the --debug or --strict flags are passed. - * For a full list of closure compiler groups, consult the output of + * For a full list of Closure Compiler groups, consult the output of * google-closure-compiler --help or look in the source here: * https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/DiagnosticGroups.java#L117 * @@ -185,7 +185,7 @@ function stripApacheLicense() { * appearing on any list will default to setting provided by the * compiler, which may vary depending on compilation level. */ -var JSCOMP_ERROR = [ +const JSCOMP_ERROR = [ // 'accessControls', // Deprecated; means same as visibility. // 'checkPrototypalTypes', // override annotations are stripped by tsc. 'checkRegExp', @@ -235,25 +235,25 @@ var JSCOMP_ERROR = [ ]; /** - * Closure compiler diagnostic groups we want to be treated as warnings. + * Closure Compiler diagnostic groups we want to be treated as warnings. * These are effected when the --debug or --strict flags are passed. * * For most (all?) diagnostic groups this is the default level, so * it's generally sufficient to remove them from JSCOMP_ERROR. */ -var JSCOMP_WARNING = [ +const JSCOMP_WARNING = [ ]; /** - * Closure compiler diagnostic groups we want to be ignored. These + * Closure Compiler diagnostic groups we want to be ignored. These * suppressions are always effected by default. * * Make sure that anything added here is commented out of JSCOMP_ERROR * above, as that takes precedence.) */ -var JSCOMP_OFF = [ +const JSCOMP_OFF = [ /* The removal of Closure type system types from our JSDoc - * annotations means that the closure compiler now generates certain + * annotations means that the Closure Compiler now generates certain * diagnostics because it no longer has enough information to be * sure that the input code is correct. The following diagnostic * groups are turned off to suppress such errors. @@ -315,6 +315,7 @@ function buildJavaScript(done) { execSync( `tsc -outDir "${TSC_OUTPUT_DIR}" -declarationDir "${TYPINGS_BUILD_DIR}"`, {stdio: 'inherit'}); + execSync(`node scripts/tsick.js "${TSC_OUTPUT_DIR}"`, {stdio: 'inherit'}); done(); } @@ -452,7 +453,7 @@ function buildLangfiles(done) { } /** - * A helper method to return an closure compiler chunk wrapper that + * A helper method to return an Closure Compiler chunk wrapper that * wraps the compiler output for the given chunk in a Universal Module * Definition. */ @@ -612,7 +613,7 @@ function getChunkOptions() { const pathSepRegExp = new RegExp(path.sep.replace(/\\/, '\\\\'), "g"); /** - * Helper method for calling the Closure compiler, establishing + * Helper method for calling the Closure Compiler, establishing * default options (that can be overridden by the caller). * @param {*} options Caller-supplied options that will override the * defaultOptions. @@ -663,7 +664,7 @@ function buildCompiled() { const packageJson = getPackageJson(); // For version number. const options = { // The documentation for @define claims you can't use it on a - // non-global, but the closure compiler turns everything in to a + // non-global, but the Closure Compiler turns everything in to a // global - you just have to know what the new name is! With // declareLegacyNamespace this was very straightforward. Without // it, we have to rely on implmentation details. See @@ -688,11 +689,19 @@ function buildCompiled() { /** * This task builds Blockly core, blocks and generators together and uses - * closure compiler's ADVANCED_COMPILATION mode. + * Closure Compiler's ADVANCED_COMPILATION mode. * * Prerequisite: buildDeps. */ function buildAdvancedCompilationTest() { + // If main_compressed.js exists (from a previous run) delete it so that + // a later browser-based test won't check it should the compile fail. + try { + fs.unlinkSync('./tests/compile/main_compressed.js'); + } catch (_e) { + // Probably it didn't exist. + } + const srcs = [ TSC_OUTPUT_DIR + '/closure/goog/base_minimal.js', TSC_OUTPUT_DIR + '/closure/goog/goog.js', diff --git a/scripts/gulpfiles/config.js b/scripts/gulpfiles/config.js index 200d6ac45a4..5f3cad99d32 100644 --- a/scripts/gulpfiles/config.js +++ b/scripts/gulpfiles/config.js @@ -8,7 +8,7 @@ * @fileoverview Common configuration for Gulp scripts. */ -var path = require('path'); +const path = require('path'); // Paths are all relative to the repository root. Do not include // trailing slash. diff --git a/scripts/gulpfiles/git_tasks.js b/scripts/gulpfiles/git_tasks.js index 0f14133500a..eb3a825c025 100644 --- a/scripts/gulpfiles/git_tasks.js +++ b/scripts/gulpfiles/git_tasks.js @@ -8,10 +8,10 @@ * @fileoverview Git-related gulp tasks for Blockly. */ -var gulp = require('gulp'); -var execSync = require('child_process').execSync; +const gulp = require('gulp'); +const execSync = require('child_process').execSync; -var buildTasks = require('./build_tasks'); +const buildTasks = require('./build_tasks'); const packageTasks = require('./package_tasks'); const upstream_url = "https://github.com/google/blockly.git"; @@ -41,18 +41,18 @@ function syncMaster() { // Helper function: get a name for a rebuild branch. Format: rebuild_mm_dd_yyyy. function getRebuildBranchName() { - var date = new Date(); - var mm = date.getMonth() + 1; // Month, 0-11 - var dd = date.getDate(); // Day of the month, 1-31 - var yyyy = date.getFullYear(); + const date = new Date(); + const mm = date.getMonth() + 1; // Month, 0-11 + const dd = date.getDate(); // Day of the month, 1-31 + const yyyy = date.getFullYear(); return 'rebuild_' + mm + '_' + dd + '_' + yyyy; }; // Helper function: get a name for a rebuild branch. Format: rebuild_yyyy_mm. function getRCBranchName() { - var date = new Date(); - var mm = date.getMonth() + 1; // Month, 0-11 - var yyyy = date.getFullYear(); + const date = new Date(); + const mm = date.getMonth() + 1; // Month, 0-11 + const yyyy = date.getFullYear(); return 'rc_' + yyyy + '_' + mm; }; @@ -68,7 +68,7 @@ function checkoutBranch(branchName) { const createRC = gulp.series( syncDevelop(), function(done) { - var branchName = getRCBranchName(); + const branchName = getRCBranchName(); execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); execSync('git push ' + upstream_url + ' ' + branchName, { stdio: 'inherit' }); @@ -78,7 +78,7 @@ const createRC = gulp.series( // Create the rebuild branch. function createRebuildBranch(done) { - var branchName = getRebuildBranchName(); + const branchName = getRebuildBranchName(); console.log('make-rebuild-branch: creating branch ' + branchName); execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); done(); @@ -88,7 +88,7 @@ function createRebuildBranch(done) { function pushRebuildBranch(done) { console.log('push-rebuild-branch: committing rebuild'); execSync('git commit -am "Rebuild"', { stdio: 'inherit' }); - var branchName = getRebuildBranchName(); + const branchName = getRebuildBranchName(); execSync('git push origin ' + branchName, { stdio: 'inherit' }); console.log('Branch ' + branchName + ' pushed to GitHub.'); console.log('Next step: create a pull request against develop.'); diff --git a/scripts/gulpfiles/package_tasks.js b/scripts/gulpfiles/package_tasks.js index 2408641f7c5..d5fba6258de 100644 --- a/scripts/gulpfiles/package_tasks.js +++ b/scripts/gulpfiles/package_tasks.js @@ -8,7 +8,7 @@ * @fileoverview Gulp tasks to package Blockly for distribution on NPM. */ -var gulp = require('gulp'); +const gulp = require('gulp'); gulp.concat = require('gulp-concat'); gulp.replace = require('gulp-replace'); gulp.rename = require('gulp-rename'); @@ -16,12 +16,12 @@ gulp.insert = require('gulp-insert'); gulp.umd = require('gulp-umd'); gulp.replace = require('gulp-replace'); -var path = require('path'); -var fs = require('fs'); -var rimraf = require('rimraf'); -var build = require('./build_tasks'); -var {getPackageJson} = require('./helper_tasks'); -var {BUILD_DIR, RELEASE_DIR, TYPINGS_BUILD_DIR} = require('./config'); +const path = require('path'); +const fs = require('fs'); +const rimraf = require('rimraf'); +const build = require('./build_tasks'); +const {getPackageJson} = require('./helper_tasks'); +const {BUILD_DIR, RELEASE_DIR, TYPINGS_BUILD_DIR} = require('./config'); // Path to template files for gulp-umd. const TEMPLATE_DIR = 'scripts/package/templates'; @@ -290,7 +290,7 @@ function packageLocales() { * @example */ function packageUMDBundle() { - var srcs = [ + const srcs = [ `${RELEASE_DIR}/blockly_compressed.js`, `${RELEASE_DIR}/msg/en.js`, `${RELEASE_DIR}/blocks_compressed.js`, diff --git a/scripts/gulpfiles/release_tasks.js b/scripts/gulpfiles/release_tasks.js index 4a387705f9e..58717985c0c 100644 --- a/scripts/gulpfiles/release_tasks.js +++ b/scripts/gulpfiles/release_tasks.js @@ -8,22 +8,22 @@ * @fileoverview Gulp scripts for releasing Blockly. */ -var execSync = require('child_process').execSync; -var fs = require('fs'); -var gulp = require('gulp'); -var readlineSync = require('readline-sync'); +const execSync = require('child_process').execSync; +const fs = require('fs'); +const gulp = require('gulp'); +const readlineSync = require('readline-sync'); -var gitTasks = require('./git_tasks'); -var packageTasks = require('./package_tasks'); -var {getPackageJson} = require('./helper_tasks'); -var {RELEASE_DIR} = require('./config'); +const gitTasks = require('./git_tasks'); +const packageTasks = require('./package_tasks'); +const {getPackageJson} = require('./helper_tasks'); +const {RELEASE_DIR} = require('./config'); // Gets the current major version. function getMajorVersion() { - var { version } = getPackageJson(); - var re = new RegExp(/^(\d)./); - var match = re.exec(version); + const { version } = getPackageJson(); + const re = new RegExp(/^(\d)./); + const match = re.exec(version); if (!match[0]) { return null; } @@ -33,7 +33,7 @@ function getMajorVersion() { // Updates the version depending on user input. function updateVersion(done, updateType) { - var majorVersion = getMajorVersion(); + const majorVersion = getMajorVersion(); if (!majorVersion) { done(new Error('Something went wrong when getting the major version number.')); } else if (!updateType) { @@ -62,14 +62,14 @@ function updateVersion(done, updateType) { // Prompt the user to figure out what kind of version update we should do. function updateVersionPrompt(done) { - var releaseTypes = ['Major', 'Minor', 'Patch']; - var index = readlineSync.keyInSelect(releaseTypes, 'Which version type?'); + const releaseTypes = ['Major', 'Minor', 'Patch']; + const index = readlineSync.keyInSelect(releaseTypes, 'Which version type?'); updateVersion(done, releaseTypes[index]); } // Checks with the user that they are on the correct git branch. function checkBranch(done) { - var gitBranchName = execSync('git rev-parse --abbrev-ref HEAD').toString(); + const gitBranchName = execSync('git rev-parse --abbrev-ref HEAD').toString(); if (readlineSync.keyInYN(`You are on '${gitBranchName.trim()}'. Is this the correct branch?`)) { done(); } else { @@ -101,7 +101,7 @@ function checkReleaseDir(done) { // Check with the user that the version number is correct, then login and publish to npm. function loginAndPublish_(done, isBeta) { - var { version } = getPackageJson(); + const { version } = getPackageJson(); if(readlineSync.keyInYN(`You are about to publish blockly with the version number:${version}. Do you want to continue?`)) { execSync(`npm login --registry https://wombat-dressing-room.appspot.com`, {stdio: 'inherit'}); execSync(`npm publish --registry https://wombat-dressing-room.appspot.com ${isBeta ? '--tag beta' : ''}`, {cwd: RELEASE_DIR, stdio: 'inherit'}); @@ -124,15 +124,15 @@ function loginAndPublishBeta(done) { // Repeatedly prompts the user for a beta version number until a valid one is given. // A valid version number must have '-beta.x' and can not have already been used to publish to npm. function updateBetaVersion(done) { - var isValid = false; - var newVersion = null; - var blocklyVersions = JSON.parse(execSync('npm view blockly versions --json').toString()); - var re = new RegExp(/-beta\.(\d)/); - var latestBetaVersion = execSync('npm show blockly version --tag beta').toString().trim(); + let isValid = false; + let newVersion = null; + const blocklyVersions = JSON.parse(execSync('npm view blockly versions --json').toString()); + const re = new RegExp(/-beta\.(\d)/); + const latestBetaVersion = execSync('npm show blockly version --tag beta').toString().trim(); while(!isValid) { newVersion = readlineSync.question(`What is the new beta version? (latest beta version: ${latestBetaVersion})`); - var existsOnNpm = blocklyVersions.indexOf(newVersion) > -1; - var isFormatted = newVersion.search(re) > -1; + const existsOnNpm = blocklyVersions.indexOf(newVersion) > -1; + const isFormatted = newVersion.search(re) > -1; if (!existsOnNpm && isFormatted) { isValid = true; } else if (existsOnNpm) { diff --git a/scripts/gulpfiles/test_tasks.js b/scripts/gulpfiles/test_tasks.js index 0712278b3a6..68fb6396375 100644 --- a/scripts/gulpfiles/test_tasks.js +++ b/scripts/gulpfiles/test_tasks.js @@ -20,11 +20,9 @@ const rimraf = require('rimraf'); const buildTasks = require('./build_tasks'); const {BUILD_DIR, RELEASE_DIR} = require('./config'); -const runMochaTestsInBrowser = - require('../../tests/mocha/run_mocha_tests_in_browser.js'); - -const runGeneratorsInBrowser = - require('../../tests/generators/run_generators_in_browser.js'); +const {runMochaTestsInBrowser} = require('../../tests/mocha/webdriver.js'); +const {runGeneratorsInBrowser} = require('../../tests/generators/webdriver.js'); +const {runCompileCheckInBrowser} = require('../../tests/compile/webdriver.js'); const OUTPUT_DIR = 'build/generators'; const GOLDEN_DIR = 'tests/generators/golden'; @@ -39,7 +37,7 @@ class Tester { this.failCount = 0; this.tasks = tasks; } - + /** * Run all tests in sequence. */ @@ -56,11 +54,11 @@ class Tester { asTask() { return this.runAll.bind(this); } - + /** * Run an arbitrary Gulp task as a test. - * @param {function} task Any gulp task - * @return {Promise} asynchronous result + * @param {function} task Any Gulp task. + * @return {Promise} Asynchronous result. */ async runTestTask(task) { const id = task.name; @@ -106,8 +104,8 @@ class Tester { /** * Helper method for running test command. - * @param {string} command command line to run - * @return {Promise} asynchronous result + * @param {string} command Command line to run. + * @return {Promise} Asynchronous result. */ async function runTestCommand(command) { execSync(command, {stdio: 'inherit'}); @@ -116,7 +114,7 @@ async function runTestCommand(command) { /** * Lint the codebase. * Skip for CI environments, because linting is run separately. - * @return {Promise} asynchronous result + * @return {Promise} Asynchronous result. */ function eslint() { if (process.env.CI) { @@ -128,8 +126,8 @@ function eslint() { /** * Run the full usual build and package process, checking to ensure - * there are no closure compiler warnings / errors. - * @return {Promise} asynchronous result + * there are no Closure Compiler warnings / errors. + * @return {Promise} Asynchronous result. */ function build() { return runTestCommand('npm run package -- --verbose --debug'); @@ -137,7 +135,7 @@ function build() { /** * Run renaming validation test. - * @return {Promise} asynchronous result + * @return {Promise} Asynchronous result. */ function renamings() { return runTestCommand('node tests/migration/validate-renamings.js'); @@ -145,16 +143,16 @@ function renamings() { /** * Helper method for gzipping file. - * @param {string} file target file - * @return {Promise} asynchronous result + * @param {string} file Target file. + * @return {Promise} Asynchronous result. */ function gzipFile(file) { return new Promise((resolve) => { const name = path.posix.join(RELEASE_DIR, file); const stream = gulp.src(name) - .pipe(gzip()) - .pipe(gulp.dest(RELEASE_DIR)); + .pipe(gzip()) + .pipe(gulp.dest(RELEASE_DIR)); stream.on('end', () => { resolve(); @@ -164,9 +162,9 @@ function gzipFile(file) { /** * Helper method for comparing file size. - * @param {string} file target file - * @param {number} expected expected size - * @return {number} 0: success / 1: failed + * @param {string} file Target file. + * @param {number} expected Expected size. + * @return {number} 0: success / 1: failed. */ function compareSize(file, expected) { const name = path.posix.join(RELEASE_DIR, file); @@ -176,12 +174,12 @@ function compareSize(file, expected) { if (size > compare) { const message = `Failed: ` + - `Size of ${name} has grown more than 10%. ${size} vs ${expected} `; + `Size of ${name} has grown more than 10%. ${size} vs ${expected}`; console.log(`${BOLD_RED}${message}${ANSI_RESET}`); return 1; } else { const message = - `Size of ${name} at ${size} compared to previous ${expected}`; + `Size of ${name} at ${size} compared to previous ${expected}`; console.log(`${BOLD_GREEN}${message}${ANSI_RESET}`); return 0; } @@ -189,7 +187,7 @@ function compareSize(file, expected) { /** * Helper method for zipping the compressed files. - * @return {Promise} asynchronous result + * @return {Promise} Asynchronous result. */ function zippingFiles() { // GZip them for additional size comparisons (keep originals, force @@ -202,7 +200,7 @@ function zippingFiles() { /** * Check the sizes of built files for unexpected growth. - * @return {Promise} asynchronous result + * @return {Promise} Asynchronous result. */ async function metadata() { // Zipping the compressed files. @@ -234,10 +232,10 @@ async function metadata() { /** * Run Mocha tests inside a browser. - * @return {Promise} asynchronous result + * @return {Promise} Asynchronous result. */ async function mocha() { - const result = await runMochaTestsInBrowser().catch(e => { + const result = await runMochaTestsInBrowser().catch(e => { throw e; }); if (result) { @@ -248,9 +246,9 @@ async function mocha() { /** * Helper method for comparison file. - * @param {string} file1 first target file - * @param {string} file2 second target file - * @return {boolean} comparison result (true: same / false: different) + * @param {string} file1 First target file. + * @param {string} file2 Second target file. + * @return {boolean} Comparison result (true: same / false: different). */ function compareFile(file1, file2) { const buf1 = fs.readFileSync(file1); @@ -258,14 +256,13 @@ function compareFile(file1, file2) { // Normalize the line feed. const code1 = buf1.toString().replace(/(?:\r\n|\r|\n)/g, '\n'); const code2 = buf2.toString().replace(/(?:\r\n|\r|\n)/g, '\n'); - const result = (code1 === code2); - return result; + return code1 === code2; } /** * Helper method for checking the result of generator. - * @param {string} suffix target suffix - * @return {number} check result (0: success / 1: failed) + * @param {string} suffix Target suffix. + * @return {number} Check result (0: success / 1: failed). */ function checkResult(suffix) { const fileName = `generated.${suffix}`; @@ -279,12 +276,12 @@ function checkResult(suffix) { if (fs.existsSync(goldenFileName)) { if (compareFile(resultFileName, goldenFileName)) { console.log(`${SUCCESS_PREFIX} ${suffix}: ` + - `${resultFileName} matches ${goldenFileName}`); + `${resultFileName} matches ${goldenFileName}`); return 0; } else { console.log( - `${FAILURE_PREFIX} ${suffix}: ` + - `${resultFileName} does not match ${goldenFileName}`); + `${FAILURE_PREFIX} ${suffix}: ` + + `${resultFileName} does not match ${goldenFileName}`); } } else { console.log(`File ${goldenFileName} not found!`); @@ -297,7 +294,7 @@ function checkResult(suffix) { /** * Run generator tests inside a browser and check the results. - * @return {Promise} asynchronous result + * @return {Promise} Asynchronous result. */ async function generators() { // Clean up. @@ -311,7 +308,7 @@ async function generators() { generatorSuffixes.forEach((suffix) => { failed += checkResult(suffix); }); - + if (failed === 0) { console.log(`${BOLD_GREEN}All generator tests passed.${ANSI_RESET}`); } else { @@ -323,12 +320,20 @@ async function generators() { /** * Run Node tests. - * @return {Promise} asynchronous result + * @return {Promise} Asynchronous result. */ function node() { return runTestCommand('mocha tests/node --config tests/node/.mocharc.js'); } +/** + * Attempt advanced compilation of a Blockly app. + * @return {Promise} Asynchronous result. + */ +function advancedCompile() { + const compilePromise = runTestCommand('npm run test:compile:advanced'); + return compilePromise.then(runCompileCheckInBrowser); +} // Run all tests in sequence. const test = new Tester([ @@ -339,7 +344,7 @@ const test = new Tester([ mocha, generators, node, - buildTasks.onlyBuildAdvancedCompilationTest, + advancedCompile, ]).asTask(); diff --git a/scripts/tsick.js b/scripts/tsick.js new file mode 100644 index 00000000000..cfe5eaf72ba --- /dev/null +++ b/scripts/tsick.js @@ -0,0 +1,83 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Lightweight conversion from tsc ouptut to Closure Compiler + * compatible input. + */ +'use strict'; + +// eslint-disable-next-line no-undef +const fs = require('fs'); + +// eslint-disable-next-line no-undef +const DIR = process.argv[2]; + +// Number of files rewritten. +let fileCount = 0; + +/** + * Recursively spider the given directory and rewrite any JS files found. + * @param {string} dir Directory path to start from. + */ +function spider(dir) { + if (!dir.endsWith('/')) { + dir += '/'; + } + const entries = fs.readdirSync(dir, {withFileTypes: true}); + for (const entry of entries) { + if (entry.isDirectory()) { + spider(dir + entry.name + '/'); + } else if (entry.name.endsWith('.js')) { + rewriteFile(dir + entry.name); + } + } +} + +/** + * Given a file, rewrite it if it contains problematic patterns. + * @param {string} path Path of file to potentially rewrite. + */ +function rewriteFile(path) { + const oldCode = fs.readFileSync(path, 'utf8'); + const newCode = rewriteEnum(oldCode); + if (newCode !== oldCode) { + fileCount++; + fs.writeFileSync(path, newCode); + } +} + +/** + * Unquote enum definitions in the given code string. + * @param {string} code Original code generated by tsc. + * @return {string} Rewritten code for Closure Compiler. + */ +function rewriteEnum(code) { + // Find all enum definitions. They look like this: + // (function (names) { + // ... + // })(names || (names = {})); + const enumDefs = code.match(/\s+\(function \((\w+)\) \{\n[^}]*\}\)\(\1 [^)]+\1 = \{\}\)\);/g) || []; + for (const oldEnumDef of enumDefs) { + // enumDef looks like a bunch of lines in one of these two formats: + // ScopeType["BLOCK"] = "block"; + // KeyCodes[KeyCodes["TAB"] = 9] = "TAB"; + // We need to unquote them to look like one of these two formats: + // ScopeType.BLOCK = "block"; + // KeyCodes[KeyCodes.TAB = 9] = "TAB"; + let newEnumDef = oldEnumDef.replace(/\["(\w+)"\]/g, '.$1'); + newEnumDef = newEnumDef.replace(') {', ') { // Converted by tsick.'); + code = code.replace(oldEnumDef, newEnumDef); + } + return code; +} + +if (DIR) { + spider(DIR); + console.log(`Unquoted enums in ${fileCount} files.`); +} else { + throw Error('No source directory specified'); +} diff --git a/tests/compile/main.js b/tests/compile/main.js index 9e4aad1d7ec..c34f1bf1cb9 100644 --- a/tests/compile/main.js +++ b/tests/compile/main.js @@ -12,10 +12,13 @@ goog.module('Main'); // TODO: I think we need to make sure these get exported? // const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions'); const {inject} = goog.require('Blockly.inject'); +const {getMainWorkspace} = goog.require('Blockly.common'); +const {Msg} = goog.require('Blockly.Msg'); /** @suppress {extraRequire} */ goog.require('Blockly.geras.Renderer'); /** @suppress {extraRequire} */ goog.require('Blockly.VerticalFlyout'); + // Blocks /** @suppress {extraRequire} */ goog.require('Blockly.libraryBlocks.logic'); @@ -30,8 +33,25 @@ goog.require('testBlocks'); function init() { + Object.assign(Msg, window['Blockly']['Msg']); inject('blocklyDiv', /** @type {BlocklyOptions} */ ({ 'toolbox': document.getElementById('toolbox') })); -}; +} window.addEventListener('load', init); + + +// Called externally from our test driver to see if Blockly loaded more or less +// correctly. This is not a comprehensive test, but it will catch catastrophic +// fails (by far the most common cases). +window['healthCheck'] = function() { + // Just check that we have a reasonable number of blocks in the flyout. + // Expecting 8 blocks, but leave a wide margin. + try { + const blockCount = + getMainWorkspace().getFlyout().getWorkspace().getTopBlocks().length; + return (blockCount > 5 && blockCount < 100); + } catch (_e) { + return false; + } +}; diff --git a/tests/compile/webdriver.js b/tests/compile/webdriver.js new file mode 100644 index 00000000000..50084c73b89 --- /dev/null +++ b/tests/compile/webdriver.js @@ -0,0 +1,82 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview Node.js script to check the health of the compile test in + * Chrome, via webdriver. + */ +const webdriverio = require('webdriverio'); + + +/** + * Run the generator for a given language and save the results to a file. + * @param {Thenable} browser A Thenable managing the processing of the browser + * tests. + */ +async function runHealthCheckInBrowser(browser) { + const result = await browser.execute(() => { + return healthCheck(); + }) + if (!result) throw Error('Health check failed in advanced compilation test.'); + console.log('Health check completed successfully.'); +} + +/** + * Runs the generator tests in Chrome. It uses webdriverio to + * launch Chrome and load index.html. Outputs a summary of the test results + * to the console and outputs files for later validation. + * @return the Thenable managing the processing of the browser tests. + */ +async function runCompileCheckInBrowser() { + const options = { + capabilities: { + browserName: 'chrome', + }, + logLevel: 'warn', + services: ['selenium-standalone'] + }; + // Run in headless mode on Github Actions. + if (process.env.CI) { + options.capabilities['goog:chromeOptions'] = { + args: ['--headless', '--no-sandbox', '--disable-dev-shm-usage'] + }; + } else { + // --disable-gpu is needed to prevent Chrome from hanging on Linux with + // NVIDIA drivers older than v295.20. See + // https://github.com/google/blockly/issues/5345 for details. + options.capabilities['goog:chromeOptions'] = { + args: ['--disable-gpu'] + }; + } + + const url = 'file://' + __dirname + '/index.html'; + + console.log('Starting webdriverio...'); + const browser = await webdriverio.remote(options); + console.log('Loading url: ' + url); + await browser.url(url); + + await runHealthCheckInBrowser(browser); + + await browser.deleteSession(); +} + +if (require.main === module) { + runCompileCheckInBrowser().catch(e => { + console.error(e); + process.exit(1); + }).then(function(result) { + if (result) { + console.log('Compile test failed'); + process.exit(1); + } else { + console.log('Compile test passed'); + process.exit(0); + } + }); +} + +module.exports = {runCompileCheckInBrowser}; diff --git a/tests/generators/index.html b/tests/generators/index.html index 3b108867775..c93e966d99e 100644 --- a/tests/generators/index.html +++ b/tests/generators/index.html @@ -19,7 +19,6 @@ -