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.
This commit is contained in:
Neil Fraser
2022-11-25 20:45:00 +01:00
committed by GitHub
parent be4b8323c5
commit 5a64a9a7f7
14 changed files with 349 additions and 154 deletions

View File

@@ -7,7 +7,7 @@
/tests/compile/* /tests/compile/*
/tests/jsunit/* /tests/jsunit/*
/tests/generators/* /tests/generators/*
/tests/mocha/run_mocha_tests_in_browser.js /tests/mocha/webdriver.js
/tests/screenshot/* /tests/screenshot/*
/tests/test_runner.js /tests/test_runner.js
/tests/workspace_svg/* /tests/workspace_svg/*

View File

@@ -8,16 +8,16 @@
* @fileoverview Gulp script to deploy Blockly demos on appengine. * @fileoverview Gulp script to deploy Blockly demos on appengine.
*/ */
var gulp = require('gulp'); const gulp = require('gulp');
var fs = require('fs'); const fs = require('fs');
var rimraf = require('rimraf'); const rimraf = require('rimraf');
var path = require('path'); const path = require('path');
var execSync = require('child_process').execSync; const execSync = require('child_process').execSync;
const buildTasks = require('./build_tasks.js'); const buildTasks = require('./build_tasks.js');
const packageTasks = require('./package_tasks.js'); const packageTasks = require('./package_tasks.js');
var packageJson = require('../../package.json'); const packageJson = require('../../package.json');
const demoTmpDir = '../_deploy'; const demoTmpDir = '../_deploy';
const demoStaticTmpDir = '../_deploy/static'; const demoStaticTmpDir = '../_deploy/static';
@@ -121,10 +121,10 @@ function deployAndClean(done) {
* Constructs a beta demo version name based on the current date. * Constructs a beta demo version name based on the current date.
*/ */
function getDemosBetaVersion() { function getDemosBetaVersion() {
var date = new Date(); const date = new Date();
var mm = date.getMonth() + 1; // Month, 0-11 const mm = date.getMonth() + 1; // Month, 0-11
var dd = date.getDate(); // Day of the month, 1-31 const dd = date.getDate(); // Day of the month, 1-31
var yyyy = date.getFullYear(); const yyyy = date.getFullYear();
return `${yyyy}${mm < 10 ? '0' + mm : mm}${dd}-beta`; return `${yyyy}${mm < 10 ? '0' + mm : mm}${dd}-beta`;
} }
@@ -140,7 +140,7 @@ function deployBetaAndClean(done) {
/** /**
* Prepares demos. * Prepares demos.
* *
* Prerequisites (invoked): clean, build * Prerequisites (invoked): clean, build
*/ */
const prepareDemos = gulp.series( const prepareDemos = gulp.series(

View File

@@ -8,27 +8,27 @@
* @fileoverview Gulp script to build Blockly for Node & NPM. * @fileoverview Gulp script to build Blockly for Node & NPM.
*/ */
var gulp = require('gulp'); const gulp = require('gulp');
gulp.replace = require('gulp-replace'); gulp.replace = require('gulp-replace');
gulp.rename = require('gulp-rename'); gulp.rename = require('gulp-rename');
gulp.sourcemaps = require('gulp-sourcemaps'); gulp.sourcemaps = require('gulp-sourcemaps');
var path = require('path'); const path = require('path');
var fs = require('fs'); const fs = require('fs');
const {exec, execSync} = require('child_process'); const {exec, execSync} = require('child_process');
var through2 = require('through2'); const through2 = require('through2');
const clangFormat = require('clang-format'); const clangFormat = require('clang-format');
const clangFormatter = require('gulp-clang-format'); const clangFormatter = require('gulp-clang-format');
var closureCompiler = require('google-closure-compiler').gulp(); const closureCompiler = require('google-closure-compiler').gulp();
var closureDeps = require('google-closure-deps'); const closureDeps = require('google-closure-deps');
var argv = require('yargs').argv; const argv = require('yargs').argv;
var rimraf = require('rimraf'); const rimraf = require('rimraf');
var {BUILD_DIR, DEPS_FILE, RELEASE_DIR, TEST_DEPS_FILE, TSC_OUTPUT_DIR, TYPINGS_BUILD_DIR} = require('./config'); const {BUILD_DIR, DEPS_FILE, RELEASE_DIR, TEST_DEPS_FILE, TSC_OUTPUT_DIR, TYPINGS_BUILD_DIR} = require('./config');
var {getPackageJson} = require('./helper_tasks'); const {getPackageJson} = require('./helper_tasks');
var {posixPath} = require('../helpers'); const {posixPath} = require('../helpers');
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Build // // 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. * 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: * 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 * 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 * appearing on any list will default to setting provided by the
* compiler, which may vary depending on compilation level. * compiler, which may vary depending on compilation level.
*/ */
var JSCOMP_ERROR = [ const JSCOMP_ERROR = [
// 'accessControls', // Deprecated; means same as visibility. // 'accessControls', // Deprecated; means same as visibility.
// 'checkPrototypalTypes', // override annotations are stripped by tsc. // 'checkPrototypalTypes', // override annotations are stripped by tsc.
'checkRegExp', '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. * These are effected when the --debug or --strict flags are passed.
* *
* For most (all?) diagnostic groups this is the default level, so * For most (all?) diagnostic groups this is the default level, so
* it's generally sufficient to remove them from JSCOMP_ERROR. * 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. * suppressions are always effected by default.
* *
* Make sure that anything added here is commented out of JSCOMP_ERROR * Make sure that anything added here is commented out of JSCOMP_ERROR
* above, as that takes precedence.) * above, as that takes precedence.)
*/ */
var JSCOMP_OFF = [ const JSCOMP_OFF = [
/* The removal of Closure type system types from our JSDoc /* 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 * diagnostics because it no longer has enough information to be
* sure that the input code is correct. The following diagnostic * sure that the input code is correct. The following diagnostic
* groups are turned off to suppress such errors. * groups are turned off to suppress such errors.
@@ -315,6 +315,7 @@ function buildJavaScript(done) {
execSync( execSync(
`tsc -outDir "${TSC_OUTPUT_DIR}" -declarationDir "${TYPINGS_BUILD_DIR}"`, `tsc -outDir "${TSC_OUTPUT_DIR}" -declarationDir "${TYPINGS_BUILD_DIR}"`,
{stdio: 'inherit'}); {stdio: 'inherit'});
execSync(`node scripts/tsick.js "${TSC_OUTPUT_DIR}"`, {stdio: 'inherit'});
done(); 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 * wraps the compiler output for the given chunk in a Universal Module
* Definition. * Definition.
*/ */
@@ -612,7 +613,7 @@ function getChunkOptions() {
const pathSepRegExp = new RegExp(path.sep.replace(/\\/, '\\\\'), "g"); 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). * default options (that can be overridden by the caller).
* @param {*} options Caller-supplied options that will override the * @param {*} options Caller-supplied options that will override the
* defaultOptions. * defaultOptions.
@@ -663,7 +664,7 @@ function buildCompiled() {
const packageJson = getPackageJson(); // For version number. const packageJson = getPackageJson(); // For version number.
const options = { const options = {
// The documentation for @define claims you can't use it on a // 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 // global - you just have to know what the new name is! With
// declareLegacyNamespace this was very straightforward. Without // declareLegacyNamespace this was very straightforward. Without
// it, we have to rely on implmentation details. See // 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 * 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. * Prerequisite: buildDeps.
*/ */
function buildAdvancedCompilationTest() { 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 = [ const srcs = [
TSC_OUTPUT_DIR + '/closure/goog/base_minimal.js', TSC_OUTPUT_DIR + '/closure/goog/base_minimal.js',
TSC_OUTPUT_DIR + '/closure/goog/goog.js', TSC_OUTPUT_DIR + '/closure/goog/goog.js',

View File

@@ -8,7 +8,7 @@
* @fileoverview Common configuration for Gulp scripts. * @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 // Paths are all relative to the repository root. Do not include
// trailing slash. // trailing slash.

View File

@@ -8,10 +8,10 @@
* @fileoverview Git-related gulp tasks for Blockly. * @fileoverview Git-related gulp tasks for Blockly.
*/ */
var gulp = require('gulp'); const gulp = require('gulp');
var execSync = require('child_process').execSync; const execSync = require('child_process').execSync;
var buildTasks = require('./build_tasks'); const buildTasks = require('./build_tasks');
const packageTasks = require('./package_tasks'); const packageTasks = require('./package_tasks');
const upstream_url = "https://github.com/google/blockly.git"; 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. // Helper function: get a name for a rebuild branch. Format: rebuild_mm_dd_yyyy.
function getRebuildBranchName() { function getRebuildBranchName() {
var date = new Date(); const date = new Date();
var mm = date.getMonth() + 1; // Month, 0-11 const mm = date.getMonth() + 1; // Month, 0-11
var dd = date.getDate(); // Day of the month, 1-31 const dd = date.getDate(); // Day of the month, 1-31
var yyyy = date.getFullYear(); const yyyy = date.getFullYear();
return 'rebuild_' + mm + '_' + dd + '_' + yyyy; return 'rebuild_' + mm + '_' + dd + '_' + yyyy;
}; };
// Helper function: get a name for a rebuild branch. Format: rebuild_yyyy_mm. // Helper function: get a name for a rebuild branch. Format: rebuild_yyyy_mm.
function getRCBranchName() { function getRCBranchName() {
var date = new Date(); const date = new Date();
var mm = date.getMonth() + 1; // Month, 0-11 const mm = date.getMonth() + 1; // Month, 0-11
var yyyy = date.getFullYear(); const yyyy = date.getFullYear();
return 'rc_' + yyyy + '_' + mm; return 'rc_' + yyyy + '_' + mm;
}; };
@@ -68,7 +68,7 @@ function checkoutBranch(branchName) {
const createRC = gulp.series( const createRC = gulp.series(
syncDevelop(), syncDevelop(),
function(done) { function(done) {
var branchName = getRCBranchName(); const branchName = getRCBranchName();
execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); execSync('git checkout -b ' + branchName, { stdio: 'inherit' });
execSync('git push ' + upstream_url + ' ' + branchName, execSync('git push ' + upstream_url + ' ' + branchName,
{ stdio: 'inherit' }); { stdio: 'inherit' });
@@ -78,7 +78,7 @@ const createRC = gulp.series(
// Create the rebuild branch. // Create the rebuild branch.
function createRebuildBranch(done) { function createRebuildBranch(done) {
var branchName = getRebuildBranchName(); const branchName = getRebuildBranchName();
console.log('make-rebuild-branch: creating branch ' + branchName); console.log('make-rebuild-branch: creating branch ' + branchName);
execSync('git checkout -b ' + branchName, { stdio: 'inherit' }); execSync('git checkout -b ' + branchName, { stdio: 'inherit' });
done(); done();
@@ -88,7 +88,7 @@ function createRebuildBranch(done) {
function pushRebuildBranch(done) { function pushRebuildBranch(done) {
console.log('push-rebuild-branch: committing rebuild'); console.log('push-rebuild-branch: committing rebuild');
execSync('git commit -am "Rebuild"', { stdio: 'inherit' }); execSync('git commit -am "Rebuild"', { stdio: 'inherit' });
var branchName = getRebuildBranchName(); const branchName = getRebuildBranchName();
execSync('git push origin ' + branchName, { stdio: 'inherit' }); execSync('git push origin ' + branchName, { stdio: 'inherit' });
console.log('Branch ' + branchName + ' pushed to GitHub.'); console.log('Branch ' + branchName + ' pushed to GitHub.');
console.log('Next step: create a pull request against develop.'); console.log('Next step: create a pull request against develop.');

View File

@@ -8,7 +8,7 @@
* @fileoverview Gulp tasks to package Blockly for distribution on NPM. * @fileoverview Gulp tasks to package Blockly for distribution on NPM.
*/ */
var gulp = require('gulp'); const gulp = require('gulp');
gulp.concat = require('gulp-concat'); gulp.concat = require('gulp-concat');
gulp.replace = require('gulp-replace'); gulp.replace = require('gulp-replace');
gulp.rename = require('gulp-rename'); gulp.rename = require('gulp-rename');
@@ -16,12 +16,12 @@ gulp.insert = require('gulp-insert');
gulp.umd = require('gulp-umd'); gulp.umd = require('gulp-umd');
gulp.replace = require('gulp-replace'); gulp.replace = require('gulp-replace');
var path = require('path'); const path = require('path');
var fs = require('fs'); const fs = require('fs');
var rimraf = require('rimraf'); const rimraf = require('rimraf');
var build = require('./build_tasks'); const build = require('./build_tasks');
var {getPackageJson} = require('./helper_tasks'); const {getPackageJson} = require('./helper_tasks');
var {BUILD_DIR, RELEASE_DIR, TYPINGS_BUILD_DIR} = require('./config'); const {BUILD_DIR, RELEASE_DIR, TYPINGS_BUILD_DIR} = require('./config');
// Path to template files for gulp-umd. // Path to template files for gulp-umd.
const TEMPLATE_DIR = 'scripts/package/templates'; const TEMPLATE_DIR = 'scripts/package/templates';
@@ -290,7 +290,7 @@ function packageLocales() {
* @example <script src="https://unpkg.com/blockly/blockly.min.js"></script> * @example <script src="https://unpkg.com/blockly/blockly.min.js"></script>
*/ */
function packageUMDBundle() { function packageUMDBundle() {
var srcs = [ const srcs = [
`${RELEASE_DIR}/blockly_compressed.js`, `${RELEASE_DIR}/blockly_compressed.js`,
`${RELEASE_DIR}/msg/en.js`, `${RELEASE_DIR}/msg/en.js`,
`${RELEASE_DIR}/blocks_compressed.js`, `${RELEASE_DIR}/blocks_compressed.js`,

View File

@@ -8,22 +8,22 @@
* @fileoverview Gulp scripts for releasing Blockly. * @fileoverview Gulp scripts for releasing Blockly.
*/ */
var execSync = require('child_process').execSync; const execSync = require('child_process').execSync;
var fs = require('fs'); const fs = require('fs');
var gulp = require('gulp'); const gulp = require('gulp');
var readlineSync = require('readline-sync'); const readlineSync = require('readline-sync');
var gitTasks = require('./git_tasks'); const gitTasks = require('./git_tasks');
var packageTasks = require('./package_tasks'); const packageTasks = require('./package_tasks');
var {getPackageJson} = require('./helper_tasks'); const {getPackageJson} = require('./helper_tasks');
var {RELEASE_DIR} = require('./config'); const {RELEASE_DIR} = require('./config');
// Gets the current major version. // Gets the current major version.
function getMajorVersion() { function getMajorVersion() {
var { version } = getPackageJson(); const { version } = getPackageJson();
var re = new RegExp(/^(\d)./); const re = new RegExp(/^(\d)./);
var match = re.exec(version); const match = re.exec(version);
if (!match[0]) { if (!match[0]) {
return null; return null;
} }
@@ -33,7 +33,7 @@ function getMajorVersion() {
// Updates the version depending on user input. // Updates the version depending on user input.
function updateVersion(done, updateType) { function updateVersion(done, updateType) {
var majorVersion = getMajorVersion(); const majorVersion = getMajorVersion();
if (!majorVersion) { if (!majorVersion) {
done(new Error('Something went wrong when getting the major version number.')); done(new Error('Something went wrong when getting the major version number.'));
} else if (!updateType) { } 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. // Prompt the user to figure out what kind of version update we should do.
function updateVersionPrompt(done) { function updateVersionPrompt(done) {
var releaseTypes = ['Major', 'Minor', 'Patch']; const releaseTypes = ['Major', 'Minor', 'Patch'];
var index = readlineSync.keyInSelect(releaseTypes, 'Which version type?'); const index = readlineSync.keyInSelect(releaseTypes, 'Which version type?');
updateVersion(done, releaseTypes[index]); updateVersion(done, releaseTypes[index]);
} }
// Checks with the user that they are on the correct git branch. // Checks with the user that they are on the correct git branch.
function checkBranch(done) { 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?`)) { if (readlineSync.keyInYN(`You are on '${gitBranchName.trim()}'. Is this the correct branch?`)) {
done(); done();
} else { } else {
@@ -101,7 +101,7 @@ function checkReleaseDir(done) {
// Check with the user that the version number is correct, then login and publish to npm. // Check with the user that the version number is correct, then login and publish to npm.
function loginAndPublish_(done, isBeta) { 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?`)) { 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 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'}); 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. // 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. // A valid version number must have '-beta.x' and can not have already been used to publish to npm.
function updateBetaVersion(done) { function updateBetaVersion(done) {
var isValid = false; let isValid = false;
var newVersion = null; let newVersion = null;
var blocklyVersions = JSON.parse(execSync('npm view blockly versions --json').toString()); const blocklyVersions = JSON.parse(execSync('npm view blockly versions --json').toString());
var re = new RegExp(/-beta\.(\d)/); const re = new RegExp(/-beta\.(\d)/);
var latestBetaVersion = execSync('npm show blockly version --tag beta').toString().trim(); const latestBetaVersion = execSync('npm show blockly version --tag beta').toString().trim();
while(!isValid) { while(!isValid) {
newVersion = readlineSync.question(`What is the new beta version? (latest beta version: ${latestBetaVersion})`); newVersion = readlineSync.question(`What is the new beta version? (latest beta version: ${latestBetaVersion})`);
var existsOnNpm = blocklyVersions.indexOf(newVersion) > -1; const existsOnNpm = blocklyVersions.indexOf(newVersion) > -1;
var isFormatted = newVersion.search(re) > -1; const isFormatted = newVersion.search(re) > -1;
if (!existsOnNpm && isFormatted) { if (!existsOnNpm && isFormatted) {
isValid = true; isValid = true;
} else if (existsOnNpm) { } else if (existsOnNpm) {

View File

@@ -20,11 +20,9 @@ const rimraf = require('rimraf');
const buildTasks = require('./build_tasks'); const buildTasks = require('./build_tasks');
const {BUILD_DIR, RELEASE_DIR} = require('./config'); const {BUILD_DIR, RELEASE_DIR} = require('./config');
const runMochaTestsInBrowser = const {runMochaTestsInBrowser} = require('../../tests/mocha/webdriver.js');
require('../../tests/mocha/run_mocha_tests_in_browser.js'); const {runGeneratorsInBrowser} = require('../../tests/generators/webdriver.js');
const {runCompileCheckInBrowser} = require('../../tests/compile/webdriver.js');
const runGeneratorsInBrowser =
require('../../tests/generators/run_generators_in_browser.js');
const OUTPUT_DIR = 'build/generators'; const OUTPUT_DIR = 'build/generators';
const GOLDEN_DIR = 'tests/generators/golden'; const GOLDEN_DIR = 'tests/generators/golden';
@@ -39,7 +37,7 @@ class Tester {
this.failCount = 0; this.failCount = 0;
this.tasks = tasks; this.tasks = tasks;
} }
/** /**
* Run all tests in sequence. * Run all tests in sequence.
*/ */
@@ -56,11 +54,11 @@ class Tester {
asTask() { asTask() {
return this.runAll.bind(this); return this.runAll.bind(this);
} }
/** /**
* Run an arbitrary Gulp task as a test. * Run an arbitrary Gulp task as a test.
* @param {function} task Any gulp task * @param {function} task Any Gulp task.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
async runTestTask(task) { async runTestTask(task) {
const id = task.name; const id = task.name;
@@ -106,8 +104,8 @@ class Tester {
/** /**
* Helper method for running test command. * Helper method for running test command.
* @param {string} command command line to run * @param {string} command Command line to run.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
async function runTestCommand(command) { async function runTestCommand(command) {
execSync(command, {stdio: 'inherit'}); execSync(command, {stdio: 'inherit'});
@@ -116,7 +114,7 @@ async function runTestCommand(command) {
/** /**
* Lint the codebase. * Lint the codebase.
* Skip for CI environments, because linting is run separately. * Skip for CI environments, because linting is run separately.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
function eslint() { function eslint() {
if (process.env.CI) { if (process.env.CI) {
@@ -128,8 +126,8 @@ function eslint() {
/** /**
* Run the full usual build and package process, checking to ensure * Run the full usual build and package process, checking to ensure
* there are no closure compiler warnings / errors. * there are no Closure Compiler warnings / errors.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
function build() { function build() {
return runTestCommand('npm run package -- --verbose --debug'); return runTestCommand('npm run package -- --verbose --debug');
@@ -137,7 +135,7 @@ function build() {
/** /**
* Run renaming validation test. * Run renaming validation test.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
function renamings() { function renamings() {
return runTestCommand('node tests/migration/validate-renamings.js'); return runTestCommand('node tests/migration/validate-renamings.js');
@@ -145,16 +143,16 @@ function renamings() {
/** /**
* Helper method for gzipping file. * Helper method for gzipping file.
* @param {string} file target file * @param {string} file Target file.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
function gzipFile(file) { function gzipFile(file) {
return new Promise((resolve) => { return new Promise((resolve) => {
const name = path.posix.join(RELEASE_DIR, file); const name = path.posix.join(RELEASE_DIR, file);
const stream = gulp.src(name) const stream = gulp.src(name)
.pipe(gzip()) .pipe(gzip())
.pipe(gulp.dest(RELEASE_DIR)); .pipe(gulp.dest(RELEASE_DIR));
stream.on('end', () => { stream.on('end', () => {
resolve(); resolve();
@@ -164,9 +162,9 @@ function gzipFile(file) {
/** /**
* Helper method for comparing file size. * Helper method for comparing file size.
* @param {string} file target file * @param {string} file Target file.
* @param {number} expected expected size * @param {number} expected Expected size.
* @return {number} 0: success / 1: failed * @return {number} 0: success / 1: failed.
*/ */
function compareSize(file, expected) { function compareSize(file, expected) {
const name = path.posix.join(RELEASE_DIR, file); const name = path.posix.join(RELEASE_DIR, file);
@@ -176,12 +174,12 @@ function compareSize(file, expected) {
if (size > compare) { if (size > compare) {
const message = `Failed: ` + 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}`); console.log(`${BOLD_RED}${message}${ANSI_RESET}`);
return 1; return 1;
} else { } else {
const message = 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}`); console.log(`${BOLD_GREEN}${message}${ANSI_RESET}`);
return 0; return 0;
} }
@@ -189,7 +187,7 @@ function compareSize(file, expected) {
/** /**
* Helper method for zipping the compressed files. * Helper method for zipping the compressed files.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
function zippingFiles() { function zippingFiles() {
// GZip them for additional size comparisons (keep originals, force // GZip them for additional size comparisons (keep originals, force
@@ -202,7 +200,7 @@ function zippingFiles() {
/** /**
* Check the sizes of built files for unexpected growth. * Check the sizes of built files for unexpected growth.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
async function metadata() { async function metadata() {
// Zipping the compressed files. // Zipping the compressed files.
@@ -234,10 +232,10 @@ async function metadata() {
/** /**
* Run Mocha tests inside a browser. * Run Mocha tests inside a browser.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
async function mocha() { async function mocha() {
const result = await runMochaTestsInBrowser().catch(e => { const result = await runMochaTestsInBrowser().catch(e => {
throw e; throw e;
}); });
if (result) { if (result) {
@@ -248,9 +246,9 @@ async function mocha() {
/** /**
* Helper method for comparison file. * Helper method for comparison file.
* @param {string} file1 first target file * @param {string} file1 First target file.
* @param {string} file2 second target file * @param {string} file2 Second target file.
* @return {boolean} comparison result (true: same / false: different) * @return {boolean} Comparison result (true: same / false: different).
*/ */
function compareFile(file1, file2) { function compareFile(file1, file2) {
const buf1 = fs.readFileSync(file1); const buf1 = fs.readFileSync(file1);
@@ -258,14 +256,13 @@ function compareFile(file1, file2) {
// Normalize the line feed. // Normalize the line feed.
const code1 = buf1.toString().replace(/(?:\r\n|\r|\n)/g, '\n'); const code1 = buf1.toString().replace(/(?:\r\n|\r|\n)/g, '\n');
const code2 = buf2.toString().replace(/(?:\r\n|\r|\n)/g, '\n'); const code2 = buf2.toString().replace(/(?:\r\n|\r|\n)/g, '\n');
const result = (code1 === code2); return code1 === code2;
return result;
} }
/** /**
* Helper method for checking the result of generator. * Helper method for checking the result of generator.
* @param {string} suffix target suffix * @param {string} suffix Target suffix.
* @return {number} check result (0: success / 1: failed) * @return {number} Check result (0: success / 1: failed).
*/ */
function checkResult(suffix) { function checkResult(suffix) {
const fileName = `generated.${suffix}`; const fileName = `generated.${suffix}`;
@@ -279,12 +276,12 @@ function checkResult(suffix) {
if (fs.existsSync(goldenFileName)) { if (fs.existsSync(goldenFileName)) {
if (compareFile(resultFileName, goldenFileName)) { if (compareFile(resultFileName, goldenFileName)) {
console.log(`${SUCCESS_PREFIX} ${suffix}: ` + console.log(`${SUCCESS_PREFIX} ${suffix}: ` +
`${resultFileName} matches ${goldenFileName}`); `${resultFileName} matches ${goldenFileName}`);
return 0; return 0;
} else { } else {
console.log( console.log(
`${FAILURE_PREFIX} ${suffix}: ` + `${FAILURE_PREFIX} ${suffix}: ` +
`${resultFileName} does not match ${goldenFileName}`); `${resultFileName} does not match ${goldenFileName}`);
} }
} else { } else {
console.log(`File ${goldenFileName} not found!`); console.log(`File ${goldenFileName} not found!`);
@@ -297,7 +294,7 @@ function checkResult(suffix) {
/** /**
* Run generator tests inside a browser and check the results. * Run generator tests inside a browser and check the results.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
async function generators() { async function generators() {
// Clean up. // Clean up.
@@ -311,7 +308,7 @@ async function generators() {
generatorSuffixes.forEach((suffix) => { generatorSuffixes.forEach((suffix) => {
failed += checkResult(suffix); failed += checkResult(suffix);
}); });
if (failed === 0) { if (failed === 0) {
console.log(`${BOLD_GREEN}All generator tests passed.${ANSI_RESET}`); console.log(`${BOLD_GREEN}All generator tests passed.${ANSI_RESET}`);
} else { } else {
@@ -323,12 +320,20 @@ async function generators() {
/** /**
* Run Node tests. * Run Node tests.
* @return {Promise} asynchronous result * @return {Promise} Asynchronous result.
*/ */
function node() { function node() {
return runTestCommand('mocha tests/node --config tests/node/.mocharc.js'); 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. // Run all tests in sequence.
const test = new Tester([ const test = new Tester([
@@ -339,7 +344,7 @@ const test = new Tester([
mocha, mocha,
generators, generators,
node, node,
buildTasks.onlyBuildAdvancedCompilationTest, advancedCompile,
]).asTask(); ]).asTask();

83
scripts/tsick.js Normal file
View File

@@ -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');
}

View File

@@ -12,10 +12,13 @@ goog.module('Main');
// TODO: I think we need to make sure these get exported? // TODO: I think we need to make sure these get exported?
// const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions'); // const {BlocklyOptions} = goog.requireType('Blockly.BlocklyOptions');
const {inject} = goog.require('Blockly.inject'); const {inject} = goog.require('Blockly.inject');
const {getMainWorkspace} = goog.require('Blockly.common');
const {Msg} = goog.require('Blockly.Msg');
/** @suppress {extraRequire} */ /** @suppress {extraRequire} */
goog.require('Blockly.geras.Renderer'); goog.require('Blockly.geras.Renderer');
/** @suppress {extraRequire} */ /** @suppress {extraRequire} */
goog.require('Blockly.VerticalFlyout'); goog.require('Blockly.VerticalFlyout');
// Blocks // Blocks
/** @suppress {extraRequire} */ /** @suppress {extraRequire} */
goog.require('Blockly.libraryBlocks.logic'); goog.require('Blockly.libraryBlocks.logic');
@@ -30,8 +33,25 @@ goog.require('testBlocks');
function init() { function init() {
Object.assign(Msg, window['Blockly']['Msg']);
inject('blocklyDiv', /** @type {BlocklyOptions} */ ({ inject('blocklyDiv', /** @type {BlocklyOptions} */ ({
'toolbox': document.getElementById('toolbox') 'toolbox': document.getElementById('toolbox')
})); }));
}; }
window.addEventListener('load', init); 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;
}
};

View File

@@ -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};

View File

@@ -19,7 +19,6 @@
<script src="../bootstrap.js"></script> <script src="../bootstrap.js"></script>
<script> <script>
var demoWorkspace = null; var demoWorkspace = null;
var outputCode = '';
function start() { function start() {
demoWorkspace = Blockly.inject('blocklyDiv', { demoWorkspace = Blockly.inject('blocklyDiv', {
@@ -172,31 +171,26 @@ function toJavaScript() {
var code = '\'use strict\';\n\n' var code = '\'use strict\';\n\n'
code += javascriptGenerator.workspaceToCode(demoWorkspace); code += javascriptGenerator.workspaceToCode(demoWorkspace);
setOutput(code); setOutput(code);
outputCode = code;
} }
function toPython() { function toPython() {
var code = pythonGenerator.workspaceToCode(demoWorkspace); var code = pythonGenerator.workspaceToCode(demoWorkspace);
setOutput(code); setOutput(code);
outputCode = code;
} }
function toPhp() { function toPhp() {
var code = phpGenerator.workspaceToCode(demoWorkspace); var code = phpGenerator.workspaceToCode(demoWorkspace);
setOutput(code); setOutput(code);
outputCode = code;
} }
function toLua() { function toLua() {
var code = luaGenerator.workspaceToCode(demoWorkspace); var code = luaGenerator.workspaceToCode(demoWorkspace);
setOutput(code); setOutput(code);
outputCode = code;
} }
function toDart() { function toDart() {
var code = dartGenerator.workspaceToCode(demoWorkspace); var code = dartGenerator.workspaceToCode(demoWorkspace);
setOutput(code); setOutput(code);
outputCode = code;
} }
function changeIndex() { function changeIndex() {
@@ -205,7 +199,7 @@ function changeIndex() {
demoWorkspace.getToolbox().flyout_.workspace_.options.oneBasedIndex = oneBasedIndex; demoWorkspace.getToolbox().flyout_.workspace_.options.oneBasedIndex = oneBasedIndex;
} }
</script> </script>
<script type=module> <script type="module">
// Wait for Blockly to finish loading before running tests. // Wait for Blockly to finish loading before running tests.
import '../bootstrap_done.mjs'; import '../bootstrap_done.mjs';

View File

@@ -5,13 +5,12 @@
*/ */
/** /**
* @fileoverview Node.js script to run generator tests in Firefox, via webdriver. * @fileoverview Node.js script to run generator tests in Chrome, via webdriver.
*/ */
var webdriverio = require('webdriverio'); var webdriverio = require('webdriverio');
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
module.exports = runGeneratorsInBrowser;
/** /**
* Run the generator for a given language and save the results to a file. * Run the generator for a given language and save the results to a file.
@@ -36,8 +35,8 @@ async function runLangGeneratorInBrowser(browser, filename, codegenFn) {
* Runs the generator tests in Chrome. It uses webdriverio to * Runs the generator tests in Chrome. It uses webdriverio to
* launch Chrome and load index.html. Outputs a summary of the test results * launch Chrome and load index.html. Outputs a summary of the test results
* to the console and outputs files for later validation. * to the console and outputs files for later validation.
* @param {string} outputDir output directory * @param {string} outputDir Output directory.
* @return the Thenable managing the processing of the browser tests. * @return The Thenable managing the processing of the browser tests.
*/ */
async function runGeneratorsInBrowser(outputDir) { async function runGeneratorsInBrowser(outputDir) {
var options = { var options = {
@@ -66,7 +65,7 @@ async function runGeneratorsInBrowser(outputDir) {
console.log('Starting webdriverio...'); console.log('Starting webdriverio...');
const browser = await webdriverio.remote(options); const browser = await webdriverio.remote(options);
console.log('Initialized.\nLoading url: ' + url); console.log('Loading url: ' + url);
await browser.url(url); await browser.url(url);
await browser.execute(function() { await browser.execute(function() {
@@ -112,3 +111,5 @@ if (require.main === module) {
} }
}); });
} }
module.exports = {runGeneratorsInBrowser};

View File

@@ -7,10 +7,9 @@
/** /**
* @fileoverview Node.js script to run Mocha tests in Chrome, via webdriver. * @fileoverview Node.js script to run Mocha tests in Chrome, via webdriver.
*/ */
var webdriverio = require('webdriverio'); const webdriverio = require('webdriverio');
var {posixPath} = require('../../scripts/helpers'); const {posixPath} = require('../../scripts/helpers');
module.exports = runMochaTestsInBrowser;
/** /**
* Runs the Mocha tests in this directory in Chrome. It uses webdriverio to * Runs the Mocha tests in this directory in Chrome. It uses webdriverio to
@@ -19,14 +18,14 @@ module.exports = runMochaTestsInBrowser;
* @return {number} 0 on success, 1 on failure. * @return {number} 0 on success, 1 on failure.
*/ */
async function runMochaTestsInBrowser() { async function runMochaTestsInBrowser() {
var options = { const options = {
capabilities: { capabilities: {
browserName: 'chrome' browserName: 'chrome',
}, },
services: [ services: [
['selenium-standalone'] ['selenium-standalone'],
], ],
logLevel: 'warn' logLevel: 'warn',
}; };
// Run in headless mode on Github Actions. // Run in headless mode on Github Actions.
if (process.env.CI) { if (process.env.CI) {
@@ -34,41 +33,41 @@ async function runMochaTestsInBrowser() {
args: [ args: [
'--headless', '--no-sandbox', '--disable-dev-shm-usage', '--headless', '--no-sandbox', '--disable-dev-shm-usage',
'--allow-file-access-from-files', '--allow-file-access-from-files',
] ],
}; };
} else { } else {
// --disable-gpu is needed to prevent Chrome from hanging on Linux with // --disable-gpu is needed to prevent Chrome from hanging on Linux with
// NVIDIA drivers older than v295.20. See // NVIDIA drivers older than v295.20. See
// https://github.com/google/blockly/issues/5345 for details. // https://github.com/google/blockly/issues/5345 for details.
options.capabilities['goog:chromeOptions'] = { options.capabilities['goog:chromeOptions'] = {
args: ['--allow-file-access-from-files', '--disable-gpu'] args: ['--allow-file-access-from-files', '--disable-gpu'],
}; };
} }
var url = 'file://' + posixPath(__dirname) + '/index.html'; const url = 'file://' + posixPath(__dirname) + '/index.html';
console.log('Starting webdriverio...'); console.log('Starting webdriverio...');
const browser = await webdriverio.remote(options); const browser = await webdriverio.remote(options);
console.log('Initialized.\nLoading url: ' + url); console.log('Loading URL: ' + url);
await browser.url(url); await browser.url(url);
await browser.waitUntil(async () => { await browser.waitUntil(async() => {
var elem = await browser.$('#failureCount'); const elem = await browser.$('#failureCount');
var text = await elem.getAttribute('tests_failed'); const text = await elem.getAttribute('tests_failed');
return text != 'unset'; return text !== 'unset';
}, { }, {
timeout: 50000 timeout: 50000,
}); });
const elem = await browser.$('#failureCount'); const elem = await browser.$('#failureCount');
const numOfFailure = await elem.getAttribute('tests_failed'); const numOfFailure = await elem.getAttribute('tests_failed');
if (numOfFailure > 0) { if (numOfFailure > 0) {
console.log('============Blockly Mocha Test Failures================') console.log('============Blockly Mocha Test Failures================');
const failureMessagesEls = await browser.$$('#failureMessages p'); const failureMessagesEls = await browser.$$('#failureMessages p');
if (!failureMessagesEls.length) { if (!failureMessagesEls.length) {
console.log('There is at least one test failure, but no messages reported. Mocha may be failing because no tests are being run.'); console.log('There is at least one test failure, but no messages reported. Mocha may be failing because no tests are being run.');
} }
for (let el of failureMessagesEls) { for (const el of failureMessagesEls) {
console.log(await el.getText()); console.log(await el.getText());
} }
} }
@@ -84,7 +83,7 @@ async function runMochaTestsInBrowser() {
} }
if (require.main === module) { if (require.main === module) {
runMochaTestsInBrowser().catch(e => { runMochaTestsInBrowser().catch((e) => {
console.error(e); console.error(e);
process.exit(1); process.exit(1);
}).then(function(result) { }).then(function(result) {
@@ -97,3 +96,5 @@ if (require.main === module) {
} }
}); });
} }
module.exports = {runMochaTestsInBrowser};