* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { spawnSync } from 'node:child_process';
import * as path from 'node:path';
import type { LintRunResult } from '../lib/LintRunResult';
import { Logger } from '../lib/Logger';
import type { ProblemInfo } from '../lib/ProblemInfo';
import type { TestModeProperties } from './TestMode';
import { TestMode } from './TestMode';
import type { HomeCheckArguments } from './TestArgs';
import type { RunTestFileOptions } from './RunTestFileOptions';
import { LintTest } from './LintTest';
import { TAB } from './Consts';
import type { LinterOptions } from '../lib/LinterOptions';
import { getCommandLineArguments } from './CommandLineUtil';
import { parseCommandLine } from '../cli/CommandLineParser';
export class HomeCheckTest extends LintTest {
private readonly homecheckArgs: HomeCheckArguments;
private readonly testCommonOpts?: LinterOptions;
private readonly testModeArgs?: string;
constructor(
runTestFileOpts: RunTestFileOptions,
homecheckArgs: HomeCheckArguments,
testModeProps: TestModeProperties,
testCommonOpts?: LinterOptions,
testModeArgs?: string
) {
const testFilePath = path.normalize(path.join(runTestFileOpts.testDir, runTestFileOpts.testFile));
const linterOptions = createHomeCheckLinterOptions(testModeProps, testCommonOpts, testModeArgs, runTestFileOpts);
super({
testDir: runTestFileOpts.testDir,
testFile: runTestFileOpts.testFile,
testModeProps: testModeProps,
cmdOptions: {
inputFiles: [testFilePath],
linterOptions: linterOptions
}
});
this.homecheckArgs = homecheckArgs;
this.testCommonOpts = testCommonOpts;
this.testModeArgs = testModeArgs;
}
run(): boolean {
Logger.info(`Running test ${this.testFile} (${TestMode[this.testModeProps.mode]} HomeCheck mode)`);
const runnerResult = this.runHomeCheck();
if (!runnerResult) {
return false;
}
const problemsInfos = new Map([[path.normalize(this.cmdOptions.inputFiles[0]), runnerResult.problems]]);
return this.validate({ problemsInfos } as LintRunResult);
}
private runHomeCheck(): { problems: ProblemInfo[] } | null {
const projectPath = path.resolve(this.testDir, this.homecheckArgs.projectPath ?? '.');
const testFilePath = path.normalize(path.resolve(this.testDir, this.testFile));
const runnerConfig = {
projectPath,
testFile: testFilePath,
rules: this.homecheckArgs.rules,
ruleSet: this.homecheckArgs.ruleSet,
ruleOptions: this.resolveRuleOptions(),
ohosSdkPath: this.resolveOptionalTestPath(this.homecheckArgs.ohosSdkPath),
hmsSdkPath: this.resolveOptionalTestPath(this.homecheckArgs.hmsSdkPath),
languageTags: this.resolveLanguageTags(projectPath),
modeOpts: this.testModeProps.modeOpts
};
const encodedConfig = Buffer.from(JSON.stringify(runnerConfig)).toString('base64');
const runnerPath = path.join(__dirname, 'HomeCheckRunner.js');
const result = spawnSync(process.execPath, [runnerPath, encodedConfig], {
cwd: process.cwd(),
encoding: 'utf8'
});
if (result.status !== 0) {
Logger.info(`${TAB}HomeCheck test failed to run:\n${result.stderr || result.stdout}`);
return null;
}
try {
return JSON.parse(result.stdout);
} catch (error) {
Logger.info(`${TAB}Failed to parse HomeCheck test result: ${(error as Error).message}`);
Logger.info(`${TAB}Raw output:\n${result.stdout}`);
return null;
}
}
private resolveOptionalTestPath(filePath: string | undefined): string {
return filePath ? path.resolve(this.testDir, filePath) : '';
}
private resolveLanguageTags(projectPath: string): Record<string, number> {
const languageTags = this.homecheckArgs.languageTags ?? {};
return Object.fromEntries(
Object.entries(languageTags).map(([filePath, language]) => {
return [path.normalize(path.resolve(projectPath, filePath)), language];
})
);
}
private resolveRuleOptions(): Record<string, object[]> | undefined {
const ruleOptions = this.homecheckArgs.ruleOptions;
if (!ruleOptions) {
return undefined;
}
return JSON.parse(JSON.stringify(ruleOptions), (key: string, value: unknown) => {
return key === 'path' && typeof value === 'string' ? path.resolve(this.testDir, value) : value;
});
}
}
function createHomeCheckLinterOptions(
testModeProps: TestModeProperties,
testCommonOpts?: LinterOptions,
testModeArgs?: string,
runTestFileOpts?: RunTestFileOptions
): LinterOptions {
const linterOpts: LinterOptions = {};
if (testCommonOpts) {
Object.assign(linterOpts, testCommonOpts);
}
if (testModeArgs) {
Object.assign(linterOpts, getLinterOptionsFromCommandLine(testModeArgs));
}
if (runTestFileOpts?.testRunnerOpts?.linterOptions) {
Object.assign(linterOpts, runTestFileOpts.testRunnerOpts.linterOptions);
}
Object.assign(linterOpts, testModeProps.modeOpts);
return linterOpts;
}
function getLinterOptionsFromCommandLine(cmdLine: string): LinterOptions {
return parseCommandLine(getCommandLineArguments(cmdLine), { exitOnFail: false, disableErrorOutput: true }).
linterOptions;
}