diff --git a/.mocharc.js b/.mocharc.js new file mode 100644 index 0000000000..fb2c98119c --- /dev/null +++ b/.mocharc.js @@ -0,0 +1,19 @@ +const config = { + require: ['source-map-support/register', 'test/support/modules_helper.js', 'test/support/test_helper.js'], + file: ['test/support/root_hooks.js'], + reporter: 'test/support/mocha_reporter.js', +}; + +// mocha has a ridiculous issue (https://github.com/mochajs/mocha/issues/4100) that command line +// specs don't override config specs; they are merged instead, so you can't run a single test file +// if you've defined specs in your config. therefore we work around it by only adding specs to the +// config if none are passed as arguments +if (!process.argv.slice(2).some(isTestFile)) { + config.spec = ['test/realtime/*.test.js', 'test/rest/*.test.js']; +} + +function isTestFile(arg) { + return arg.match(/\.test.js$/); +} + +module.exports = config; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8aa3a0afb7..7cfdd46c47 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,27 +45,27 @@ Run the Mocha test suite npm run test:node -Or run just one test file +You can pass any Mocha CLI arguments and flags to the test:node script after the `--` separator, for example running one test file: - npm run test:node -- --file=test/realtime/auth.test.js + npm run test:node -- test/realtime/auth.test.js Or run just one test - npm run test:node -- --file=test/rest/status.test.js --grep=test_name_here + npm run test:node -- --grep=test_name_here Or run test skipping the build - npm run test:node:skip-build -- --file=test/rest/status.test.js --grep=test_name_here + npm run test:node:skip-build -- test/rest/status.test.js --grep=test_name_here ### Debugging the mocha tests locally with a debugger Run the following command to launch tests with the debugger enabled. The tests will block until you attach a debugger. - node --inspect-brk=9229 node_modules/.bin/grunt test:node + node --inspect-brk=9229 node_modules/.bin/mocha Alternatively you can also run the tests for single file - node --inspect-brk=9229 node_modules/.bin/grunt test:node --test=test/realtime/auth.test.js + node --inspect-brk=9229 node_modules/.bin/mocha test/realtime/auth.test.js The included vscode launch config allows you to launch and attach the debugger in one step, simply open the test file you want to run and start debugging. Note that breakpoint setting for realtime code will be within the diff --git a/Gruntfile.js b/Gruntfile.js index d158a35d8b..5b118f1afd 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,10 +8,9 @@ var umdWrapper = require('esbuild-plugin-umd-wrapper'); var banner = require('./src/fragments/license'); var process = require('process'); var stripLogsPlugin = require('./grunt/esbuild/strip-logs').default; +var MochaServer = require('./test/web_server'); module.exports = function (grunt) { - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-bump'); grunt.loadNpmTasks('grunt-webpack'); var dirs = { @@ -22,13 +21,6 @@ module.exports = function (grunt) { dest: 'build', }; - function compilerSpec(src, dest) { - return { - src: src, - dest: dest || src.replace(/\.js/, '.min.js'), - }; - } - async function execExternalPromises(cmd) { grunt.log.ok('Executing ' + cmd); return new Promise(function (resolve, reject) { @@ -55,7 +47,6 @@ module.exports = function (grunt) { var gruntConfig = { dirs: dirs, - pkgVersion: grunt.file.readJSON('package.json').version, webpack: { all: Object.values(webpackConfig), node: [webpackConfig.node], @@ -63,21 +54,6 @@ module.exports = function (grunt) { }, }; - gruntConfig.bump = { - options: { - files: ['package.json', 'README.md'], - globalReplace: true, - commit: true, - commitMessage: 'Regenerate and release version %VERSION%', - commitFiles: [], // Add files manually as can't add new files with a commit flag - createTag: true, - tagName: '%VERSION%', - tagMessage: 'Version %VERSION%', - push: false, - prereleaseName: 'beta', - }, - }; - grunt.initConfig(gruntConfig); grunt.registerTask('checkGitSubmodules', 'Check, if git submodules are properly installed', function () { @@ -108,6 +84,21 @@ module.exports = function (grunt) { grunt.registerTask('all', ['build', 'requirejs']); + grunt.registerTask('mocha:webserver', 'Run the Mocha web server', function () { + const done = this.async(); + const server = new MochaServer(); + server.listen(); + + process.on('SIGTERM', () => { + server.close(); + done(); + }); + process.on('SIGINT', () => { + server.close(); + done(); + }); + }); + grunt.registerTask('build:browser', function () { var done = this.async(); @@ -152,74 +143,12 @@ module.exports = function (grunt) { }); }); - grunt.loadTasks('test/tasks'); - - grunt.registerTask('test', ['test:node']); - grunt.registerTask( - 'test:node', - 'Build the library and run the node test suite\nOptions\n --test [tests] e.g. --test test/rest/auth.js', - ['build:node', 'mocha'], - ); - grunt.registerTask('test:webserver', 'Launch the Mocha test web server on http://localhost:3000/', [ 'build:browser', 'checkGitSubmodules', 'mocha:webserver', ]); - grunt.registerTask('release:refresh-pkgVersion', 'Refreshes GruntConfig.pkgVersion', function () { - grunt.config('pkgVersion', grunt.file.readJSON('package.json').version); - grunt.log.ok('pkgVersion updated'); - }); - - grunt.registerTask('release:git-add-generated', 'Adds generated files to the git staging area', function () { - var done = this.async(); - var generatedFiles = [ - gruntConfig.dirs.common + '/lib/util/defaults.js', - gruntConfig.dirs.fragments + '/license.js', - 'package.json', - 'package-lock.json', - 'README.md', - 'test/support/browser_file_list.js', - ]; - var cmd = 'git add -A ' + generatedFiles.join(' '); - grunt.log.ok('Executing ' + cmd); - - require('child_process').exec(cmd, function (err, stdout, stderr) { - if (err) { - grunt.fatal('git add . -A failed with ' + stderr); - } - done(); - }); - }); - - grunt.registerTask('release:git-push', 'Pushes to git', execExternal('git push origin main --follow-tags')); - - grunt.registerTask('release:ably-deploy', 'Deploys to ably CDN', function () { - var version = grunt.file.readJSON('package.json').version, - cmd = 'node scripts/cdn_deploy.js --skipCheckout --tag ' + version; - console.log('Publishing version ' + version + ' of the library to the CDN'); - execExternal(cmd).call(this); - }); - - grunt.registerTask('release:deploy', 'Pushes a new release to github and then deploys to the Ably CDN', function () { - grunt.task.run(['release:git-push', 'release:ably-deploy']); - }); - - grunt.registerTask( - 'release', - 'Increments the version, regenerates, and makes a tagged commit. Run as "grunt release:type", where "type" is "major", "minor", "patch", "prepatch", etc.)', - function (versionType) { - grunt.task.run([ - 'bump-only:' + versionType, - 'release:refresh-pkgVersion', - 'all', - 'release:git-add-generated', - 'bump-commit', - ]); - }, - ); - (function () { const baseDir = path.join(__dirname, 'test', 'package', 'browser'); const buildDir = path.join(baseDir, 'build'); diff --git a/package-lock.json b/package-lock.json index cabe3808df..96ff9a92f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,15 +45,12 @@ "express": "^4.17.1", "glob": "~4.4", "grunt": "^1.6.1", - "grunt-bump": "^0.3.1", "grunt-cli": "~1.2.0", - "grunt-contrib-concat": "~0.5", "grunt-shell": "~1.1", "grunt-webpack": "^5.0.0", "hexy": "~0.2", "jmespath": "^0.16.0", "jsdom": "^20.0.0", - "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", "mocha": "^8.1.3", "mocha-junit-reporter": "^2.2.1", @@ -66,6 +63,7 @@ "requirejs": "~2.1", "shelljs": "~0.8", "source-map-explorer": "^2.5.2", + "source-map-support": "^0.5.21", "stream-browserify": "^3.0.0", "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^4.0.1", @@ -2117,15 +2115,6 @@ "ajv": "^6.9.1" } }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "engines": { - "node": ">=0.4.2" - } - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -5264,30 +5253,6 @@ "node": ">=16" } }, - "node_modules/grunt-bump": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/grunt-bump/-/grunt-bump-0.3.4.tgz", - "integrity": "sha512-BqAVn7i091B8DMjUCKtMz7KUQmVBUvBRmrDaf72UOxe6sqV9JjczroXNkr/nOLPp2N9XZFbgexAf7JtNv8FrSg==", - "dev": true, - "dependencies": { - "semver": "^4.3.3" - }, - "engines": { - "node": ">= 0.8.0" - }, - "peerDependencies": { - "grunt": ">=0.4.0" - } - }, - "node_modules/grunt-bump/node_modules/semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha512-IrpJ+yoG4EOH8DFWuVg+8H1kW1Oaof0Wxe7cPcXW3x9BjkN/eVo54F15LyqemnDIUYskQWr9qvl/RihmSy6+xQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/grunt-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", @@ -5349,92 +5314,6 @@ "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", "dev": true }, - "node_modules/grunt-contrib-concat": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-0.5.1.tgz", - "integrity": "sha512-on8j+wGXvo6yVEDjyoUsFU2GeYcpZ9Qhj78XQMeU3yDYSPcHfCeuOo2hV8GAQO9u1JuQFEHgA0O7wFKXO7miqg==", - "dev": true, - "dependencies": { - "chalk": "^0.5.1", - "source-map": "^0.3.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "grunt": ">=0.4.0" - } - }, - "node_modules/grunt-contrib-concat/node_modules/ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-concat/node_modules/ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha512-f2PKUkN5QngiSemowa6Mrk9MPCdtFiOSmibjZ+j1qhLGHHYsqZwmBMRF3IRMVXo8sybDqx2fJl2d/8OphBoWkA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-concat/node_modules/chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha512-bIKA54hP8iZhyDT81TOsJiQvR1gW+ZYSXFaZUAvoD4wCHdbHY2actmpTE4x344ZlFqHbvoxKOaESULTZN2gstg==", - "dev": true, - "dependencies": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-concat/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/grunt-contrib-concat/node_modules/strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha512-DerhZL7j6i6/nEnVG0qViKXI0OKouvvpsAiaj7c+LfqZZZxdwZtv8+UiA/w4VUJpT8UzX0pR1dcHOii1GbmruQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^0.2.1" - }, - "bin": { - "strip-ansi": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/grunt-contrib-concat/node_modules/supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha512-tdCZ28MnM7k7cJDJc7Eq80A9CsRFAAOZUy41npOZCs++qSjfIy7o5Rh46CBk+Dk5FbKJ33X3Tqg4YrV07N5RaA==", - "dev": true, - "bin": { - "supports-color": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/grunt-known-options": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", @@ -5697,30 +5576,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha512-1YsTg1fk2/6JToQhtZkArMkurq8UoWU1Qe0aR3VUHjgij4nOylSWLWAtBXoZ4/dXOmugfLGm1c+QhuD0JyedFA==", - "dev": true, - "dependencies": { - "ansi-regex": "^0.2.0" - }, - "bin": { - "has-ansi": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-ansi/node_modules/ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -6749,19 +6604,6 @@ "node": ">=4.0" } }, - "node_modules/kexec": { - "version": "3.0.0", - "resolved": "git+ssh://git@github.com/ably-forks/node-kexec.git#f29f54037c7db6ad29e1781463b182e5929215a0", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "nan": "^2.13.2" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7337,12 +7179,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "dev": true - }, "node_modules/nanoid": { "version": "3.1.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", @@ -8994,18 +8830,6 @@ "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/source-map": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", - "integrity": "sha512-jz8leTIGS8+qJywWiO9mKza0hJxexdeIYXhDHw9avTQcXSNAGk3hiiRMpmI2Qf9dOrZDrDpgH9VNefzuacWC9A==", - "dev": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/source-map-explorer": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.3.tgz", @@ -12391,12 +12215,6 @@ "dev": true, "requires": {} }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true - }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -14779,23 +14597,6 @@ } } }, - "grunt-bump": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/grunt-bump/-/grunt-bump-0.3.4.tgz", - "integrity": "sha512-BqAVn7i091B8DMjUCKtMz7KUQmVBUvBRmrDaf72UOxe6sqV9JjczroXNkr/nOLPp2N9XZFbgexAf7JtNv8FrSg==", - "dev": true, - "requires": { - "semver": "^4.3.3" - }, - "dependencies": { - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha512-IrpJ+yoG4EOH8DFWuVg+8H1kW1Oaof0Wxe7cPcXW3x9BjkN/eVo54F15LyqemnDIUYskQWr9qvl/RihmSy6+xQ==", - "dev": true - } - } - }, "grunt-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", @@ -14844,64 +14645,6 @@ } } }, - "grunt-contrib-concat": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/grunt-contrib-concat/-/grunt-contrib-concat-0.5.1.tgz", - "integrity": "sha512-on8j+wGXvo6yVEDjyoUsFU2GeYcpZ9Qhj78XQMeU3yDYSPcHfCeuOo2hV8GAQO9u1JuQFEHgA0O7wFKXO7miqg==", - "dev": true, - "requires": { - "chalk": "^0.5.1", - "source-map": "^0.3.0" - }, - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==", - "dev": true - }, - "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha512-f2PKUkN5QngiSemowa6Mrk9MPCdtFiOSmibjZ+j1qhLGHHYsqZwmBMRF3IRMVXo8sybDqx2fJl2d/8OphBoWkA==", - "dev": true - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha512-bIKA54hP8iZhyDT81TOsJiQvR1gW+ZYSXFaZUAvoD4wCHdbHY2actmpTE4x344ZlFqHbvoxKOaESULTZN2gstg==", - "dev": true, - "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha512-DerhZL7j6i6/nEnVG0qViKXI0OKouvvpsAiaj7c+LfqZZZxdwZtv8+UiA/w4VUJpT8UzX0pR1dcHOii1GbmruQ==", - "dev": true, - "requires": { - "ansi-regex": "^0.2.1" - } - }, - "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha512-tdCZ28MnM7k7cJDJc7Eq80A9CsRFAAOZUy41npOZCs++qSjfIy7o5Rh46CBk+Dk5FbKJ33X3Tqg4YrV07N5RaA==", - "dev": true - } - } - }, "grunt-known-options": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", @@ -15038,23 +14781,6 @@ "duplexer": "^0.1.2" } }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha512-1YsTg1fk2/6JToQhtZkArMkurq8UoWU1Qe0aR3VUHjgij4nOylSWLWAtBXoZ4/dXOmugfLGm1c+QhuD0JyedFA==", - "dev": true, - "requires": { - "ansi-regex": "^0.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==", - "dev": true - } - } - }, "has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -15790,14 +15516,6 @@ "object.values": "^1.1.6" } }, - "kexec": { - "version": "git+ssh://git@github.com/ably-forks/node-kexec.git#f29f54037c7db6ad29e1781463b182e5929215a0", - "dev": true, - "from": "kexec@ably-forks/node-kexec#update-for-node-12", - "requires": { - "nan": "^2.13.2" - } - }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -16230,12 +15948,6 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "dev": true - }, "nanoid": { "version": "3.1.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", @@ -17429,15 +17141,6 @@ "is-fullwidth-code-point": "^3.0.0" } }, - "source-map": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.3.0.tgz", - "integrity": "sha512-jz8leTIGS8+qJywWiO9mKza0hJxexdeIYXhDHw9avTQcXSNAGk3hiiRMpmI2Qf9dOrZDrDpgH9VNefzuacWC9A==", - "dev": true, - "requires": { - "amdefine": ">=0.0.4" - } - }, "source-map-explorer": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.3.tgz", diff --git a/package.json b/package.json index b1e36ba611..d57cdbf94b 100644 --- a/package.json +++ b/package.json @@ -81,15 +81,12 @@ "express": "^4.17.1", "glob": "~4.4", "grunt": "^1.6.1", - "grunt-bump": "^0.3.1", "grunt-cli": "~1.2.0", - "grunt-contrib-concat": "~0.5", "grunt-shell": "~1.1", "grunt-webpack": "^5.0.0", "hexy": "~0.2", "jmespath": "^0.16.0", "jsdom": "^20.0.0", - "kexec": "ably-forks/node-kexec#update-for-node-12", "minimist": "^1.2.5", "mocha": "^8.1.3", "mocha-junit-reporter": "^2.2.1", @@ -102,6 +99,7 @@ "requirejs": "~2.1", "shelljs": "~0.8", "source-map-explorer": "^2.5.2", + "source-map-support": "^0.5.21", "stream-browserify": "^3.0.0", "ts-loader": "^9.4.2", "tsconfig-paths-webpack-plugin": "^4.0.1", @@ -127,9 +125,9 @@ "scripts": { "start:react": "npx vite serve", "grunt": "grunt", - "test": "grunt test", - "test:node": "grunt test:node", - "test:node:skip-build": "grunt mocha", + "test": "npm run test:node", + "test:node": "npm run build:node && mocha", + "test:node:skip-build": "mocha", "test:webserver": "grunt test:webserver", "test:playwright": "node test/support/runPlaywrightTests.js", "test:react": "vitest run", diff --git a/test/all.js b/test/all.js deleted file mode 100644 index b94d0a8acd..0000000000 --- a/test/all.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -var fs = require('fs'), - path = require('path'), - specDir = __dirname; - -require('./support/modules_helper'); -require('./support/test_helper'); - -function findAll(dir, pattern) { - var searchDir = path.resolve(specDir, dir), - result = {}; - - fs.readdirSync(searchDir) - .filter(function (file) { - return pattern.test(file); - }) - .forEach(function (file) { - result[file.match(pattern)[1]] = require(path.resolve(searchDir, file)); - }); - - return result; -} - -exports.rest = findAll('rest', /(\w+)\.test\.js/); -exports.realtime = findAll('realtime', /(\w+)\.test\.js/); - -exports.tear_down = require('./support/tear_down'); diff --git a/test/common/globals/environment.js b/test/common/globals/environment.js index ff27799bb4..f1515222f8 100644 --- a/test/common/globals/environment.js +++ b/test/common/globals/environment.js @@ -1,7 +1,7 @@ /* Assumes process.env defined, or window.__env__ or populated via globals.env.js and karam-env-preprocessor plugin */ define(function (require) { - var defaultLogLevel = 2, + var defaultLogLevel = 4, environment = isBrowser ? window.__env__ || {} : process.env, ablyEnvironment = environment.ABLY_ENV || 'sandbox', realtimeHost = environment.ABLY_REALTIME_HOST, @@ -11,6 +11,8 @@ define(function (require) { tls = 'ABLY_USE_TLS' in environment ? environment.ABLY_USE_TLS.toLowerCase() !== 'false' : true, logLevel = environment.ABLY_LOG_LEVEL || defaultLogLevel; + let logLevelSet = environment.ABLY_LOG_LEVEL !== undefined; + if (isBrowser) { var url = window.location.href, keysValues = url.split(/[\?&]+/), @@ -27,13 +29,39 @@ define(function (require) { if (query['port']) port = query['port']; if (query['tls_port']) tlsPort = query['tls_port']; if (query['tls']) tls = query['tls'].toLowerCase() !== 'false'; - if (query['log_level']) logLevel = Number(query['log_level']) || defaultLogLevel; + if (query['log_level']) { + logLevel = Number(query['log_level']); + logLevelSet = true; + } } else if (process) { process.on('uncaughtException', function (err) { console.error(err.stack); }); } + function getLogTimestamp() { + const time = new Date(); + return ( + time.getHours().toString().padStart(2, '0') + + ':' + + time.getMinutes().toString().padStart(2, '0') + + ':' + + time.getSeconds().toString().padStart(2, '0') + + '.' + + time.getMilliseconds().toString().padStart(3, '0') + ); + } + + let clientLogs = []; + + function getLogs() { + return clientLogs; + } + + function flushLogs() { + clientLogs = []; + } + return (module.exports = { environment: ablyEnvironment, realtimeHost: realtimeHost, @@ -42,12 +70,15 @@ define(function (require) { tlsPort: tlsPort, tls: tls, logLevel: logLevel, + getLogs, + flushLogs, + logHandler: function (msg) { - var time = new Date(); - console.log( - time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds() + '.' + time.getMilliseconds(), - msg, - ); + if (logLevelSet) { + console.log(getLogTimestamp(), msg); + } else { + clientLogs.push([getLogTimestamp(), msg]); + } }, }); }); diff --git a/test/common/modules/shared_helper.js b/test/common/modules/shared_helper.js index 833bbfa114..9f922fc669 100644 --- a/test/common/modules/shared_helper.js +++ b/test/common/modules/shared_helper.js @@ -7,9 +7,10 @@ define([ 'test/common/modules/testapp_module', 'test/common/modules/client_module', 'test/common/modules/testapp_manager', + 'globals', 'async', 'chai', -], function (testAppModule, clientModule, testAppManager, async, chai) { +], function (testAppModule, clientModule, testAppManager, globals, async, chai) { var utils = clientModule.Ably.Realtime.Utils; var platform = clientModule.Ably.Realtime.Platform; var BufferUtils = platform.BufferUtils; @@ -236,6 +237,38 @@ define([ expect(json1 === json2, 'JSON data contents mismatch.').to.be.ok; } + let activeClients = []; + + function AblyRealtime(options) { + const client = clientModule.AblyRealtime(options); + activeClients.push(client); + return client; + } + + /* Slightly crude catch-all hook to close any dangling realtime clients left open + * after a test fails without calling closeAndFinish */ + function closeActiveClients() { + activeClients.forEach((client) => { + client.close(); + }); + activeClients = []; + } + + function logTestResults() { + if (this.currentTest.isFailed()) { + const logs = globals.getLogs(); + if (logs.length > 0) { + // empty console.logs are for vertical spacing + console.log(); + console.log('Logs for failing test: \n'); + logs.forEach(([timestamp, log]) => { + console.log(timestamp, log); + }); + console.log(); + } + } + } + return (module.exports = { setupApp: testAppModule.setup, tearDownApp: testAppModule.tearDown, @@ -244,7 +277,7 @@ define([ Ably: clientModule.Ably, AblyRest: clientModule.AblyRest, - AblyRealtime: clientModule.AblyRealtime, + AblyRealtime: AblyRealtime, ablyClientOptions: clientModule.ablyClientOptions, Utils: utils, @@ -270,5 +303,7 @@ define([ whenPromiseSettles: whenPromiseSettles, randomString: randomString, testMessageEquality: testMessageEquality, + closeActiveClients, + logTestResults, }); }); diff --git a/test/realtime/auth.test.js b/test/realtime/auth.test.js index ad82908c3c..eacae1f9a3 100644 --- a/test/realtime/auth.test.js +++ b/test/realtime/auth.test.js @@ -1318,7 +1318,6 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async /* Check that only the last authorize matters */ it('multiple_concurrent_authorize', function (done) { var realtime = helper.AblyRealtime({ - logLevel: 4, useTokenAuth: true, defaultTokenParams: { capability: { wrong: ['*'] } }, }); diff --git a/test/realtime/channel.test.js b/test/realtime/channel.test.js index b4851edcbd..f8fd8ade1d 100644 --- a/test/realtime/channel.test.js +++ b/test/realtime/channel.test.js @@ -1474,11 +1474,16 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async channel.state = 'suspended'; whenPromiseSettles(channel.detach(), function () { - expect(channel.state).to.equal( - 'detached', - 'Check that detach on suspended channel results in detached channel', - ); - done(); + try { + expect(channel.state).to.equal( + 'detached', + 'Check that detach on suspended channel results in detached channel', + ); + + closeAndFinish(done, realtime); + } catch (err) { + closeAndFinish(done, realtime, err); + } }); }); @@ -1492,10 +1497,10 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async whenPromiseSettles(channel.detach(), function (err) { if (!err) { - done(new Error('expected detach to return error response')); + closeAndFinish(done, realtime, new Error('expected detach to return error response')); return; } - done(); + closeAndFinish(done, realtime); }); }); diff --git a/test/realtime/connection.test.js b/test/realtime/connection.test.js index 2a5eeb5ff1..413902b25e 100644 --- a/test/realtime/connection.test.js +++ b/test/realtime/connection.test.js @@ -68,7 +68,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async it('connectionAttributes', function (done) { var realtime; try { - realtime = helper.AblyRealtime({ logLevel: 4 }); + realtime = helper.AblyRealtime(); realtime.connection.on('connected', function () { try { const recoveryContext = JSON.parse(realtime.connection.recoveryKey); diff --git a/test/realtime/connectivity.test.js b/test/realtime/connectivity.test.js index 3fb4136b97..ba2c408323 100644 --- a/test/realtime/connectivity.test.js +++ b/test/realtime/connectivity.test.js @@ -34,19 +34,20 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { }); }); + function options(connectivityCheckUrl, disableConnectivityCheck) { + return { + connectivityCheckUrl, + disableConnectivityCheck, + autoConnect: false, + }; + } + describe('configured_connectivity_check_url', function () { var urlScheme = 'https://'; var echoServer = 'echo.ably.io'; var successUrl = echoServer + '/respondwith?status=200'; var failUrl = echoServer + '/respondwith?status=500'; - function options(connectivityCheckUrl) { - return { - connectivityCheckUrl: connectivityCheckUrl, - autoConnect: false, - }; - } - it('succeeds with scheme', function (done) { whenPromiseSettles( new helper.AblyRealtime(options(urlScheme + successUrl)).http.checkConnectivity(), @@ -129,10 +130,7 @@ define(['ably', 'shared_helper', 'chai'], function (Ably, helper, chai) { it('disable_connectivity_check', function (done) { whenPromiseSettles( - new helper.AblyRealtime({ - connectivityCheckUrl: 'notarealhost', - disableConnectivityCheck: true, - }).http.checkConnectivity(), + new helper.AblyRealtime(options('notarealhost', true)).http.checkConnectivity(), function (err, res) { try { expect(res && !err, 'Connectivity check completed ' + (err && utils.inspectError(err))).to.be.ok; diff --git a/test/realtime/presence.test.js b/test/realtime/presence.test.js index 11d39707fd..1855aeb12f 100644 --- a/test/realtime/presence.test.js +++ b/test/realtime/presence.test.js @@ -1968,9 +1968,9 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async * and only members that changed between ATTACHED states should result in * presence events */ it('suspended_preserves_presence', function (done) { - var mainRealtime = helper.AblyRealtime({ clientId: 'main', logLevel: 4 }), - continuousRealtime = helper.AblyRealtime({ clientId: 'continuous', logLevel: 4 }), - leavesRealtime = helper.AblyRealtime({ clientId: 'leaves', logLevel: 4 }), + var mainRealtime = helper.AblyRealtime({ clientId: 'main' }), + continuousRealtime = helper.AblyRealtime({ clientId: 'continuous' }), + leavesRealtime = helper.AblyRealtime({ clientId: 'leaves' }), channelName = 'suspended_preserves_presence', mainChannel = mainRealtime.channels.get(channelName); diff --git a/test/rest/fallbacks.test.js b/test/rest/fallbacks.test.js index 3828980878..e0883def5f 100644 --- a/test/rest/fallbacks.test.js +++ b/test/rest/fallbacks.test.js @@ -24,7 +24,6 @@ define(['shared_helper', 'async', 'chai'], function (helper, async, chai) { restHost: helper.unroutableHost, fallbackHosts: [goodHost], httpRequestTimeout: 3000, - logLevel: 4, }); var validUntil; var serverTime = await rest.time(); diff --git a/test/support/browser_setup.js b/test/support/browser_setup.js index 9a4f59bba7..2cab40ad30 100644 --- a/test/support/browser_setup.js +++ b/test/support/browser_setup.js @@ -1,6 +1,7 @@ 'use strict'; var allTestFiles = [], + TEST_HOOKS_REGEXP = /root_hooks\.js$/i, TEST_REGEXP = /\.test\.js$/i, // TEST_REGEXP = /simple\.test\.js$/i, TEAR_DOWN_REGEXP = /tear_down\.js$/i; @@ -17,6 +18,14 @@ var forEachKey = function (object, fn) { } }; +// Add the root mocha hooks +forEachKey(window.__testFiles__.files, function (file) { + if (TEST_HOOKS_REGEXP.test(file)) { + // Normalize paths to RequireJS module names. + allTestFiles.push(pathToModule(file)); + } +}); + // Match all test files forEachKey(window.__testFiles__.files, function (file) { if (TEST_REGEXP.test(file)) { diff --git a/test/support/root_hooks.js b/test/support/root_hooks.js index 8c96f0d332..0576bc9a64 100644 --- a/test/support/root_hooks.js +++ b/test/support/root_hooks.js @@ -9,4 +9,7 @@ define(['shared_helper'], function (helper) { done(); }); }); + + afterEach(helper.closeActiveClients); + afterEach(helper.logTestResults); }); diff --git a/test/support/runPlaywrightTests.js b/test/support/runPlaywrightTests.js index 5ca2e323b3..5dd1ca2b51 100644 --- a/test/support/runPlaywrightTests.js +++ b/test/support/runPlaywrightTests.js @@ -1,16 +1,16 @@ const playwright = require('playwright'); const path = require('path'); -const mochaWebServerProcess = require('child_process').fork(path.resolve(__dirname, '..', 'web_server'), { - env: { PLAYWRIGHT_TEST: 1 }, -}); +const MochaServer = require('../web_server'); const fs = require('fs'); const jUnitDirectoryPath = require('./junit_directory_path'); const port = process.env.PORT || 3000; const host = 'localhost'; const playwrightBrowsers = ['chromium', 'firefox', 'webkit']; +const mochaServer = new MochaServer(/* playwrightTest: */ true); const runTests = async (browserType) => { + mochaServer.listen(); const browser = await browserType.launch(); const page = await browser.newPage(); await page.goto(`http://${host}:${port}`); @@ -85,7 +85,7 @@ const runTests = async (browserType) => { caughtError = error; } - mochaWebServerProcess.kill(); + mochaServer.close(); // now when mocha web server is terminated, if there was an error, we can log it and exit with a failure code if (caughtError) { diff --git a/test/tasks/grunt-mocha.js b/test/tasks/grunt-mocha.js deleted file mode 100644 index 576cfef510..0000000000 --- a/test/tasks/grunt-mocha.js +++ /dev/null @@ -1,70 +0,0 @@ -'use strict'; - -var fs = require('fs'), - path = require('path'), - shell = require('shelljs'), - kexec = require('kexec'); - -module.exports = function (grunt) { - var file = grunt.option('file'), - debug = grunt.option('debug'), - inspector = grunt.option('inspector'), - fgrep = grunt.option('fgrep'), - helpers = ['test/support/modules_helper.js', 'test/support/test_helper.js', 'test/support/root_hooks.js']; - - function getRelativePath(files) { - return files.map(function (helperPath) { - var fullPath = path.join(path.dirname(fs.realpathSync(__filename)), '../..', helperPath); - return path.relative(process.cwd(), fullPath); - }); - } - - function resolveTests(testString) { - return testString.split(',').map(function (test) { - var fullPath = path.join(process.cwd(), test); - return path.relative(process.cwd(), fullPath); - }); - } - - grunt.registerTask('mocha:webserver', 'Run the Mocha web server', function () { - kexec('test/web_server'); - }); - - grunt.registerTask( - 'mocha', - 'Run the Mocha test suite.\nOptions:\n --file= e.g. --file=test/rest/auth.test.js\n --debug will debug using standard node debugger\n --inspector will start with node inspector', - function () { - var runTests = getRelativePath(helpers).concat(['test/realtime/*.test.js', 'test/rest/*.test.js']).join(' '); - grunt.log.writeln('Running Mocha test suite against ' + (file ? file : 'all tests')); - - if (file) { - runTests = getRelativePath(helpers).concat(resolveTests(file)).join(' '); - } - - if (fgrep) { - runTests += ' --fgrep ' + fgrep; - } - - runTests += ` --reporter ${path.join(__dirname, '..', 'support', 'mocha_reporter.js')}`; - - var done = this.async(), - nodeExecutable = 'node'; - - if (debug) { - nodeExecutable = 'node debug'; - } else if (inspector) { - nodeExecutable = 'node-debug'; - } - - shell.exec(nodeExecutable + ' node_modules/.bin/mocha ' + runTests, function (code) { - if (code !== 0) { - grunt.log.error('Mocha tests failed!'); - shell.exit(1); - } else { - grunt.log.ok('Mocha tests passed'); - } - done(); - }); - }, - ); -}; diff --git a/test/web_server b/test/web_server deleted file mode 100755 index f42134d906..0000000000 --- a/test/web_server +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env node - -/* - Runs a simple web server that runs the mocha.html tests - This is useful if you need to run the mocha tests visually - via a tunnel to your localhost server -*/ - -"use strict"; - -var express = require('express'), - cors = require('cors'); - -var server = express(); - -/* - * This environment varuable is used to let this script know that tests are - * running in a playwright context. When truthy, a different html document is - * returned on 'GET /' and console logging is skipped. - */ -const playwrightTest = !!process.env.PLAYWRIGHT_TEST; - -server.use(function(req, res, next) { - if (!playwrightTest) console.log('%s %s %s', req.method, req.url, req.path); - next(); -}); - -server.use(cors()); - -server.get('/', function(req, res) { - if (playwrightTest) { - res.redirect('/playwright.html'); - } else { - res.redirect('/mocha.html'); - } -}); - -server.use('/node_modules', express.static(__dirname + '/../node_modules')); -server.use('/test', express.static(__dirname)); -server.use('/browser', express.static(__dirname + '/../src/web')); -server.use('/build', express.static(__dirname + '/../build')); -server.use(express.static(__dirname)); - -var port = process.env.PORT || 3000; -server.listen(port); - -console.log("Mocha test server listening on http://localhost:3000/"); diff --git a/test/web_server.js b/test/web_server.js new file mode 100755 index 0000000000..bc5c29bdb2 --- /dev/null +++ b/test/web_server.js @@ -0,0 +1,53 @@ +var express = require('express'), + cors = require('cors'); + +/** + * Runs a simple web server that runs the mocha.html tests + * This is useful if you need to run the mocha tests visually + * via a tunnel to your localhost server + * + * @param playwrightTest - used to let this script know that tests are + * running in a playwright context. When truthy, a different html document is + * returned on 'GET /' and console logging is skipped. + */ + +class MochaServer { + constructor(playwrightTest) { + this.playwrightTest = playwrightTest; + } + + async listen() { + const app = express(); + app.use((req, res, next) => { + if (!this.playwrightTest) console.log('%s %s %s', req.method, req.url, req.path); + next(); + }); + + app.use(cors()); + + app.get('/', (req, res) => { + if (this.playwrightTest) { + res.redirect('/playwright.html'); + } else { + res.redirect('/mocha.html'); + } + }); + + app.use('/node_modules', express.static(__dirname + '/../node_modules')); + app.use('/test', express.static(__dirname)); + app.use('/browser', express.static(__dirname + '/../src/web')); + app.use('/build', express.static(__dirname + '/../build')); + app.use(express.static(__dirname)); + + const port = process.env.PORT || 3000; + this.server = app.listen(port); + + console.log('Mocha test server listening on http://localhost:3000/'); + } + + close() { + this.server.close(); + } +} + +module.exports = MochaServer; diff --git a/webpack.config.js b/webpack.config.js index e0e8b1942c..e32013f942 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -61,6 +61,7 @@ const nodeConfig = { optimization: { minimize: false, }, + devtool: 'source-map', }; const nativeScriptConfig = {