Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix test running in docker (close #5709) #5724

Merged
merged 1 commit into from
Nov 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 24 additions & 75 deletions Gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ const listBrowsers = require('testcafe-browser-tools').getInsta
const npmAuditor = require('npm-auditor');
const checkLicenses = require('./test/dependency-licenses-checker');
const packageInfo = require('./package');
const getPublishTags = require('./docker/get-publish-tags');
const isDockerDaemonRunning = require('./docker/is-docker-daemon-running');
const ensureDockerEnvironment = require('./gulp/docker/ensure-docker-environment');
const getDockerPublishInfo = require('./gulp/docker/get-publish-info');
const runFunctionalTestInDocker = require('./gulp/docker/run-functional-test-via-command-line');
const { exitDomains, enterDomains } = require('./gulp/helpers/domain');
const getTimeout = require('./gulp/helpers/get-timeout');

Expand Down Expand Up @@ -146,8 +147,7 @@ const CLIENT_TESTS_SAUCELABS_SETTINGS = {

const CLIENT_TEST_LOCAL_BROWSERS_ALIASES = ['ie', 'edge', 'chrome', 'firefox', 'safari'];

const PUBLISH_TAGS = getPublishTags(packageInfo);
const PUBLISH_REPO = 'testcafe/testcafe';
const { PUBLISH_TAGS, PUBLISH_REPO } = getDockerPublishInfo(packageInfo);

const NODE_MODULE_BINS = path.join(__dirname, 'node_modules/.bin');

Expand Down Expand Up @@ -206,7 +206,7 @@ gulp.task('lint', () => {
'src/**/*.js',
'src/**/*.ts',
'test/**/*.js',
'gulp/helpers/**/*',
'gulp/**/*.js',
'!test/client/vendor/**/*.*',
'!test/functional/fixtures/api/es-next/custom-client-scripts/data/*.js',
'Gulpfile.js'
Expand Down Expand Up @@ -851,71 +851,6 @@ gulp.step('test-functional-local-compiler-service-run', () => {

gulp.task('test-functional-local-compiler-service', gulp.series('prepare-tests', 'test-functional-local-compiler-service-run'));

function getDockerEnv (machineName) {
return childProcess
.execSync('docker-machine env --shell bash ' + machineName)
.toString()
.split('\n')
.map(line => {
return line.match(/export\s*(.*)="(.*)"$/);
})
.filter(match => {
return !!match;
})
.reduce((env, match) => {
env[match[1]] = match[2];
return env;
}, {});
}

function isDockerMachineRunning (machineName) {
try {
return childProcess.execSync('docker-machine status ' + machineName).toString().match(/Running/);
}
catch (e) {
return false;
}
}

function isDockerMachineExist (machineName) {
try {
childProcess.execSync('docker-machine status ' + machineName);
return true;
}
catch (e) {
return !e.message.match(/Host does not exist/);
}
}

function startDocker () {
const dockerMachineName = process.env['DOCKER_MACHINE_NAME'] || 'default';

if (!isDockerMachineExist(dockerMachineName))
childProcess.execSync('docker-machine create -d virtualbox ' + dockerMachineName);

if (!isDockerMachineRunning(dockerMachineName))
childProcess.execSync('docker-machine start ' + dockerMachineName);

const dockerEnv = getDockerEnv(dockerMachineName);

assignIn(process.env, dockerEnv);
}

function ensureDockerEnvironment () {
if (isDockerDaemonRunning())
return;

if (!process.env['DOCKER_HOST']) {
try {
startDocker();
}
catch (e) {
throw new Error('Unable to initialize Docker environment. Use Docker terminal to run this task.\n' +
e.stack);
}
}
}

gulp.task('docker-build', done => {
childProcess.execSync('npm pack', { env: process.env }).toString();

Expand All @@ -930,26 +865,40 @@ gulp.task('docker-build', done => {
done();
});

gulp.step('docker-test-run', done => {
gulp.step('docker-server-test-run', done => {
ensureDockerEnvironment();

childProcess.execSync(`docker build --no-cache --build-arg tag=${packageInfo.version} -t docker-server-tests -f test/docker/Dockerfile .`,
{ stdio: 'inherit', env: process.env });

childProcess.execSync('docker image rm docker-server-tests', { stdio: 'inherit', env: process.env });

done();
});

gulp.step('docker-functional-test-run', () => {
ensureDockerEnvironment();

return runFunctionalTestInDocker(PUBLISH_REPO, packageInfo);
});

gulp.step('docker-publish-run', done => {
PUBLISH_TAGS.forEach(tag => {
childProcess.execSync(`docker push ${PUBLISH_REPO}:${tag}`, { stdio: 'inherit', env: process.env });
const PUBLISH_COMMANDS = [
'docker push',
'docker pull',
'docker image rm'
];

childProcess.execSync(`docker pull ${PUBLISH_REPO}:${tag}`, { stdio: 'inherit', env: process.env });
PUBLISH_TAGS.forEach(tag => {
PUBLISH_COMMANDS.forEach(command => {
childProcess.execSync(`${command} ${PUBLISH_REPO}:${tag}`, { stdio: 'inherit', env: process.env });
});
});

done();
});

gulp.task('docker-test', gulp.series('docker-build', 'docker-test-run'));
gulp.task('docker-test', gulp.series('docker-build', 'docker-server-test-run', 'docker-functional-test-run'));

gulp.task('docker-test-travis', gulp.series('build', 'docker-test'));

Expand Down
18 changes: 18 additions & 0 deletions gulp/docker/ensure-docker-environment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const isDockerDaemonRunning = require('./is-docker-daemon-running');
const startDocker = require('./start-docker');

module.exports = function ensureDockerEnvironment () {
if (isDockerDaemonRunning())
return;

if (!process.env['DOCKER_HOST']) {
try {
startDocker();
}
catch (e) {
throw new Error('Unable to initialize Docker environment. Use Docker terminal to run this task.\n' +
e.stack);
}
}
};

19 changes: 19 additions & 0 deletions gulp/docker/get-docker-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const childProcess = require('child_process');

module.exports = function getDockerEnv (machineName) {
return childProcess
.execSync('docker-machine env --shell bash ' + machineName)
.toString()
.split('\n')
.map(line => {
return line.match(/export\s*(.*)="(.*)"$/);
})
.filter(match => {
return !!match;
})
.reduce((env, match) => {
env[match[1]] = match[2];
return env;
}, {});
};

12 changes: 12 additions & 0 deletions gulp/docker/get-publish-info.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const getPublishTags = require('./get-publish-tags');

module.exports = function getPublishInfo (packageInfo) {
const PUBLISH_TAGS = getPublishTags(packageInfo);
const PUBLISH_REPO = 'testcafe/testcafe';

return {
PUBLISH_TAGS,
PUBLISH_REPO
};
};

File renamed without changes.
11 changes: 11 additions & 0 deletions gulp/docker/is-docker-machine-exists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const childProcess = require('child_process');

module.exports = function isDockerMachineExist (machineName) {
try {
childProcess.execSync('docker-machine status ' + machineName);
return true;
}
catch (e) {
return !e.message.match(/Host does not exist/);
}
};
10 changes: 10 additions & 0 deletions gulp/docker/is-docker-machine-running.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const childProcess = require('child_process');

module.exports = function isDockerMachineRunning (machineName) {
try {
return childProcess.execSync('docker-machine status ' + machineName).toString().match(/Running/);
}
catch (e) {
return false;
}
};
48 changes: 48 additions & 0 deletions gulp/docker/run-functional-test-via-command-line.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const tmp = require('tmp');
const copy = require('recursive-copy');
const path = require('path');
const childProcess = require('child_process');
const os = require('os');
const osFamily = require('os-family');

function correctPathOnWindows (tmpDir) {
const parsedPath = path.parse(tmpDir);
const newRoot = '//' + parsedPath.root.replace(':', '');
const modifiedTmpDir = tmpDir.replace(parsedPath.root, newRoot);
const modifiedTmpDirSegments = modifiedTmpDir.split(path.sep);
const userNameTmpDirPart = modifiedTmpDirSegments[2];

if (userNameTmpDirPart.includes('~')) {
// NOTE: Docker cannot resolve paths with `~` symbol on Windows operation system
const userInfo = os.userInfo();

modifiedTmpDirSegments[2] = userInfo.username;
}

return modifiedTmpDirSegments.join(path.posix.sep);
}

function setAccessPrivileges (dir) {
const cmd = `chmod -R 777 ${dir}`;

childProcess.execSync(cmd, { stdio: 'inherit', env: process.env });
}

module.exports = function runFunctionalTestViaCommandLine (publishRepository, packageInfo) {
tmp.setGracefulCleanup();

const tmpDir = tmp.dirSync();
const testsDir = path.join(__dirname, '../../test/docker/testcafe-fixtures');

return copy(testsDir, tmpDir.name).then(() => {
const resultTmpDirPath = osFamily.win ? correctPathOnWindows(tmpDir.name) : tmpDir.name;

if (osFamily.linux)
setAccessPrivileges(resultTmpDirPath);

const cmd = `docker run -i -v ${resultTmpDirPath}:/tests -w /tests ${publishRepository}:${packageInfo.version} chromium:headless basic-test.js`;

childProcess.execSync(cmd, { stdio: 'inherit', env: process.env });
});
};

20 changes: 20 additions & 0 deletions gulp/docker/start-docker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const childProcess = require('child_process');
const isDockerMachineExist = require('./is-docker-machine-exists');
const isDockerMachineRunning = require('./is-docker-daemon-running');
const getDockerEnv = require('./get-docker-env');
const { assignIn } = require('lodash');

module.exports = function startDocker () {
const dockerMachineName = process.env['DOCKER_MACHINE_NAME'] || 'default';

if (!isDockerMachineExist(dockerMachineName))
childProcess.execSync('docker-machine create -d virtualbox ' + dockerMachineName);

if (!isDockerMachineRunning(dockerMachineName))
childProcess.execSync('docker-machine start ' + dockerMachineName);

const dockerEnv = getDockerEnv(dockerMachineName);

assignIn(process.env, dockerEnv);
};

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "testcafe",
"description": "Automated browser testing for the modern web development stack.",
"license": "MIT",
"version": "1.10.0-rc.2",
"version": "1.10.0-rc.3",
"author": {
"name": "Developer Express Inc.",
"url": "https://www.devexpress.com/"
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/babel/format-babel-produced-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function (code: string): string {
return code.replace(/ +/g, ' ')
.replace(/\r?\n|\r/g, '')
.replace(/[{,;}] /g, str => {
return str.trim();
});
}
6 changes: 5 additions & 1 deletion src/compiler/babel/load-libs.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ function getModuleResolverOpts () {
};
}


function getTransformRuntimeOpts () {
// NOTE: We are forced to import helpers to each compiled file
// because of '@babel/plugin-transform-runtime' plugin cannot correctly resolve path
// to the helpers from the '@babel/runtime' module.
return {
absoluteRuntime: true
'helpers': false
};
}

Expand Down
15 changes: 11 additions & 4 deletions src/compiler/compile-client-function.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { noop } from 'lodash';
import loadBabelLibs from './babel/load-libs';
import { ClientFunctionAPIError } from '../errors/runtime';
import { RUNTIME_ERRORS } from '../errors/types';
import formatBabelProducedCode from './babel/format-babel-produced-code';

const ANONYMOUS_FN_RE = /^function\s*\*?\s*\(/;
const ES6_OBJ_METHOD_NAME_RE = /^(\S+?)\s*\(/;
const USE_STRICT_RE = /^('|")use strict('|");?/;
const TRAILING_SEMICOLON_RE = /;\s*$/;
const REGENERATOR_FOOTPRINTS_RE = /(_index\d+\.default|_regenerator\d+\.default|regeneratorRuntime)\.wrap\(function _callee\$\(_context\)/;
const ASYNC_TO_GENERATOR_OUTPUT_CODE = asyncToGenerator(noop).toString();
const ASYNC_TO_GENERATOR_OUTPUT_CODE = formatBabelProducedCode(asyncToGenerator(noop).toString());


function getBabelOptions () {
const { presetEnvForClientFunction, transformForOfAsArray } = loadBabelLibs();
Expand Down Expand Up @@ -58,8 +60,14 @@ function makeFnCodeSuitableForParsing (fnCode) {
return fnCode;
}

function containsAsyncToGeneratorOutputCode (code) {
const formattedCode = formatBabelProducedCode(code);

return formattedCode.includes(ASYNC_TO_GENERATOR_OUTPUT_CODE);
}

export default function compileClientFunction (fnCode, dependencies, instantiationCallsiteName, compilationCallsiteName) {
if (fnCode === ASYNC_TO_GENERATOR_OUTPUT_CODE)
if (containsAsyncToGeneratorOutputCode(fnCode))
throw new ClientFunctionAPIError(compilationCallsiteName, instantiationCallsiteName, RUNTIME_ERRORS.regeneratorInClientFunctionCode);

fnCode = makeFnCodeSuitableForParsing(fnCode);
Expand All @@ -68,8 +76,7 @@ export default function compileClientFunction (fnCode, dependencies, instantiati
fnCode = downgradeES(fnCode);
fnCode = hammerhead.processScript(fnCode, false);

// NOTE: check compiled code for regenerator injection: we have either generator
// recompiled in Node.js 4+ for client or async function declared in function code.
// NOTE: check compiled code for regenerator injection
if (REGENERATOR_FOOTPRINTS_RE.test(fnCode))
throw new ClientFunctionAPIError(compilationCallsiteName, instantiationCallsiteName, RUNTIME_ERRORS.regeneratorInClientFunctionCode);

Expand Down
7 changes: 1 addition & 6 deletions src/compiler/test-file/formats/es-next/compiler.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import loadBabelLibs from '../../../babel/load-libs';
import APIBasedTestFileCompilerBase from '../../api-based';

const FLOW_MARKER_RE = /^\s*\/\/\s*@flow\s*\n|^\s*\/\*\s*@flow\s*\*\//;

function isFlowCode (code) {
return FLOW_MARKER_RE.test(code);
}
import isFlowCode from './is-flow-code';

export default class ESNextTestFileCompiler extends APIBasedTestFileCompilerBase {
static getBabelOptions (filename, code) {
Expand Down
5 changes: 5 additions & 0 deletions src/compiler/test-file/formats/es-next/is-flow-code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const FLOW_MARKER_RE = /^\s*\/\/\s*@flow\s*\n|^\s*\/\*\s*@flow\s*\*\//;

export default function (code: string): boolean {
return FLOW_MARKER_RE.test(code);
}
6 changes: 6 additions & 0 deletions test/docker/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"globals": {
"test": true,
"fixture": true
}
}
Loading