1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.module = void 0;
|
4 | const tslib_1 = require("tslib");
|
5 | const inversify_1 = require("inversify");
|
6 | const ymir_1 = require("@fimbul/ymir");
|
7 | const base_1 = require("./base");
|
8 | const cached_file_system_1 = require("../services/cached-file-system");
|
9 | const baseline_1 = require("../baseline");
|
10 | const path = require("path");
|
11 | const chalk = require("chalk");
|
12 | const utils_1 = require("../utils");
|
13 | const glob = require("glob");
|
14 | const semver_1 = require("semver");
|
15 | const runner_1 = require("../runner");
|
16 | const ts = require("typescript");
|
17 | const diff = require("diff");
|
18 | const argparse_1 = require("../argparse");
|
19 | const optparse_1 = require("../optparse");
|
20 | const TEST_OPTION_SPEC = {
|
21 | ...argparse_1.GLOBAL_OPTIONS_SPEC,
|
22 | fix: optparse_1.OptionParser.Transform.noDefault(argparse_1.GLOBAL_OPTIONS_SPEC.fix),
|
23 | typescriptVersion: optparse_1.OptionParser.Factory.parsePrimitive('string'),
|
24 | };
|
25 | let FakeDirectoryService = class FakeDirectoryService {
|
26 | constructor(realDirectorySerivce) {
|
27 | this.realDirectorySerivce = realDirectorySerivce;
|
28 | }
|
29 | getCurrentDirectory() {
|
30 | return this.cwd;
|
31 | }
|
32 | getRealCurrentDirectory() {
|
33 | return this.realDirectorySerivce.getCurrentDirectory();
|
34 | }
|
35 | };
|
36 | FakeDirectoryService = tslib_1.__decorate([
|
37 | inversify_1.injectable(),
|
38 | tslib_1.__metadata("design:paramtypes", [ymir_1.DirectoryService])
|
39 | ], FakeDirectoryService);
|
40 | let TestCommandRunner = class TestCommandRunner extends base_1.AbstractCommandRunner {
|
41 | constructor(runner, fs, logger, directoryService) {
|
42 | super();
|
43 | this.runner = runner;
|
44 | this.fs = fs;
|
45 | this.logger = logger;
|
46 | this.directoryService = directoryService;
|
47 | }
|
48 | run(options) {
|
49 | const currentTypescriptVersion = getNormalizedTypescriptVersion();
|
50 | const basedir = this.directoryService.getRealCurrentDirectory();
|
51 | let baselineDir;
|
52 | let root;
|
53 | let baselinesSeen;
|
54 | let success = true;
|
55 | const host = {
|
56 | checkResult: (file, kind, summary) => {
|
57 | const relative = path.relative(root, file);
|
58 | if (relative.startsWith('..' + path.sep))
|
59 | throw new ymir_1.ConfigurationError(`Testing file '${file}' outside of '${root}'.`);
|
60 | const actual = kind === "fix" ? summary.content : baseline_1.createBaseline(summary);
|
61 | const baselineFile = `${path.resolve(baselineDir, relative)}.${kind}`;
|
62 | const end = (pass, text, baselineDiff) => {
|
63 | this.logger.log(` ${chalk.grey.dim(path.relative(basedir, baselineFile))} ${chalk[pass ? 'green' : 'red'](text)}`);
|
64 | if (pass)
|
65 | return true;
|
66 | if (baselineDiff !== undefined)
|
67 | this.logger.log(baselineDiff);
|
68 | success = false;
|
69 | return !options.bail;
|
70 | };
|
71 | if (kind === "fix" && summary.fixes === 0) {
|
72 | if (!this.fs.isFile(baselineFile))
|
73 | return true;
|
74 | if (options.updateBaselines) {
|
75 | this.fs.remove(baselineFile);
|
76 | return end(true, 'REMOVED');
|
77 | }
|
78 | baselinesSeen.push(utils_1.unixifyPath(baselineFile));
|
79 | return end(false, 'EXISTS');
|
80 | }
|
81 | baselinesSeen.push(utils_1.unixifyPath(baselineFile));
|
82 | let expected;
|
83 | try {
|
84 | expected = this.fs.readFile(baselineFile);
|
85 | }
|
86 | catch {
|
87 | if (!options.updateBaselines)
|
88 | return end(false, 'MISSING');
|
89 | this.fs.createDirectory(path.dirname(baselineFile));
|
90 | this.fs.writeFile(baselineFile, actual);
|
91 | return end(true, 'CREATED');
|
92 | }
|
93 | if (expected === actual)
|
94 | return end(true, 'PASSED');
|
95 | if (options.updateBaselines) {
|
96 | this.fs.writeFile(baselineFile, actual);
|
97 | return end(true, 'UPDATED');
|
98 | }
|
99 | return end(false, 'FAILED', createBaselineDiff(actual, expected));
|
100 | },
|
101 | };
|
102 | const globOptions = {
|
103 | absolute: true,
|
104 | cache: {},
|
105 | nodir: true,
|
106 | realpathCache: {},
|
107 | statCache: {},
|
108 | symlinks: {},
|
109 | cwd: basedir,
|
110 | };
|
111 | for (const pattern of options.files) {
|
112 | for (const testcase of glob.sync(pattern, globOptions)) {
|
113 | const { typescriptVersion, ...testConfig } = optparse_1.OptionParser.parse(require(testcase), TEST_OPTION_SPEC, { validate: true, context: testcase, exhaustive: true });
|
114 | if (typescriptVersion !== undefined && !semver_1.satisfies(currentTypescriptVersion, typescriptVersion)) {
|
115 | this.logger.log(`${path.relative(basedir, testcase)} ${chalk.yellow(`SKIPPED, requires TypeScript ${typescriptVersion}`)}`);
|
116 | continue;
|
117 | }
|
118 | root = path.dirname(testcase);
|
119 | baselineDir = buildBaselineDirectoryName(basedir, 'baselines', testcase);
|
120 | this.logger.log(path.relative(basedir, testcase));
|
121 | this.directoryService.cwd = root;
|
122 | baselinesSeen = [];
|
123 | if (!this.test(testConfig, host))
|
124 | return false;
|
125 | if (options.exact) {
|
126 | const remainingGlobOptions = { ...globOptions, cwd: baselineDir, ignore: baselinesSeen };
|
127 | for (const unchecked of glob.sync('**', remainingGlobOptions)) {
|
128 | if (options.updateBaselines) {
|
129 | this.fs.remove(unchecked);
|
130 | this.logger.log(` ${chalk.grey.dim(path.relative(basedir, unchecked))} ${chalk.green('REMOVED')}`);
|
131 | }
|
132 | else {
|
133 | this.logger.log(` ${chalk.grey.dim(path.relative(basedir, unchecked))} ${chalk.red('UNCHECKED')}`);
|
134 | if (options.bail)
|
135 | return false;
|
136 | success = false;
|
137 | }
|
138 | }
|
139 | }
|
140 | }
|
141 | }
|
142 | return success;
|
143 | }
|
144 | test(config, host) {
|
145 | const lintOptions = { ...config, fix: false };
|
146 | const lintResult = Array.from(this.runner.lintCollection(lintOptions));
|
147 | let containsFixes = false;
|
148 | for (const [fileName, summary] of lintResult) {
|
149 | if (!host.checkResult(fileName, "lint" , summary))
|
150 | return false;
|
151 | containsFixes = containsFixes || summary.findings.some(isFixable);
|
152 | }
|
153 | if (config.fix || config.fix === undefined) {
|
154 | lintOptions.fix = config.fix || true;
|
155 | const fixResult = containsFixes ? this.runner.lintCollection(lintOptions) : lintResult;
|
156 | for (const [fileName, summary] of fixResult)
|
157 | if (!host.checkResult(fileName, "fix" , summary))
|
158 | return false;
|
159 | }
|
160 | return true;
|
161 | }
|
162 | };
|
163 | TestCommandRunner = tslib_1.__decorate([
|
164 | inversify_1.injectable(),
|
165 | tslib_1.__metadata("design:paramtypes", [runner_1.Runner,
|
166 | cached_file_system_1.CachedFileSystem,
|
167 | ymir_1.MessageHandler,
|
168 | FakeDirectoryService])
|
169 | ], TestCommandRunner);
|
170 | function buildBaselineDirectoryName(basedir, baselineDir, testcase) {
|
171 | const parts = path.relative(basedir, path.dirname(testcase)).split(path.sep);
|
172 | if (/^(__)?tests?(__)?$/.test(parts[0])) {
|
173 | parts[0] = baselineDir;
|
174 | }
|
175 | else {
|
176 | parts.unshift(baselineDir);
|
177 | }
|
178 | return path.resolve(basedir, parts.join(path.sep), getTestName(path.basename(testcase)));
|
179 | }
|
180 | function getTestName(basename) {
|
181 | let ext = path.extname(basename);
|
182 | basename = basename.slice(0, -ext.length);
|
183 | ext = path.extname(basename);
|
184 | if (ext === '')
|
185 | return 'default';
|
186 | return basename.slice(0, -ext.length);
|
187 | }
|
188 |
|
189 | function getNormalizedTypescriptVersion() {
|
190 | const v = new semver_1.SemVer(ts.version);
|
191 | return new semver_1.SemVer(`${v.major}.${v.minor}.${v.patch}`);
|
192 | }
|
193 | function isFixable(finding) {
|
194 | return finding.fix !== undefined;
|
195 | }
|
196 | function createBaselineDiff(actual, expected) {
|
197 | const result = [
|
198 | chalk.red('Expected'),
|
199 | chalk.green('Actual'),
|
200 | ];
|
201 | const lines = diff.createPatch('', expected, actual, '', '').split(/\n(?!\\)/g).slice(4);
|
202 | for (let line of lines) {
|
203 | switch (line[0]) {
|
204 | case '@':
|
205 | line = chalk.blueBright(line);
|
206 | break;
|
207 | case '+':
|
208 | line = chalk.green('+' + prettyLine(line.substr(1)));
|
209 | break;
|
210 | case '-':
|
211 | line = chalk.red('-' + prettyLine(line.substr(1)));
|
212 | }
|
213 | result.push(line);
|
214 | }
|
215 | return result.join('\n');
|
216 | }
|
217 | function prettyLine(line) {
|
218 | return line
|
219 | .replace(/\t/g, '\u2409')
|
220 | .replace(/\r$/, '\u240d')
|
221 | .replace(/^\uFEFF/, '<BOM>');
|
222 | }
|
223 | exports.module = new inversify_1.ContainerModule((bind) => {
|
224 | bind(FakeDirectoryService).toSelf().inSingletonScope();
|
225 | bind(ymir_1.DirectoryService).toDynamicValue((context) => {
|
226 | return context.container.get(FakeDirectoryService);
|
227 | }).inSingletonScope().when((request) => {
|
228 | return request.parentRequest == undefined || request.parentRequest.target.serviceIdentifier !== FakeDirectoryService;
|
229 | });
|
230 | bind(ymir_1.DirectoryService).toDynamicValue(({ container }) => {
|
231 | if (container.parent && container.parent.isBound(ymir_1.DirectoryService))
|
232 | return container.parent.get(ymir_1.DirectoryService);
|
233 | return {
|
234 | getCurrentDirectory() {
|
235 | return process.cwd();
|
236 | },
|
237 | };
|
238 | }).inSingletonScope().whenInjectedInto(FakeDirectoryService);
|
239 | bind(base_1.AbstractCommandRunner).to(TestCommandRunner);
|
240 | });
|
241 |
|
\ | No newline at end of file |