1 | "use strict";
|
2 |
|
3 | const MemoryFS = require('memory-fs');
|
4 | const path = require('path');
|
5 | const utils = require('../utils');
|
6 | const Promise = require('bluebird');
|
7 | const _ = require('lodash');
|
8 | const lazyRequire = require('../commons/lazyRequire');
|
9 | const { AbortError } = require('../commons/AbortError');
|
10 |
|
11 | const { isEqual, cloneDeep } = require('lodash');
|
12 |
|
13 |
|
14 | const state = {
|
15 | compiler: null,
|
16 | webpackConfig: null
|
17 | };
|
18 |
|
19 | exports.buildCodeTests = async function buildCodeTestsGuarded(
|
20 | files,
|
21 | webpackConfig,
|
22 | runnerOptionsToMaybeCopyToTestEnvironment,
|
23 | fileSystem,
|
24 | optionalAbortSignal
|
25 | ) {
|
26 | try {
|
27 | return await buildCodeTests(files, webpackConfig, runnerOptionsToMaybeCopyToTestEnvironment, fileSystem, optionalAbortSignal);
|
28 | } catch (e) {
|
29 | if (e && e.code === 'ENOENT') {
|
30 |
|
31 |
|
32 | state.compiler = null;
|
33 | state.webpackConfig = null;
|
34 | fileSystem.data = {};
|
35 | return await buildCodeTests(files, webpackConfig, runnerOptionsToMaybeCopyToTestEnvironment, fileSystem, optionalAbortSignal);
|
36 | }
|
37 | throw e;
|
38 | }
|
39 | }
|
40 |
|
41 | async function buildCodeTests(files, webpackConfig = {mode: 'development'}, runnerOptionsToMaybeCopyToTestEnvironment, fileSystem, optionalAbortSignal) {
|
42 |
|
43 | const webpack = await lazyRequire('webpack');
|
44 |
|
45 | const suite = {};
|
46 | const webpackConfigBeforeOurChanges = cloneDeep(webpackConfig);
|
47 |
|
48 | webpackConfig.externals = {
|
49 | 'testim': '__testim',
|
50 |
|
51 | };
|
52 | webpackConfig.devtool = webpackConfig.devtool || 'inline-source-map';
|
53 |
|
54 | webpackConfig.plugins = webpackConfig.plugins || [];
|
55 |
|
56 | webpackConfig.plugins.push(new webpack.DefinePlugin(getEnvironmentVariables(runnerOptionsToMaybeCopyToTestEnvironment)))
|
57 | webpackConfig.plugins.push(new webpack.DefinePlugin({
|
58 | 'process.argv': JSON.stringify(process.argv)
|
59 | }));
|
60 | files = files.map(f => path.resolve(f));
|
61 |
|
62 | const fileHashes = files.map(x => utils.guid(30));
|
63 |
|
64 | webpackConfig.optimization = { minimize: false };
|
65 |
|
66 | webpackConfig.entry = _.fromPairs(_.zip(files, fileHashes).map(([filename, hash]) => {
|
67 | return [hash, filename];
|
68 | }));
|
69 | webpackConfig.output = Object.assign({
|
70 | devtoolModuleFilenameTemplate: (info) => `file:///${info.absoluteResourcePath}`,
|
71 | filename: '[name].bundle.js',
|
72 | }, webpackConfig.output);
|
73 |
|
74 |
|
75 | let compiler;
|
76 |
|
77 | if (fileSystem) {
|
78 |
|
79 | if (isEqual(state.webpackConfig, webpackConfigBeforeOurChanges) && state.compiler) {
|
80 |
|
81 | compiler = state.compiler;
|
82 | } else {
|
83 | state.webpackConfig = webpackConfigBeforeOurChanges;
|
84 | state.compiler = webpack(webpackConfig);
|
85 | compiler = state.compiler;
|
86 | }
|
87 | } else {
|
88 | compiler = webpack(webpackConfig);
|
89 | }
|
90 |
|
91 | const mfs = fileSystem || new MemoryFS();
|
92 | compiler.outputFileSystem = mfs;
|
93 |
|
94 |
|
95 | const abortSignalPromise = Promise.fromCallback(cb => {
|
96 | if (optionalAbortSignal) {
|
97 | optionalAbortSignal.addEventListener("abort", () => {
|
98 | cb(new AbortError());
|
99 | });
|
100 | }
|
101 | });
|
102 |
|
103 |
|
104 | try {
|
105 | const stats = await Promise.race([Promise.fromCallback(cb => compiler.run(cb)), abortSignalPromise]);
|
106 | if (stats.hasErrors()) {
|
107 | throw new Error(stats.toJson().errors);
|
108 | }
|
109 | } catch (e) {
|
110 | const {ArgError} = require('../errors');
|
111 |
|
112 | const cantFindFile = e.message.match(/Entry module not found: Error: Can't resolve '(.*)'/);
|
113 | if (cantFindFile && cantFindFile.length === 2) {
|
114 | if (webpackConfig.output && webpackConfig.output.library === 'tdk') {
|
115 | throw new ArgError(`Could not open dev-kit functions file in ${cantFindFile[1]}`);
|
116 | }
|
117 | throw new ArgError(`Can't find test files in: '${cantFindFile[1]}'`);
|
118 | }
|
119 |
|
120 | throw new ArgError("Compilation Webpack Error in tests: " + e.message);
|
121 | }
|
122 |
|
123 | const fileResults = files.map((file, i) => ({code: mfs.readFileSync(path.resolve('./dist', `${fileHashes[i]}.bundle.js`)), name: file }));
|
124 |
|
125 | suite.tests = [fileResults.map(({code, name}) => ({
|
126 | code: code.toString(),
|
127 | baseUrl: "",
|
128 | name: path.resolve(name),
|
129 | testConfig: {},
|
130 | testConfigId: null,
|
131 | testId: utils.guid(),
|
132 | resultId: utils.guid(),
|
133 | isTestsContainer: true
|
134 | }))];
|
135 | suite.runName = 'Testim Dev Kit Run ' + (new Date().toLocaleString());
|
136 | return suite;
|
137 | }
|
138 |
|
139 |
|
140 | function getEnvironmentVariables(runnerOptionsToMaybeCopyToTestEnvironment) {
|
141 |
|
142 | let fromEnvironment = _.fromPairs(
|
143 | Object.keys(process.env)
|
144 | .filter(key => /^TDK_/i.test(key) || key === 'BASE_URL')
|
145 | .map(key => [key, process.env[key]])
|
146 | );
|
147 |
|
148 | let fromConfig = {
|
149 | 'BASE_URL': runnerOptionsToMaybeCopyToTestEnvironment.baseUrl
|
150 | };
|
151 |
|
152 | return {
|
153 | 'process.env': stringifyValues({ ...fromConfig, ...fromEnvironment})
|
154 | };
|
155 | }
|
156 | function stringifyValues(object) {
|
157 | return _.fromPairs(Object.entries(object).map(([key, value]) => [key, JSON.stringify(value)]));
|
158 | }
|