13af6ab5fSopenharmony_ci/* 23af6ab5fSopenharmony_ci * Copyright (c) 2022-2024 Huawei Device Co., Ltd. 33af6ab5fSopenharmony_ci * Licensed under the Apache License, Version 2.0 (the "License"); 43af6ab5fSopenharmony_ci * you may not use this file except in compliance with the License. 53af6ab5fSopenharmony_ci * You may obtain a copy of the License at 63af6ab5fSopenharmony_ci * 73af6ab5fSopenharmony_ci * http://www.apache.org/licenses/LICENSE-2.0 83af6ab5fSopenharmony_ci * 93af6ab5fSopenharmony_ci * Unless required by applicable law or agreed to in writing, software 103af6ab5fSopenharmony_ci * distributed under the License is distributed on an "AS IS" BASIS, 113af6ab5fSopenharmony_ci * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 123af6ab5fSopenharmony_ci * See the License for the specific language governing permissions and 133af6ab5fSopenharmony_ci * limitations under the License. 143af6ab5fSopenharmony_ci */ 153af6ab5fSopenharmony_ci 163af6ab5fSopenharmony_ciimport { Logger } from '../lib/Logger'; 173af6ab5fSopenharmony_ciimport { LoggerImpl } from './LoggerImpl'; 183af6ab5fSopenharmony_ciLogger.init(new LoggerImpl()); 193af6ab5fSopenharmony_ci 203af6ab5fSopenharmony_ciimport * as fs from 'node:fs'; 213af6ab5fSopenharmony_ciimport * as path from 'node:path'; 223af6ab5fSopenharmony_ciimport * as ts from 'typescript'; 233af6ab5fSopenharmony_ciimport type { CommandLineOptions } from '../lib/CommandLineOptions'; 243af6ab5fSopenharmony_ciimport { lint } from '../lib/LinterRunner'; 253af6ab5fSopenharmony_ciimport { TypeScriptLinter } from '../lib/TypeScriptLinter'; 263af6ab5fSopenharmony_ciimport type { Autofix } from '../lib/autofixes/Autofixer'; 273af6ab5fSopenharmony_ciimport { parseCommandLine } from './CommandLineParser'; 283af6ab5fSopenharmony_ciimport { compileLintOptions } from './Compiler'; 293af6ab5fSopenharmony_ciimport { getEtsLoaderPath } from './LinterCLI'; 303af6ab5fSopenharmony_ciimport { ProblemSeverity } from '../lib/ProblemSeverity'; 313af6ab5fSopenharmony_ci 323af6ab5fSopenharmony_ciconst TEST_DIR = 'test'; 333af6ab5fSopenharmony_ciconst TAB = ' '; 343af6ab5fSopenharmony_ci 353af6ab5fSopenharmony_ciinterface TestNodeInfo { 363af6ab5fSopenharmony_ci line: number; 373af6ab5fSopenharmony_ci column: number; 383af6ab5fSopenharmony_ci endLine: number; 393af6ab5fSopenharmony_ci endColumn: number; 403af6ab5fSopenharmony_ci problem: string; 413af6ab5fSopenharmony_ci autofix?: Autofix[]; 423af6ab5fSopenharmony_ci suggest?: string; 433af6ab5fSopenharmony_ci rule?: string; 443af6ab5fSopenharmony_ci severity?: string; 453af6ab5fSopenharmony_ci} 463af6ab5fSopenharmony_ci 473af6ab5fSopenharmony_cienum Mode { 483af6ab5fSopenharmony_ci DEFAULT, 493af6ab5fSopenharmony_ci AUTOFIX 503af6ab5fSopenharmony_ci} 513af6ab5fSopenharmony_ci 523af6ab5fSopenharmony_ciconst RESULT_EXT: string[] = []; 533af6ab5fSopenharmony_ciRESULT_EXT[Mode.DEFAULT] = '.json'; 543af6ab5fSopenharmony_ciRESULT_EXT[Mode.AUTOFIX] = '.autofix.json'; 553af6ab5fSopenharmony_ciconst AUTOFIX_SKIP_EXT = '.autofix.skip'; 563af6ab5fSopenharmony_ciconst ARGS_CONFIG_EXT = '.args.json'; 573af6ab5fSopenharmony_ciconst DIFF_EXT = '.diff'; 583af6ab5fSopenharmony_ciconst testExtensionSts = '.sts'; 593af6ab5fSopenharmony_ciconst testExtensionDSts = '.d.sts'; 603af6ab5fSopenharmony_ci 613af6ab5fSopenharmony_cifunction runTests(testDirs: string[]): number { 623af6ab5fSopenharmony_ci 633af6ab5fSopenharmony_ci /* 643af6ab5fSopenharmony_ci * Set the IDE mode manually to enable storing information 653af6ab5fSopenharmony_ci * about found bad nodes and also disable the log output. 663af6ab5fSopenharmony_ci */ 673af6ab5fSopenharmony_ci TypeScriptLinter.ideMode = true; 683af6ab5fSopenharmony_ci TypeScriptLinter.testMode = true; 693af6ab5fSopenharmony_ci 703af6ab5fSopenharmony_ci let hasComparisonFailures = false; 713af6ab5fSopenharmony_ci let passed = 0; 723af6ab5fSopenharmony_ci let failed = 0; 733af6ab5fSopenharmony_ci // Get tests from test directory 743af6ab5fSopenharmony_ci if (!testDirs?.length) { 753af6ab5fSopenharmony_ci // eslint-disable-next-line no-param-reassign 763af6ab5fSopenharmony_ci testDirs = [TEST_DIR]; 773af6ab5fSopenharmony_ci } 783af6ab5fSopenharmony_ci for (const testDir of testDirs) { 793af6ab5fSopenharmony_ci const testFiles: string[] = fs.readdirSync(testDir).filter((x) => { 803af6ab5fSopenharmony_ci return ( 813af6ab5fSopenharmony_ci x.trimEnd().endsWith(ts.Extension.Ts) && !x.trimEnd().endsWith(ts.Extension.Dts) || 823af6ab5fSopenharmony_ci x.trimEnd().endsWith(ts.Extension.Tsx) || 833af6ab5fSopenharmony_ci x.trimEnd().endsWith(ts.Extension.Ets) || 843af6ab5fSopenharmony_ci x.trimEnd().endsWith(testExtensionSts) && !x.trimEnd().endsWith(testExtensionDSts) 853af6ab5fSopenharmony_ci ); 863af6ab5fSopenharmony_ci }); 873af6ab5fSopenharmony_ci Logger.info(`\nProcessing "${testDir}" directory:\n`); 883af6ab5fSopenharmony_ci // Run each test in Default and Autofix mode: 893af6ab5fSopenharmony_ci [passed, failed, hasComparisonFailures] = runTestFiles(testFiles, testDir); 903af6ab5fSopenharmony_ci } 913af6ab5fSopenharmony_ci Logger.info(`\nSUMMARY: ${passed + failed} total, ${passed} passed or skipped, ${failed} failed.`); 923af6ab5fSopenharmony_ci Logger.info(failed > 0 ? '\nTEST FAILED' : '\nTEST SUCCESSFUL'); 933af6ab5fSopenharmony_ci process.exit(hasComparisonFailures ? -1 : 0); 943af6ab5fSopenharmony_ci} 953af6ab5fSopenharmony_ci 963af6ab5fSopenharmony_cifunction runTestFiles(testFiles: string[], testDir: string): [number, number, boolean] { 973af6ab5fSopenharmony_ci let hasComparisonFailures = false; 983af6ab5fSopenharmony_ci let passed = 0; 993af6ab5fSopenharmony_ci let failed = 0; 1003af6ab5fSopenharmony_ci for (const testFile of testFiles) { 1013af6ab5fSopenharmony_ci let renamed = false; 1023af6ab5fSopenharmony_ci let tsName = testFile; 1033af6ab5fSopenharmony_ci if (testFile.includes(testExtensionSts)) { 1043af6ab5fSopenharmony_ci renamed = true; 1053af6ab5fSopenharmony_ci tsName = testFile.replace(testExtensionSts, ts.Extension.Ts); 1063af6ab5fSopenharmony_ci fs.renameSync(path.join(testDir, testFile), path.join(testDir, tsName)); 1073af6ab5fSopenharmony_ci } 1083af6ab5fSopenharmony_ci if (runTest(testDir, tsName, Mode.DEFAULT)) { 1093af6ab5fSopenharmony_ci failed++; 1103af6ab5fSopenharmony_ci hasComparisonFailures = true; 1113af6ab5fSopenharmony_ci } else { 1123af6ab5fSopenharmony_ci passed++; 1133af6ab5fSopenharmony_ci } 1143af6ab5fSopenharmony_ci if (runTest(testDir, tsName, Mode.AUTOFIX)) { 1153af6ab5fSopenharmony_ci failed++; 1163af6ab5fSopenharmony_ci hasComparisonFailures = true; 1173af6ab5fSopenharmony_ci } else { 1183af6ab5fSopenharmony_ci passed++; 1193af6ab5fSopenharmony_ci } 1203af6ab5fSopenharmony_ci if (renamed) { 1213af6ab5fSopenharmony_ci fs.renameSync(path.join(testDir, tsName), path.join(testDir, testFile)); 1223af6ab5fSopenharmony_ci } 1233af6ab5fSopenharmony_ci } 1243af6ab5fSopenharmony_ci return [passed, failed, hasComparisonFailures]; 1253af6ab5fSopenharmony_ci} 1263af6ab5fSopenharmony_ci 1273af6ab5fSopenharmony_cifunction parseArgs(testDir: string, testFile: string, mode: Mode): CommandLineOptions { 1283af6ab5fSopenharmony_ci // Configure test parameters and run linter. 1293af6ab5fSopenharmony_ci const cmdArgs: string[] = [path.join(testDir, testFile)]; 1303af6ab5fSopenharmony_ci const argsFileName = path.join(testDir, testFile + ARGS_CONFIG_EXT); 1313af6ab5fSopenharmony_ci 1323af6ab5fSopenharmony_ci if (fs.existsSync(argsFileName)) { 1333af6ab5fSopenharmony_ci const data = fs.readFileSync(argsFileName).toString(); 1343af6ab5fSopenharmony_ci const args = JSON.parse(data); 1353af6ab5fSopenharmony_ci if (args.testMode !== undefined) { 1363af6ab5fSopenharmony_ci TypeScriptLinter.testMode = args.testMode; 1373af6ab5fSopenharmony_ci } 1383af6ab5fSopenharmony_ci if (args.arkts2 === true) { 1393af6ab5fSopenharmony_ci cmdArgs.push('--arkts-2'); 1403af6ab5fSopenharmony_ci } 1413af6ab5fSopenharmony_ci } 1423af6ab5fSopenharmony_ci 1433af6ab5fSopenharmony_ci if (mode === Mode.AUTOFIX) { 1443af6ab5fSopenharmony_ci cmdArgs.push('--autofix'); 1453af6ab5fSopenharmony_ci } 1463af6ab5fSopenharmony_ci 1473af6ab5fSopenharmony_ci return parseCommandLine(cmdArgs); 1483af6ab5fSopenharmony_ci} 1493af6ab5fSopenharmony_ci 1503af6ab5fSopenharmony_cifunction compareExpectedAndActual(testDir: string, testFile: string, mode: Mode, resultNodes: TestNodeInfo[]): string { 1513af6ab5fSopenharmony_ci // Read file with expected test result. 1523af6ab5fSopenharmony_ci let expectedResult: { nodes: TestNodeInfo[] }; 1533af6ab5fSopenharmony_ci let diff: string = ''; 1543af6ab5fSopenharmony_ci const resultExt = RESULT_EXT[mode]; 1553af6ab5fSopenharmony_ci const testResultFileName = testFile + resultExt; 1563af6ab5fSopenharmony_ci try { 1573af6ab5fSopenharmony_ci const expectedResultFile = fs.readFileSync(path.join(testDir, testResultFileName)).toString(); 1583af6ab5fSopenharmony_ci expectedResult = JSON.parse(expectedResultFile); 1593af6ab5fSopenharmony_ci 1603af6ab5fSopenharmony_ci if (!expectedResult?.nodes || expectedResult.nodes.length !== resultNodes.length) { 1613af6ab5fSopenharmony_ci const expectedResultCount = expectedResult?.nodes ? expectedResult.nodes.length : 0; 1623af6ab5fSopenharmony_ci diff = `Expected count: ${expectedResultCount} vs actual count: ${resultNodes.length}`; 1633af6ab5fSopenharmony_ci Logger.info(`${TAB}${diff}`); 1643af6ab5fSopenharmony_ci } else { 1653af6ab5fSopenharmony_ci diff = expectedAndActualMatch(expectedResult.nodes, resultNodes); 1663af6ab5fSopenharmony_ci } 1673af6ab5fSopenharmony_ci 1683af6ab5fSopenharmony_ci if (diff) { 1693af6ab5fSopenharmony_ci Logger.info(`${TAB}Test failed. Expected and actual results differ.`); 1703af6ab5fSopenharmony_ci } 1713af6ab5fSopenharmony_ci } catch (error) { 1723af6ab5fSopenharmony_ci Logger.info(`${TAB}Test failed. ` + error); 1733af6ab5fSopenharmony_ci } 1743af6ab5fSopenharmony_ci 1753af6ab5fSopenharmony_ci return diff; 1763af6ab5fSopenharmony_ci} 1773af6ab5fSopenharmony_ci 1783af6ab5fSopenharmony_cifunction runTest(testDir: string, testFile: string, mode: Mode): boolean { 1793af6ab5fSopenharmony_ci if (mode === Mode.AUTOFIX && fs.existsSync(path.join(testDir, testFile + AUTOFIX_SKIP_EXT))) { 1803af6ab5fSopenharmony_ci Logger.info(`Skipping test ${testFile} (${Mode[mode]} mode)`); 1813af6ab5fSopenharmony_ci return false; 1823af6ab5fSopenharmony_ci } 1833af6ab5fSopenharmony_ci Logger.info(`Running test ${testFile} (${Mode[mode]} mode)`); 1843af6ab5fSopenharmony_ci 1853af6ab5fSopenharmony_ci TypeScriptLinter.initGlobals(); 1863af6ab5fSopenharmony_ci 1873af6ab5fSopenharmony_ci const currentTestMode = TypeScriptLinter.testMode; 1883af6ab5fSopenharmony_ci 1893af6ab5fSopenharmony_ci const cmdOptions = parseArgs(testDir, testFile, mode); 1903af6ab5fSopenharmony_ci const lintOptions = compileLintOptions(cmdOptions); 1913af6ab5fSopenharmony_ci lintOptions.compatibleSdkVersion = '12'; 1923af6ab5fSopenharmony_ci lintOptions.compatibleSdkVersionStage = 'beta3'; 1933af6ab5fSopenharmony_ci const result = lint(lintOptions, getEtsLoaderPath(lintOptions)); 1943af6ab5fSopenharmony_ci const fileProblems = result.problemsInfos.get(path.normalize(cmdOptions.inputFiles[0])); 1953af6ab5fSopenharmony_ci if (fileProblems === undefined) { 1963af6ab5fSopenharmony_ci return true; 1973af6ab5fSopenharmony_ci } 1983af6ab5fSopenharmony_ci 1993af6ab5fSopenharmony_ci TypeScriptLinter.testMode = currentTestMode; 2003af6ab5fSopenharmony_ci 2013af6ab5fSopenharmony_ci // Get list of bad nodes from the current run. 2023af6ab5fSopenharmony_ci const resultNodes: TestNodeInfo[] = fileProblems.map<TestNodeInfo>((x) => { 2033af6ab5fSopenharmony_ci return { 2043af6ab5fSopenharmony_ci line: x.line, 2053af6ab5fSopenharmony_ci column: x.column, 2063af6ab5fSopenharmony_ci endLine: x.endLine, 2073af6ab5fSopenharmony_ci endColumn: x.endColumn, 2083af6ab5fSopenharmony_ci problem: x.problem, 2093af6ab5fSopenharmony_ci autofix: mode === Mode.AUTOFIX ? x.autofix : undefined, 2103af6ab5fSopenharmony_ci suggest: x.suggest, 2113af6ab5fSopenharmony_ci rule: x.rule, 2123af6ab5fSopenharmony_ci severity: ProblemSeverity[x.severity] 2133af6ab5fSopenharmony_ci }; 2143af6ab5fSopenharmony_ci }); 2153af6ab5fSopenharmony_ci 2163af6ab5fSopenharmony_ci // Read file with expected test result. 2173af6ab5fSopenharmony_ci const testResult = compareExpectedAndActual(testDir, testFile, mode, resultNodes); 2183af6ab5fSopenharmony_ci 2193af6ab5fSopenharmony_ci // Write file with actual test results. 2203af6ab5fSopenharmony_ci writeActualResultFile(testDir, testFile, mode, resultNodes, testResult); 2213af6ab5fSopenharmony_ci 2223af6ab5fSopenharmony_ci return !!testResult; 2233af6ab5fSopenharmony_ci} 2243af6ab5fSopenharmony_ci 2253af6ab5fSopenharmony_cifunction expectedAndActualMatch(expectedNodes: TestNodeInfo[], actualNodes: TestNodeInfo[]): string { 2263af6ab5fSopenharmony_ci // Compare expected and actual results. 2273af6ab5fSopenharmony_ci for (let i = 0; i < actualNodes.length; i++) { 2283af6ab5fSopenharmony_ci const actual = actualNodes[i]; 2293af6ab5fSopenharmony_ci const expect = expectedNodes[i]; 2303af6ab5fSopenharmony_ci if (!locationMatch(expect, actual) || actual.problem !== expect.problem) { 2313af6ab5fSopenharmony_ci return reportDiff(expect, actual); 2323af6ab5fSopenharmony_ci } 2333af6ab5fSopenharmony_ci if (!autofixArraysMatch(expect.autofix, actual.autofix)) { 2343af6ab5fSopenharmony_ci return reportDiff(expect, actual); 2353af6ab5fSopenharmony_ci } 2363af6ab5fSopenharmony_ci if (expect.suggest && actual.suggest !== expect.suggest) { 2373af6ab5fSopenharmony_ci return reportDiff(expect, actual); 2383af6ab5fSopenharmony_ci } 2393af6ab5fSopenharmony_ci if (expect.rule && actual.rule !== expect.rule) { 2403af6ab5fSopenharmony_ci return reportDiff(expect, actual); 2413af6ab5fSopenharmony_ci } 2423af6ab5fSopenharmony_ci if (expect.severity && actual.severity !== expect.severity) { 2433af6ab5fSopenharmony_ci return reportDiff(expect, actual); 2443af6ab5fSopenharmony_ci } 2453af6ab5fSopenharmony_ci } 2463af6ab5fSopenharmony_ci 2473af6ab5fSopenharmony_ci return ''; 2483af6ab5fSopenharmony_ci} 2493af6ab5fSopenharmony_ci 2503af6ab5fSopenharmony_cifunction locationMatch(expected: TestNodeInfo, actual: TestNodeInfo): boolean { 2513af6ab5fSopenharmony_ci return ( 2523af6ab5fSopenharmony_ci actual.line === expected.line || 2533af6ab5fSopenharmony_ci actual.column === expected.column || 2543af6ab5fSopenharmony_ci !!(expected.endLine && actual.endLine === expected.endLine) || 2553af6ab5fSopenharmony_ci !!(expected.endColumn && actual.endColumn === expected.endColumn) 2563af6ab5fSopenharmony_ci ); 2573af6ab5fSopenharmony_ci} 2583af6ab5fSopenharmony_ci 2593af6ab5fSopenharmony_cifunction autofixArraysMatch(expected: Autofix[] | undefined, actual: Autofix[] | undefined): boolean { 2603af6ab5fSopenharmony_ci if (!expected && !actual) { 2613af6ab5fSopenharmony_ci return true; 2623af6ab5fSopenharmony_ci } 2633af6ab5fSopenharmony_ci if (!(expected && actual) || expected.length !== actual.length) { 2643af6ab5fSopenharmony_ci return false; 2653af6ab5fSopenharmony_ci } 2663af6ab5fSopenharmony_ci for (let i = 0; i < actual.length; ++i) { 2673af6ab5fSopenharmony_ci if ( 2683af6ab5fSopenharmony_ci actual[i].start !== expected[i].start || 2693af6ab5fSopenharmony_ci actual[i].end !== expected[i].end || 2703af6ab5fSopenharmony_ci actual[i].replacementText !== expected[i].replacementText 2713af6ab5fSopenharmony_ci ) { 2723af6ab5fSopenharmony_ci return false; 2733af6ab5fSopenharmony_ci } 2743af6ab5fSopenharmony_ci } 2753af6ab5fSopenharmony_ci return true; 2763af6ab5fSopenharmony_ci} 2773af6ab5fSopenharmony_ci 2783af6ab5fSopenharmony_cifunction writeActualResultFile( 2793af6ab5fSopenharmony_ci testDir: string, 2803af6ab5fSopenharmony_ci testFile: string, 2813af6ab5fSopenharmony_ci mode: Mode, 2823af6ab5fSopenharmony_ci resultNodes: TestNodeInfo[], 2833af6ab5fSopenharmony_ci diff: string 2843af6ab5fSopenharmony_ci): void { 2853af6ab5fSopenharmony_ci const actualResultsDir = path.join(testDir, 'results'); 2863af6ab5fSopenharmony_ci const resultExt = RESULT_EXT[mode]; 2873af6ab5fSopenharmony_ci if (!fs.existsSync(actualResultsDir)) { 2883af6ab5fSopenharmony_ci fs.mkdirSync(actualResultsDir); 2893af6ab5fSopenharmony_ci } 2903af6ab5fSopenharmony_ci 2913af6ab5fSopenharmony_ci const actualResultJSON = JSON.stringify({ nodes: resultNodes }, null, 4); 2923af6ab5fSopenharmony_ci fs.writeFileSync(path.join(actualResultsDir, testFile + resultExt), actualResultJSON); 2933af6ab5fSopenharmony_ci 2943af6ab5fSopenharmony_ci if (diff) { 2953af6ab5fSopenharmony_ci fs.writeFileSync(path.join(actualResultsDir, testFile + resultExt + DIFF_EXT), diff); 2963af6ab5fSopenharmony_ci } 2973af6ab5fSopenharmony_ci} 2983af6ab5fSopenharmony_ci 2993af6ab5fSopenharmony_cifunction reportDiff(expected: TestNodeInfo, actual: TestNodeInfo): string { 3003af6ab5fSopenharmony_ci const expectedNode = JSON.stringify({ nodes: [expected] }, null, 4); 3013af6ab5fSopenharmony_ci const actualNode = JSON.stringify({ nodes: [actual] }, null, 4); 3023af6ab5fSopenharmony_ci 3033af6ab5fSopenharmony_ci const diff = `Expected: 3043af6ab5fSopenharmony_ci${expectedNode} 3053af6ab5fSopenharmony_ciActual: 3063af6ab5fSopenharmony_ci${actualNode}`; 3073af6ab5fSopenharmony_ci 3083af6ab5fSopenharmony_ci Logger.info(diff); 3093af6ab5fSopenharmony_ci return diff; 3103af6ab5fSopenharmony_ci} 3113af6ab5fSopenharmony_ci 3123af6ab5fSopenharmony_cirunTests(process.argv.slice(2)); 313