From 5c86165f4a4288412889128ac0d978da227f39d1 Mon Sep 17 00:00:00 2001 From: liujia178 Date: Wed, 11 Jun 2025 19:58:44 +0800 Subject: [PATCH] Fix build system UT test cases Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICEFQT Signed-off-by: liujia178 --- .../test/ut/base_modeTest/base_mode.test.ts | 1095 +++++++++++++++++ .../test/ut/entryTest/entry.test.ts | 84 ++ .../ut/fileManagerTest/filemanager.test.ts | 216 ++++ .../generate_arktsconfig.test.ts | 114 ++ .../test/ut/loggerTest/logger.test.ts | 200 +++ .../demo_1.2_dep_hsp1.2/build_config.json | 47 + .../demo_1.2_dep_hsp1.2/build_config1.json | 47 + .../demo_1.2_dep_hsp1.2/build_config2.json | 26 + .../demo_1.2_dep_hsp1.2/build_config3.json | 47 + .../demo_1.2_dep_hsp1.2/build_config4.json | 47 + .../demo_1.2_dep_hsp1.2/build_config5.json | 47 + .../demo_1.2_dep_hsp1.2/build_config6.json | 47 + .../demo_1.2_dep_hsp1.2/build_config7.json | 47 + .../demo_1.2_dep_hsp1.2/build_config8.json | 47 + .../demo_1.2_dep_hsp1.2/build_config9.json | 47 + .../ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets | 22 + .../mock/demo_1.2_dep_hsp1.2/harA/index.ets | 16 + .../ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets | 7 + .../ut/mock/demo_1.2_dep_hsp1.2/hspA/Calc.ets | 3 + .../mock/demo_1.2_dep_hsp1.2/hspA/index.ets | 1 + .../plugins_driverTest/plugins_driver.test.ts | 162 +++ .../test/ut/utilsTest/utils.test.ts | 225 ++++ 22 files changed, 2594 insertions(+) create mode 100755 ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts create mode 100755 ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts create mode 100755 ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts create mode 100755 ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts create mode 100755 ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config1.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config2.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config3.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config4.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config5.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config6.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config7.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config8.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config9.json create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/Calc.ets create mode 100755 ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets create mode 100755 ets2panda/driver/build_system/test/ut/plugins_driverTest/plugins_driver.test.ts create mode 100755 ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts diff --git a/ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts b/ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts new file mode 100755 index 0000000000..3213406d6f --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/base_modeTest/base_mode.test.ts @@ -0,0 +1,1095 @@ +/* + * Copyright (c) 2025 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 * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as entryModule from '../../../src/entry'; +import { BaseMode } from '../../../src/build/base_mode'; +import { BuildConfig, BUILD_TYPE, BUILD_MODE, OHOS_MODULE_TYPE, ModuleInfo } from '../../../src/types'; +import { BuildMode } from '../../../src/build/build_mode'; +import { isWindows, isMac } from '../../../src/utils'; +import { + ErrorCode, +} from '../../../src/error_code'; +import cluster, { + Cluster, +} from 'cluster'; + +interface Job { + id: string; + type?: string; + dependencies: string[]; + dependants: string[]; + fileList?: string[]; + isDeclFile?: boolean; + isAbcJob?: boolean; + isInCycle?: boolean; + result?: any; +} + +interface WorkerInfo { + worker: ThreadWorker; + isIdle: boolean; +} + +interface ThreadWorker { + postMessage: (message: any) => void; +} + +interface Queues { + externalProgramQueue: Job[]; + abcQueue: Job[]; +} + +interface DependencyFileConfig { + dependencies: Record; + dependants: Record; +} + +jest.mock('os', () => ({ + ...jest.requireActual('os'), + type: jest.fn().mockReturnValue('Darwin') +})); + +beforeEach(() => { + jest.clearAllMocks(); + process.exit = jest.fn() as any; +}); + +beforeAll(() => { + const { execSync } = require('child_process'); + execSync('rimraf test/ut/mock/dist', { stdio: 'pipe' }); +}); + +function main(configFilePath?: string): void { + const buildConfigPath = configFilePath; + + const projectConfig: BuildConfig = JSON.parse(fs.readFileSync(buildConfigPath!, 'utf-8')); + + entryModule.build(projectConfig); +} + +describe('test mock isWindows', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(os, 'type').mockReturnValue('Windows_NT'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('test mac001', () => { + test_mac001(); + }); +}); + +function test_mac001() { + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config4.json"; + main(buildConfigPath); + expect(isWindows()).toBe(true); +} + +describe('test mock isMac', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(os, 'type').mockReturnValue('Darwin'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('test mac002', () => { + test_mac002(); + }); +}); + +function test_mac002() { + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config5.json"; + main(buildConfigPath); + expect(isMac()).toBe(true); +} + +describe('test base_mode.ts file api', () => { + test('test collectModuleInfos', () => { + test_collectModuleInfos(); + }); + + test('test collectDependentCompileFiles002', () => { + test_collectDependentCompileFiles002(); + }); + + test('test shouldSkipFile', () => { + test_shouldSkipFile(); + }); + + test('test setupCluster', () => { + test_setupCluster(); + }); + + test('test terminateAllWorkers', () => { + test_terminateAllWorkers(); + }); + + test('test collectModuleInfos 001', () => { + test_collectDependencyModules001(); + }); + + test('test collectModuleInfos 002', () => { + test_collectDependencyModules002(); + }); + + test('test runall new', () => { + test_runParallell1(); + }); + + test('test test_collectAbcFileFromByteCodeHar', () => { + test_collectAbcFileFromByteCodeHar(); + }); + + test('test getDependentModules', async () => { + test_getDependentModules(); + }); + + test('collectCompileFiles: test declaration files skip branch', () => { + test_collectCompileFiles_decl_ets_skip(); + }); + + test('collectCompileFiles: test bytecode HAR branch', () => { + test_collectCompileFiles_bytecode_har(); + }); + + test('collectCompileFiles: test file not in module path branch', () => { + test_collectCompileFiles_file_not_in_module(); + }); + + test('test createExternalProgramJob method branches', () => { + test_createExternalProgramJob_branches(); + }); + + test('test findStronglyConnectedComponents method branches', () => { + test_findStronglyConnectedComponents_branches(); + }); + + test('test assignTaskToIdleWorker abcQueue branch without job', () => { + test_assignTaskToIdleWorker_abcQueue_no_job(); + }); + + test('test assignTaskToIdleWorker with empty queues', () => { + test_assignTaskToIdleWorker_empty_queues(); + }); +}); + +function test_assignTaskToIdleWorker_empty_queues() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockConfig = { + packageName: "test", + moduleType: "har", + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache" + }; + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testAssignTaskToIdleWorker( + workerInfo: WorkerInfo, + queues: Queues, + processingJobs: Set, + serializableConfig: Object, + globalContextPtr: any + ): void { + (this as any).assignTaskToIdleWorker( + workerInfo, + queues, + processingJobs, + serializableConfig, + globalContextPtr + ); + } + } + const baseMode = new TestBaseMode(mockConfig as any); + const mockWorker = { + postMessage: jest.fn() + }; + + const workerInfo: WorkerInfo = { + worker: mockWorker as unknown as ThreadWorker, + isIdle: true + }; + + const queues: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + + const processingJobs = new Set(); + const serializableConfig = {}; + const globalContextPtr = {}; + + (baseMode as any).allFiles = new Map([ + ['test/file.ets', { + filePath: 'test/file.ets', + packageName: 'test', + arktsConfigFile: 'test/config.json', + abcFilePath: './dist/file.abc' + }] + ]); + + const postMessageSpy = jest.spyOn(mockWorker, 'postMessage'); + + baseMode.testAssignTaskToIdleWorker( + workerInfo, + queues, + processingJobs, + serializableConfig, + globalContextPtr + ); + + expect(postMessageSpy).not.toHaveBeenCalled(); + expect(processingJobs.size).toBe(0); + expect(workerInfo.isIdle).toBe(true); + jest.restoreAllMocks(); +} + +function test_assignTaskToIdleWorker_abcQueue_no_job() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockConfig = { + packageName: "test", + moduleType: "har", + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache" + }; + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + public testAssignTaskToIdleWorker( + workerInfo: WorkerInfo, + queues: Queues, + processingJobs: Set, + serializableConfig: Object, + globalContextPtr: any + ): void { + (this as any).assignTaskToIdleWorker( + workerInfo, + queues, + processingJobs, + serializableConfig, + globalContextPtr + ); + } + } + + const baseMode = new TestBaseMode(mockConfig as any); + + const mockWorker = { + postMessage: jest.fn() + }; + + const workerInfo: WorkerInfo = { + worker: mockWorker as unknown as ThreadWorker, + isIdle: true + }; + + const queues: Queues = { + externalProgramQueue: [], + abcQueue: [{ + id: 'abc:test/nonexistentfile.ets', + type: 'abc', + dependencies: [], + dependants: [], + result: null, + fileList: ['test/nonexistentfile.ets'], + isDeclFile: false, + isAbcJob: true + }] + }; + + const processingJobs = new Set(); + const serializableConfig = {}; + const globalContextPtr = {}; + + (baseMode as any).allFiles = new Map([ + ['test/otherfile.ets', { + filePath: 'test/otherfile.ets', + packageName: 'test', + arktsConfigFile: 'test/config.json', + abcFilePath: './dist/otherfile.abc' + }] + ]); + + const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); + const postMessageSpy = jest.spyOn(mockWorker, 'postMessage'); + try { + baseMode.testAssignTaskToIdleWorker( + workerInfo, + queues, + processingJobs, + serializableConfig, + globalContextPtr + ); + fail('Expected method to throw, but it did not'); + } catch (error) { + expect(error).toBeInstanceOf(ReferenceError); + expect(workerInfo.isIdle).toBe(false); + } finally { + consoleSpy.mockRestore(); + jest.restoreAllMocks(); + } +} + +function test_findStronglyConnectedComponents_branches() { + const mockConfig = { + packageName: "test", moduleRootPath: "/test/path", sourceRoots: ["./"], + loaderOutPath: "./dist", cachePath: "./dist/cache", buildMode: "Debug" + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { return Promise.resolve(); } + public testFindStronglyConnectedComponents(graph: DependencyFileConfig): Map> { + return (this as any).findStronglyConnectedComponents(graph); + } + protected createHash(input: string): string { return 'cycle-group-' + input.length; } + } + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue({ printInfo: jest.fn(), printError: jest.fn() }); + const baseMode = new TestBaseMode(mockConfig as any); + const graph1 = { + dependencies: { 'A': ['B', 'C'], 'B': ['C'], 'C': ['A'] }, + dependants: { 'A': ['C'], 'B': ['A'], 'C': ['A', 'B'] } + }; + const result1 = baseMode.testFindStronglyConnectedComponents(graph1); + expect(result1.size).toBe(1); + expect(Array.from(result1.values())[0].size).toBe(3); + const graph2 = { + dependencies: { 'A': ['B', 'C'], 'B': ['D'], 'C': ['D'], 'D': ['E'], 'E': ['B'] }, + dependants: { 'A': [], 'B': ['A', 'E'], 'C': ['A'], 'D': ['B', 'C'], 'E': ['D'] } + }; + const result2 = baseMode.testFindStronglyConnectedComponents(graph2); + expect(result2.size).toBe(1); + expect(Array.from(result2.values())[0].size).toBe(3); + const graph3 = { + dependencies: { 'A': ['B'], 'B': ['C'], 'C': ['D'], 'D': [], 'E': ['F'], 'F': ['E'] }, + dependants: { 'A': [], 'B': ['A'], 'C': ['B'], 'D': ['C'], 'E': ['F'], 'F': ['E'] } + }; + const result3 = baseMode.testFindStronglyConnectedComponents(graph3); + expect(result3.size).toBe(1); + expect(Array.from(result3.values())[0].size).toBe(2); + const graph4 = { + dependencies: { 'A': ['B'], 'B': ['C'], 'C': ['D'], 'D': [] }, + dependants: { 'A': [], 'B': ['A'], 'C': ['B'], 'D': ['C'] } + }; + const result4 = baseMode.testFindStronglyConnectedComponents(graph4); + expect(result4.size).toBe(0); +} + +function test_createExternalProgramJob_branches() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: "Debug", + moduleType: "har" + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testCreateExternalProgramJob(id: string, fileList: string[], jobs: Record, dependencies: Set, isInCycle?: boolean): void { + return (this as any).createExternalProgramJob(id, fileList, jobs, dependencies, isInCycle); + } + } + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue({ + printInfo: jest.fn(), + printError: jest.fn() + }); + + const baseMode = new TestBaseMode(mockConfig as any); + + { + const id = "external-program:test/file.ets"; + const fileList = ["test/file.ets"]; + const jobs: Record = {}; + const dependencies = new Set([id, "external-program:other.ets"]); + const isInCycle = false; + + baseMode.testCreateExternalProgramJob(id, fileList, jobs, dependencies, isInCycle); + + expect(dependencies.has(id)).toBe(false); + expect(dependencies.size).toBe(1); + + expect(jobs[id]).toBeDefined(); + expect(jobs[id].id).toBe(id); + expect(jobs[id].fileList).toEqual(fileList); + expect(jobs[id].isDeclFile).toBe(true); + expect(jobs[id].isInCycle).toBe(false); + expect(jobs[id].dependencies).toEqual(["external-program:other.ets"]); + expect(jobs[id].dependants).toEqual([]); + } + + { + const id = "external-program:test/file2.ets"; + const fileList = ["test/file2.ets", "test/file2b.ets"]; + const jobs: Record = { + [id]: { + id, + fileList: ["test/file2.ets"], + isDeclFile: false, + isInCycle: false, + isAbcJob: false, + dependencies: ["external-program:dep1.ets"], + dependants: ["external-program:dep3.ets"] + } + }; + + const dependencies = new Set(["external-program:dep2.ets"]); + const isInCycle = true; + + baseMode.testCreateExternalProgramJob(id, fileList, jobs, dependencies, isInCycle); + + expect(jobs[id]).toBeDefined(); + expect(jobs[id].id).toBe(id); + expect(jobs[id].fileList).toEqual(["test/file2.ets"]); + expect(jobs[id].isDeclFile).toBe(false); + expect(jobs[id].isInCycle).toBe(false); + expect(jobs[id].dependencies).toContain("external-program:dep1.ets"); + expect(jobs[id].dependencies).toContain("external-program:dep2.ets"); + expect(jobs[id].dependencies.length).toBe(2); + expect(jobs[id].dependants).toEqual(["external-program:dep3.ets"]); + } +} + +function test_collectCompileFiles_bytecode_har() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockConfig = { + packageName: "test", + moduleType: "har", + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + enableDeclgenEts2Ts: true, + dependentModuleList: [] + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testCollectCompileFiles(): void { + this.collectCompileFiles(); + } + + public testCollectAbcFileFromByteCodeHar(): void { + this.collectAbcFileFromByteCodeHar(); + } + } + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + const baseMode = new TestBaseMode(mockConfig as any); + + (baseMode as any).cacheDir = "./dist/cache"; + (baseMode as any).abcFiles = new Set(); + (baseMode as any).hashCache = {}; + (baseMode as any).compileFiles = new Map(); + + (baseMode as any).entryFiles = new Set([ + '/test/path/test.ets' + ]); + + (baseMode as any).moduleInfos = new Map(); + (baseMode as any).moduleInfos.set("test", { + packageName: "test", + moduleType: "har", + byteCodeHar: true, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + arktsConfigFile: "./dist/cache/test/config.json", + compileFileInfos: [] + }); + + (global as any).getFileHash = jest.fn().mockReturnValue("hash123"); + const utils = require('../../../src/utils'); + + jest.spyOn(baseMode, 'testCollectAbcFileFromByteCodeHar').mockImplementation(() => { }); + + baseMode.testCollectCompileFiles(); +} + +function test_collectCompileFiles_file_not_in_module() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockConfig = { + packageName: "test", + moduleType: "har", + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + enableDeclgenEts2Ts: true, + dependentModuleList: [] + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testCollectCompileFiles(): void { + this.collectCompileFiles(); + } + } + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + const baseMode = new TestBaseMode(mockConfig as any); + + (baseMode as any).cacheDir = "./dist/cache"; + (baseMode as any).abcFiles = new Set(); + (baseMode as any).hashCache = {}; + (baseMode as any).compileFiles = new Map(); + + (baseMode as any).entryFiles = new Set([ + '/other/path/test.ets' + ]); + + (baseMode as any).moduleInfos = new Map(); + (baseMode as any).moduleInfos.set("test", { + packageName: "test", + moduleType: "har", + byteCodeHar: false, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + arktsConfigFile: "./dist/cache/test/config.json", + compileFileInfos: [] + }); + + baseMode.testCollectCompileFiles(); + + expect(mockLogger.printError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCode.BUILDSYSTEM_FILE_NOT_BELONG_TO_ANY_MODULE_FAIL, + description: 'File does not belong to any module in moduleInfos.' + }) + ); + + expect((baseMode as any).compileFiles.size).toBe(0); +} + +function test_collectCompileFiles_decl_ets_skip() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockConfig = { + packageName: "test", + moduleType: "har", + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + enableDeclgenEts2Ts: true, + dependentModuleList: [] + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testCollectCompileFiles(): void { + this.collectCompileFiles(); + } + } + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + const baseMode = new TestBaseMode(mockConfig as any); + + (baseMode as any).cacheDir = "./dist/cache"; + (baseMode as any).abcFiles = new Set(); + (baseMode as any).hashCache = {}; + (baseMode as any).compileFiles = new Map(); + + (baseMode as any).entryFiles = new Set([ + './test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets', + '/test/path/test.d.ets' + ]); + + (baseMode as any).moduleInfos = new Map(); + (baseMode as any).moduleInfos.set("test", { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + arktsConfigFile: "./dist/cache/test/config.json", + compileFileInfos: [] + }); + + (global as any).getFileHash = jest.fn().mockReturnValue("hash123"); + const utils = require('../../../src/utils'); + + baseMode.testCollectCompileFiles(); +} + +function test_collectDependencyModules001() { + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config6.json"; + main(buildConfigPath); +} + +function test_collectDependencyModules002() { + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config7.json"; + main(buildConfigPath); +} + +function test_collectAbcFileFromByteCodeHar() { + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config8.json"; + main(buildConfigPath); +} + +function test_getDependentModules() { + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config9.json"; + main(buildConfigPath); +} + +async function test_runParallell1() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockCluster = { + isPrimary: true, + fork: jest.fn().mockReturnValue({ + on: jest.fn(), + send: jest.fn() + }), + workers: {}, + removeAllListeners: jest.fn(), + setupPrimary: jest.fn() + }; + + jest.spyOn(cluster, 'fork').mockImplementation(mockCluster.fork); + jest.spyOn(cluster, 'removeAllListeners').mockImplementation(mockCluster.removeAllListeners); + jest.spyOn(cluster, 'setupPrimary').mockImplementation(mockCluster.setupPrimary); + + const mockConfig = { + packageName: "test", + compileFiles: ["test/file.ets"], + moduleRootPath: "test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: "Debug" + }; + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance(mockConfig); + + class TestBuildMode extends BuildMode { + + public generateModuleInfos(): void { + } + + public setupCluster(): void { + } + + public mergeAbcFiles(): void { + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + + jest.spyOn(baseMode, 'generateModuleInfos').mockImplementation(() => { }); + jest.spyOn(baseMode, 'setupCluster').mockImplementation(() => { }); + jest.spyOn(baseMode, 'mergeAbcFiles').mockImplementation(() => { }); + + (baseMode as any).logger = mockLogger; + + await baseMode.runParallell(); + + expect(baseMode.generateModuleInfos).toHaveBeenCalled(); + expect(baseMode.setupCluster).toHaveBeenCalled(); + expect(baseMode.mergeAbcFiles).toHaveBeenCalled(); +} + +function test_collectModuleInfos() { + const mockLogger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + const mockConfig: BuildConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + plugins: {}, + buildType: BUILD_TYPE.BUILD, + hasMainModule: true, + moduleType: OHOS_MODULE_TYPE.HAR, + arkts: {} as any, + arktsGlobal: {} as any, + enableDeclgenEts2Ts: false, + byteCodeHar: false, + declgenV1OutPath: "./dist/declgen", + declgenV2OutPath: "./dist/declgen/v2", + buildSdkPath: "./sdk", + externalApiPaths: [], + + dependentModuleList: [ + { + "packageName": "", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "index.ets", + "language": "11.2", + "dependencies": ["hspA"], + "byteCodeHar": false + }, + { + "packageName": "", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "11.2", + "byteCodeHar": false + } + ] + }; + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance(mockConfig); + let baseModule: BuildMode = new BuildMode(mockConfig); + (baseModule as any).collectModuleInfos(); + + expect(mockLogger.printError).not.toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCode.BUILDSYSTEM_MODULE_INFO_NOT_CORRECT_FAIL, + description: 'Main module info from hvigor is not correct.' + }) + ); +} + +function test_collectDependentCompileFiles002() { + const mockLogger = { + printError: jest.fn(), + printInfo: jest.fn(), + hasErrors: jest.fn().mockReturnValue(false) + }; + + const moduleRootPath = "test/ut/mock/demo_1.2_dep_hsp1.2/entry/"; + const testFile = `${moduleRootPath}a.ets`; + + const mockConfig: BuildConfig = { + compileFiles: [testFile], + packageName: "entry", + moduleType: OHOS_MODULE_TYPE.HAR, + buildType: BUILD_TYPE.BUILD, + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: moduleRootPath, + sourceRoots: ["./"], + loaderOutPath: "test/ut/mock/dist", + cachePath: "test/ut/mock/dist/cache", + dependentModuleList: [], + plugins: {}, + hasMainModule: false, + arkts: {} as any, + arktsGlobal: {} as any, + enableDeclgenEts2Ts: false, + byteCodeHar: false, + declgenV1OutPath: "./dist/declgen", + declgenV2OutPath: "./dist/declgen/v2", + buildSdkPath: "./sdk", + externalApiPaths: [] + }; + + const BaseMode = require('../../../src/build/base_mode').BaseMode; + const BuildMode = require('../../../src/build/build_mode').BuildMode; + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance(mockConfig); + let baseModule = new BuildMode(mockConfig); + + (baseModule as any).logger = mockLogger; + (baseModule as any).moduleInfos = new Map(); + (baseModule as any).moduleInfos.set("entry", { + packageName: "entry", + moduleRootPath: moduleRootPath, + sourceRoots: ["./"], + compileFileInfos: [] + }); + + (baseModule as any).entryFiles = new Set([testFile]); + (baseModule as any).dependencyFileMap = { + dependants: { + [testFile]: ["dependency1.ets", "dependency2.ets"] + } + }; + (baseModule as any).cacheDir = "test/ut/mock/dist/cache"; + (baseModule as any).hashCache = {}; + (baseModule as any).abcFiles = new Set(); + (baseModule as any).compileFiles = new Map(); + + (baseModule as any).isBuildConfigModified = true; + + (baseModule as any).isFileChanged = jest.fn().mockReturnValue(false); + + (baseModule as any).collectDependentCompileFiles(); + + expect(mockLogger.printError).not.toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCode.BUILDSYSTEM_FILE_NOT_BELONG_TO_ANY_MODULE_FAIL, + message: 'File does not belong to any module in moduleInfos.' + }) + ); + + expect((baseModule as any).abcFiles.size).toBe(1); + const compileFilesArray = Array.from((baseModule as any).compileFiles.keys()); + expect(compileFilesArray.length).toBe(1); + expect(compileFilesArray[0]).toBe(testFile); +} + +function test_shouldSkipFile() { + const mockLogger = { printError: jest.fn() }; + const mockConfig: BuildConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + plugins: {}, + dependentModuleList: [], + buildType: BUILD_TYPE.BUILD, + hasMainModule: false, + moduleType: OHOS_MODULE_TYPE.HAR, + byteCodeHar: false, + arkts: {} as any, + arktsGlobal: {} as any, + declgenV1OutPath: "./dist/declgen", + declgenV2OutPath: "./dist/declgen/v2", + buildSdkPath: "./sdk", + externalApiPaths: [], + enableDeclgenEts2Ts: false + }; + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance(mockConfig); + let baseModule: BaseMode = new BuildMode(mockConfig); + (baseModule as any).logger = mockLogger; + (baseModule as any).hashCache = { + "/test/path/file.ets": "hash123" + }; + + const file = "/test/path/file.ets"; + const moduleInfo: ModuleInfo = { + isMainModule: false, + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + arktsConfigFile: "/cache/test/arktsconfig.json", + compileFileInfos: [], + declgenV1OutPath: "/dist/declgen", + declgenBridgeCodePath: "/dist/bridge", + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + entryFile: "index.ets", + declgenV2OutPath: "/dist/declgen/v2", + byteCodeHar: false + }; + const filePathFromModuleRoot = "file.ets"; + const abcFilePath = "/cache/test/file.abc"; + + (baseModule as any).enableDeclgenEts2Ts = true; + let result3 = (baseModule as any).shouldSkipFile(file, moduleInfo, filePathFromModuleRoot, abcFilePath); + (baseModule as any).enableDeclgenEts2Ts = false; + let result4 = (baseModule as any).shouldSkipFile(file, moduleInfo, filePathFromModuleRoot, abcFilePath); + expect(result3).toBe(false); + expect(result4).toBe(false); +} + +function test_setupCluster() { + const mockConfig: BuildConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + plugins: {}, + dependentModuleList: [], + maxWorkers: 1, + buildType: BUILD_TYPE.BUILD, + hasMainModule: true, + arkts: {} as any, + arktsGlobal: {} as any, + enableDeclgenEts2Ts: false, + moduleType: OHOS_MODULE_TYPE.HAR, + declgenV1OutPath: "./dist/declgen", + declgenV2OutPath: "./dist/declgen/v2", + buildSdkPath: "./sdk", + byteCodeHar: false, + externalApiPaths: [] + }; + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance(mockConfig); + let baseModule: BaseMode = new BuildMode(mockConfig); + + const originalRemoveAllListeners = cluster.removeAllListeners; + const originalSetupPrimary = cluster.setupPrimary; + + const removeAllListenersSpy = jest.fn(); + cluster.removeAllListeners = removeAllListenersSpy; + + const setupPrimarySpy = jest.fn(); + cluster.setupPrimary = setupPrimarySpy; + + try { + (baseModule as any).setupCluster(cluster, { + clearExitListeners: false, + execPath: '/path/to/worker', + execArgs: [] + }); + + expect(removeAllListenersSpy).not.toHaveBeenCalled(); + expect(setupPrimarySpy).toHaveBeenCalledWith({ + exec: '/path/to/worker', + execArgv: [] + }); + } finally { + cluster.removeAllListeners = originalRemoveAllListeners; + cluster.setupPrimary = originalSetupPrimary; + } +} + +function test_terminateAllWorkers() { + const mockConfig: BuildConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + plugins: {}, + dependentModuleList: [], + buildType: BUILD_TYPE.BUILD, + hasMainModule: true, + arkts: {} as any, + arktsGlobal: {} as any, + enableDeclgenEts2Ts: false, + moduleType: OHOS_MODULE_TYPE.HAR, + declgenV1OutPath: "./dist/declgen", + declgenV2OutPath: "./dist/declgen/v2", + buildSdkPath: "./sdk", + byteCodeHar: false, + externalApiPaths: [] + }; + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance(mockConfig); + let baseModule: BaseMode = new BuildMode(mockConfig); + + const originalWorkers = cluster.workers; + + try { + Object.defineProperty(cluster, 'workers', { + value: {}, + configurable: true + }); + + expect(() => { + (baseModule as any).terminateAllWorkers(); + }).not.toThrow(); + + Object.defineProperty(cluster, 'workers', { + value: undefined, + configurable: true + }); + + expect(() => { + (baseModule as any).terminateAllWorkers(); + }).not.toThrow(); + } finally { + Object.defineProperty(cluster, 'workers', { + value: originalWorkers, + configurable: true + }); + } +} diff --git a/ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts b/ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts new file mode 100755 index 0000000000..22feac87f4 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2025 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 * as fs from 'fs'; +import * as entryModule from '../../../src/entry'; +import { BuildConfig } from '../../../src/types'; + +beforeEach(() => { + jest.clearAllMocks(); + + process.exit = jest.fn() as any; + +}); + +function main(configFilePath?: string): void { + const buildConfigPath = configFilePath; + + const projectConfig: BuildConfig = JSON.parse(fs.readFileSync(buildConfigPath!, 'utf-8')); + + entryModule.build(projectConfig); +} + +describe('test entry.ts file api', () => { + test('test build001', () => { + test_build001(); + }); + test('test build002', () => { + test_build002(); + }); + test('test build003', () => { + test_build003(); + }); + test('test build004', () => { + test_build004(); + }); +}); + +function test_build001() { + const buildSpy = jest.spyOn(entryModule, 'build'); + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config3.json"; + main(buildConfigPath); + expect(buildSpy).toHaveBeenCalledWith(expect.objectContaining({ + buildType: 'build' + })); +} + +function test_build002() { + const buildSpy = jest.spyOn(entryModule, 'build'); + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config.json"; + main(buildConfigPath); + expect(buildSpy).toHaveBeenCalledWith(expect.objectContaining({ + buildType: 'build1' + })); +} + +function test_build003() { + const buildSpy = jest.spyOn(entryModule, 'build'); + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config1.json"; + main(buildConfigPath); + expect(buildSpy).toHaveBeenCalledWith(expect.objectContaining({ + frameworkMode: true, + })); +} + +function test_build004() { + const buildSpy = jest.spyOn(entryModule, 'build'); + const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config2.json"; + main(buildConfigPath); + expect(buildSpy).toHaveBeenCalledWith(expect.objectContaining({ + enableDeclgenEts2Ts: true, + })); +} diff --git a/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts b/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts new file mode 100755 index 0000000000..4beba39f10 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2025 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 { FileManager } from '../../../src/plugins/FileManager'; +import { LANGUAGE_VERSION } from '../../../src/pre_define'; +import * as utils from '../../../src/utils'; + +describe('class FileManager', () => { + const mockBuildConfig = { + dependentModuleList: [ + { + packageName: 'modA', + modulePath: '/mock/path/modA', + language: LANGUAGE_VERSION.ARKTS_1_2 + } + ], + externalApiPaths: ['/mock/staticApi'], + buildSdkPath: '/mock/sdk', + compileFiles: ['/mock/project/main.ets'] + }; + + afterEach(() => { + FileManager.cleanFileManagerObject(); + FileManager.arkTSModuleMap.clear(); + FileManager.staticApiPath.clear(); + FileManager.dynamicApiPath.clear(); + // Make tsc ignore the access of private member + // @ts-ignore + FileManager.buildConfig = undefined; + }); + + it('should initialize singleton and static properties', () => { + FileManager.init(mockBuildConfig as any); + expect(FileManager.getInstance()).toBeInstanceOf(FileManager); + expect(FileManager.buildConfig).toEqual(mockBuildConfig); + expect(FileManager.staticApiPath.size).toBe(1); + expect(FileManager.dynamicApiPath.size).toBe(2); + expect(FileManager.arkTSModuleMap.size).toBe(1); + }); + + it('should clean singleton instance', () => { + FileManager.init(mockBuildConfig as any); + FileManager.cleanFileManagerObject(); + // Make tsc ignore the access of private member + // @ts-ignore + expect(FileManager.instance).toBeUndefined(); + }); + + it('should add staticApiPath and dynamicApiPath in initSDK', () => { + FileManager.initSDK(new Set(['/api1', '/api2']), '/sdk/path'); + expect(FileManager.staticApiPath.has('/api1')).toBe(true); + expect(FileManager.staticApiPath.has('/api2')).toBe(true); + expect(FileManager.dynamicApiPath.size).toBe(2); + }); + + it('should handle empty externalApiPath in initSDK', () => { + FileManager.initSDK(new Set(), '/sdk/path'); + expect(FileManager.staticApiPath.size).toBe(0); + expect(FileManager.dynamicApiPath.size).toBe(2); + }); + + it('should get correct language version by file path', () => { + FileManager.init(mockBuildConfig as any); + const fm = FileManager.getInstance(); + expect(fm.getLanguageVersionByFilePath('/mock/staticApi/abc.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_2); + const [dynPath] = Array.from(FileManager.dynamicApiPath); + expect(fm.getLanguageVersionByFilePath(`${dynPath}/abc.ets`)).toBe(LANGUAGE_VERSION.ARKTS_1_1); + expect(fm.getLanguageVersionByFilePath('/mock/project/main.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_2); + expect(fm.getLanguageVersionByFilePath('/mock/path/modA/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_2); + expect(fm.getLanguageVersionByFilePath('/other/path/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_1); + }); + + it('should return ARKTS_1_2 if first line is use static in hybrid module', () => { + FileManager.init({ + ...mockBuildConfig, + dependentModuleList: [ + { + packageName: 'modH', + modulePath: '/mock/hybrid', + language: LANGUAGE_VERSION.ARKTS_HYBRID + } + ] + } as any); + const fm = FileManager.getInstance(); + // Make tsc ignore the access of private member + // @ts-ignore + jest.spyOn(FileManager as any, 'isFirstLineUseStatic').mockReturnValue(true); + expect(fm.getLanguageVersionByFilePath('/mock/hybrid/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_2); + }); + + it('should handle empty dependentModuleList in initLanguageVersionFromDependentModuleMap', () => { + // Make tsc ignore the access of private member + // @ts-ignore + FileManager['initLanguageVersionFromDependentModuleMap']([]); + expect(FileManager.arkTSModuleMap.size).toBe(0); + }); + + it('should getInstance auto new when not initialized', () => { + // Make tsc ignore the access of private member + // @ts-ignore + FileManager.instance = undefined; + expect(FileManager.getInstance()).toBeInstanceOf(FileManager); + }); + + it('should return ARKTS_1_1 for hybrid module if first line is not use static', () => { + FileManager.init({ + dependentModuleList: [ + { + packageName: 'modH', + modulePath: '/mock/hybrid', + language: LANGUAGE_VERSION.ARKTS_HYBRID + } + ], + externalApiPaths: [], + buildSdkPath: '/mock/sdk', + compileFiles: [] + } as any); + const fm = FileManager.getInstance(); + // Make tsc ignore the access of private member + // @ts-ignore + jest.spyOn(FileManager as any, 'isFirstLineUseStatic').mockReturnValue(false); + expect(fm.getLanguageVersionByFilePath('/mock/hybrid/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_1); + }); + + it('should allow cleanFileManagerObject to be called multiple times', () => { + FileManager.init({ + dependentModuleList: [], + externalApiPaths: [], + buildSdkPath: '/mock/sdk', + compileFiles: [] + } as any); + FileManager.cleanFileManagerObject(); + expect(FileManager.getInstance()).toBeInstanceOf(FileManager); + FileManager.cleanFileManagerObject(); + // Make tsc ignore the access of private member + // @ts-ignore + expect(FileManager.instance).toBeUndefined(); + }); + + it('should return ARKTS_1_1 if staticApiPath and dynamicApiPath are empty', () => { + FileManager.cleanFileManagerObject(); + FileManager.staticApiPath.clear(); + FileManager.dynamicApiPath.clear(); + FileManager.arkTSModuleMap.clear(); + // Make tsc ignore the access of private member + // @ts-ignore + FileManager.buildConfig = { compileFiles: [] }; + const fm = FileManager.getInstance(); + expect(fm.getLanguageVersionByFilePath('/any/path/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_1); + }); + + it('should return module language if not hybrid', () => { + FileManager.init({ + dependentModuleList: [ + { + packageName: 'modB', + modulePath: '/mock/path/modB', + language: LANGUAGE_VERSION.ARKTS_1_2 + } + ], + externalApiPaths: [], + buildSdkPath: '/mock/sdk', + compileFiles: [] + } as any); + const fm = FileManager.getInstance(); + expect(fm.getLanguageVersionByFilePath('/mock/path/modB/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_2); + }); + + it('should isFirstLineUseStatic returns false when first line is not use static', () => { + jest.spyOn(utils, 'readFirstLineSync').mockReturnValue('not static'); + + // @ts-ignore + expect(FileManager['isFirstLineUseStatic']('anyfile.ets')).toBe(false); + }); + + it('should init does nothing if already initialized', () => { + FileManager.init(mockBuildConfig as any); + const instance = FileManager.getInstance(); + FileManager.init({ + ...mockBuildConfig, + buildSdkPath: '/another/path' + } as any); + expect(FileManager.getInstance()).toBe(instance); + }); + + it('should cleanFileManagerObject does nothing if instance is undefined', () => { + // @ts-ignore + FileManager.instance = undefined; + expect(() => FileManager.cleanFileManagerObject()).not.toThrow(); + }); + + it('should getLanguageVersionByFilePath handles undefined compileFiles', () => { + FileManager.cleanFileManagerObject(); + // @ts-ignore + FileManager.buildConfig = {}; + const fm = FileManager.getInstance(); + expect(fm.getLanguageVersionByFilePath('/any/path/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_1); + }); + + it('should initSDK handles undefined externalApiPath', () => { + // @ts-ignore + expect(() => FileManager.initSDK(undefined, '/sdk/path')).not.toThrow(); + }); +}); \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts b/ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts new file mode 100755 index 0000000000..aa044c0a81 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/generate_arktsconfigTest/generate_arktsconfig.test.ts @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025 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 * as fs from 'fs'; +import path from 'path'; +import { ArkTSConfigGenerator } from '../../../src/build/generate_arktsconfig'; +import { ModuleInfo } from '../../../src/types'; + +jest.mock('../../../src/build/generate_arktsconfig', () => { + const actual = jest.requireActual('../../../src/build/generate_arktsconfig'); + function MockArkTSConfigGenerator(this: any, ...args: any[]) { } + MockArkTSConfigGenerator.prototype = actual.ArkTSConfigGenerator.prototype; + Object.getOwnPropertyNames(actual.ArkTSConfigGenerator).forEach((key) => { + if (!['prototype', 'length', 'name'].includes(key)) { + (MockArkTSConfigGenerator as any)[key] = actual.ArkTSConfigGenerator[key]; + } + }); + + return { + ArkTSConfigGenerator: MockArkTSConfigGenerator, + __esModule: true, + }; +}); + +describe('test generate_arktsconfig.ts file api', () => { + test('test getInstance', () => { + expect(() => { + ArkTSConfigGenerator.getInstance(); + }).toThrow('buildConfig and moduleInfos is required for the first instantiation of ArkTSConfigGenerator.'); + }); + + test('test getOhmurl', () => { + test_getOhmurl(); + }); + + test('test writeArkTSConfigFile', () => { + test_writeArkTSConfigFile(); + }); +}); + + +function test_writeArkTSConfigFile() { + // Mock getInstance to bypass the private constructor + jest.spyOn(ArkTSConfigGenerator, 'getInstance').mockImplementation(() => { + return Object.create(ArkTSConfigGenerator.prototype); + }); + const generator = ArkTSConfigGenerator.getInstance(); + (generator as any).logger = { + printWarn: jest.fn(), + printErrorAndExit: jest.fn(() => { throw new Error('exit'); }) + }; + (generator as any).moduleInfos = new Map(); + + const moduleInfo: ModuleInfo = { + isMainModule: true, + packageName: 'test-pkg', + moduleRootPath: '/tmp/test-module', + moduleType: 'type', + sourceRoots: [], + entryFile: 'index.ets', + arktsConfigFile: 'arktsconfig.json', + compileFileInfos: [], + declgenV1OutPath: undefined, + declgenV2OutPath: undefined, + declgenBridgeCodePath: undefined, + byteCodeHar: false, + staticDepModuleInfos: new Map(), + dynamicDepModuleInfos: new Map(), + }; + + expect(() => { + generator.writeArkTSConfigFile(moduleInfo, false); + }).toThrow('exit'); +} + +function test_getOhmurl() { + const moduleInfo: ModuleInfo = { + isMainModule: true, + packageName: 'example-package', + moduleRootPath: '/path/to/module', + moduleType: 'type', + sourceRoots: ['/src'], + entryFile: 'index.ts', + arktsConfigFile: 'arktsconfig.json', + compileFileInfos: [], + declgenV1OutPath: undefined, + declgenV2OutPath: undefined, + declgenBridgeCodePath: undefined, + byteCodeHar: false, + staticDepModuleInfos: new Map(), + dynamicDepModuleInfos: new Map(), + }; + + const filePath = ''; + const expectedUrl = 'example-package/'; + jest.spyOn(ArkTSConfigGenerator, 'getInstance').mockImplementation(() => { + return Object.create(ArkTSConfigGenerator.prototype); + }); + const generator = ArkTSConfigGenerator.getInstance(); + + expect((generator as any).getOhmurl(filePath, moduleInfo)).toBe(expectedUrl); +} diff --git a/ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts b/ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts new file mode 100755 index 0000000000..90c5bfd82d --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2025 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 { Logger, LogDataFactory, LogData } from '../../../src/logger'; +import { SubsystemCode, ErrorCode } from '../../../src/error_code'; +import { BuildConfig, BUILD_TYPE, BUILD_MODE } from '../../../src/types'; + +function createMockBuildConfig(): BuildConfig { + return { + getHvigorConsoleLogger: jest.fn(() => ({ + printInfo: jest.fn(), + printWarn: jest.fn(), + printDebug: jest.fn(), + printError: jest.fn(), + printErrorAndExit: jest.fn(), + })), + packageName: 'mockpkg', + moduleType: 'shared', + moduleRootPath: '/mock/module', + sourceRoots: [], + byteCodeHar: false, + plugins: {}, + compileFiles: [], + dependentModuleList: [], + buildType: BUILD_TYPE.BUILD, + buildMode: BUILD_MODE.DEBUG, + hasMainModule: true, + arkts: {} as any, + arktsGlobal: {} as any, + declgenV1OutPath: undefined, + declgenV2OutPath: undefined, + declgenBridgeCodePath: undefined, + buildSdkPath: '', + loaderOutPath: '', + cachePath: '', + externalApiPaths: [], + enableDeclgenEts2Ts: false, + } as unknown as BuildConfig; +} + +describe('Logger class', () => { + let logger: Logger; + + beforeEach(() => { + Logger.destroyInstance(); + logger = Logger.getInstance(createMockBuildConfig()); + }); + + afterEach(() => { + Logger.destroyInstance(); + jest.restoreAllMocks(); + }); + + test('getInstance throws if not initialized', () => { + Logger.destroyInstance(); + expect(() => Logger.getInstance()).toThrow('projectConfig is required for the first instantiation.'); + }); + + test('getInstance returns singleton', () => { + const logger1 = Logger.getInstance(createMockBuildConfig()); + const logger2 = Logger.getInstance(); + expect(logger1).toBe(logger2); + }); + + test('destroyInstance resets singleton', () => { + const logger1 = Logger.getInstance(createMockBuildConfig()); + Logger.destroyInstance(); + const logger2 = Logger.getInstance(createMockBuildConfig()); + expect(logger1).not.toBe(logger2); + }); + + test('printInfo calls underlying logger', () => { + const spy = jest.fn(); + (logger as any).loggerMap[SubsystemCode.BUILDSYSTEM].printInfo = spy; + logger.printInfo('info'); + expect(spy).toHaveBeenCalledWith('info'); + }); + + test('printWarn calls underlying logger', () => { + const spy = jest.fn(); + (logger as any).loggerMap[SubsystemCode.BUILDSYSTEM].printWarn = spy; + logger.printWarn('warn'); + expect(spy).toHaveBeenCalledWith('warn'); + }); + + test('printDebug calls underlying logger', () => { + const spy = jest.fn(); + (logger as any).loggerMap[SubsystemCode.BUILDSYSTEM].printDebug = spy; + logger.printDebug('debug'); + expect(spy).toHaveBeenCalledWith('debug'); + }); + + test('printError sets hasErrorOccurred and calls printError', () => { + const spy = jest.fn(); + // insert '001' into the map, for test + (logger as any).loggerMap['001'] = { printError: spy }; + const logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc'); + logger.printError(logData); + expect((logger as any).hasErrorOccurred).toBe(true); + expect(spy).toHaveBeenCalledWith(logData); + }); + + test('printErrorAndExit sets hasErrorOccurred and calls printErrorAndExit', () => { + const spy = jest.fn(); + (logger as any).loggerMap['001'] = { printErrorAndExit: spy }; + const logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc'); + logger.printErrorAndExit(logData); + expect((logger as any).hasErrorOccurred).toBe(true); + expect(spy).toHaveBeenCalledWith(logData); + }); + + test('hasErrors and resetErrorFlag', () => { + expect(logger.hasErrors()).toBe(false); + (logger as any).hasErrorOccurred = true; + expect(logger.hasErrors()).toBe(true); + logger.resetErrorFlag(); + expect(logger.hasErrors()).toBe(false); + }); + + test('isValidErrorCode returns true for 8 digits', () => { + expect((logger as any).isValidErrorCode('12345678')).toBe(true); + expect((logger as any).isValidErrorCode('1234567')).toBe(false); + expect((logger as any).isValidErrorCode('abcdefgh')).toBe(false); + }); + + test('getLoggerFromSubsystemCode throws for invalid code', () => { + expect(() => (logger as any).getLoggerFromSubsystemCode('INVALID')).toThrow('Invalid subsystemCode.'); + }); + + test('getLoggerFromSubsystemCode returns logger', () => { + const fakeLogger = { + printInfo: jest.fn(), + printWarn: jest.fn(), + printDebug: jest.fn(), + printError: jest.fn(), + printErrorAndExit: jest.fn() + }; + (logger as any).loggerMap['FKLGR'] = fakeLogger; + expect((logger as any).getLoggerFromSubsystemCode('FKLGR')).toBe(fakeLogger); + }); + + test('getLoggerFromErrorCode throws for invalid errorCode', () => { + expect(() => (logger as any).getLoggerFromErrorCode('badcode')).toThrow('Invalid errorCode.'); + }); + + test('getLoggerFromErrorCode returns logger for valid code', () => { + const fakeLogger = { + printInfo: jest.fn(), + printWarn: jest.fn(), + printDebug: jest.fn(), + printError: jest.fn(), + printErrorAndExit: jest.fn(), + }; + (logger as any).loggerMap['001'] = fakeLogger; + expect((logger as any).getLoggerFromErrorCode('00100001')).toBe(fakeLogger); + }); +}); + +describe('LogDataFactory and LogData', () => { + test('LogDataFactory.newInstance creates LogData', () => { + const logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc', 'cause', 'pos', ['sol1', 'sol2'], { foo: 'bar' }); + expect(logData).toBeInstanceOf(LogData); + expect(logData.code).toBe('00100001'); + expect(logData.description).toBe('desc'); + expect(logData.cause).toBe('cause'); + expect(logData.position).toBe('pos'); + expect(logData.solutions).toEqual(['sol1', 'sol2']); + expect(logData.moreInfo).toEqual({ foo: 'bar' }); + }); + + test('LogData.toString formats output', () => { + const logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc', 'cause', 'pos', ['sol1'], { foo: 'bar' }); + const str = logData.toString(); + expect(str).toContain('ERROR Code: 00100001 desc'); + expect(str).toContain('Error Message: cause pos'); + expect(str).toContain('> sol1'); + expect(str).toContain('More Info:'); + expect(str).toContain('FOO: bar'); + }); + + test('LogData.toString omits empty fields', () => { + const logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc', '', '', [''], undefined); + const str = logData.toString(); + expect(str).toContain('ERROR Code: 00100001 desc'); + expect(str).not.toContain('Error Message:'); + expect(str).not.toContain('More Info:'); + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config.json new file mode 100755 index 0000000000..000e0a1d00 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build1", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "index.ets", + "language": "1.2", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.2" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config1.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config1.json new file mode 100755 index 0000000000..0f4b843bce --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config1.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + "frameworkMode": true, + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "/root/testSuite/ets_frontend/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "language": "1.2", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.2" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config2.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config2.json new file mode 100755 index 0000000000..5db55059d6 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config2.json @@ -0,0 +1,26 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/Calc.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + ], + "enableDeclgenEts2Ts": true, + "declgenBridgeCodePath": "test/ut/mock/demo_1.2_dep_hsp1.2/declgen/default/declgenBridgeCode", + "declgenV1OutPath": "test/ut/mock/demo_1.2_dep_hsp1.2/declgen/default/declgenV1", + "declgenV2OutPath": "test/ut/mock/demo_1.2_dep_hsp1.2/declgen/default/declgenV2" +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config3.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config3.json new file mode 100755 index 0000000000..18f3232dbd --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config3.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "language": "1.2", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.2" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config4.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config4.json new file mode 100755 index 0000000000..9b4fa009f5 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config4.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "index.ets", + "language": "1.2", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.2" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config5.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config5.json new file mode 100755 index 0000000000..9b4fa009f5 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config5.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "index.ets", + "language": "1.2", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.2" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config6.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config6.json new file mode 100755 index 0000000000..b696a99c42 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config6.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "language": "1.1", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.1" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config7.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config7.json new file mode 100755 index 0000000000..12a2810108 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config7.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "language": "hybrid", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "hybrid" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config8.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config8.json new file mode 100755 index 0000000000..d4ca4121f8 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config8.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "har", + + "buildType": "build", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + "hasMainModule": true, + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "language": "1.2", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.2" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config9.json b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config9.json new file mode 100755 index 0000000000..7c21410ce6 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config9.json @@ -0,0 +1,47 @@ +{ + "compileFiles": [ + "test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets", + "test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets" + ], + + "packageName": "entry", + "moduleType": "shared", + + "buildType": "build", + "buildMode": "Debug", + + "moduleRootPath": "test/ut/mock/demo_1.2_dep_hsp1.2/entry/", + "sourceRoots": ["./", "src/main1/ets"], + + "loaderOutPath": "test/ut/mock/dist", + "cachePath": "test/ut/mock/dist/cache", + + "enableDeclgenEts2Ts": false, + "declgenV1OutPath": "test/ut/mock/dist/declgen/decl_ets", + "declgenBridgeCodePath": "test/ut/mock/dist/declgen/ets", + + "buildSdkPath": "test/mock_sdk/", + "maxWorkers": 4, + "dependentModuleList": [ + { + "packageName": "harA", + "moduleName": "harA", + "moduleType": "har", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/harA", + "sourceRoots": ["./"], + "entryFile": "index.ets", + "language": "1.2", + "dependencies": [ "hspA" ] + }, + { + "packageName": "hspA", + "moduleName": "hspA", + "moduleType": "shared", + "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "sourceRoots": ["./"], + "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "language": "1.2" + } + ] +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets new file mode 100755 index 0000000000..750382839f --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 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 {sub} from 'harA' + +function main() { + let a: string = "hello world" + console.log(a) + let ret = sub(2, 1) + console.log(ret) +} diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets new file mode 100755 index 0000000000..46ec690949 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 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. + */ + +export { sub } from './sub' \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets new file mode 100755 index 0000000000..ecf28ced52 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets @@ -0,0 +1,7 @@ +import { add } from 'hspA' + +export function sub(a: number, b: number) { + let sum = add(a, b) + console.log(sum) + return a - b +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/Calc.ets b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/Calc.ets new file mode 100755 index 0000000000..5cd39b2a1d --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/Calc.ets @@ -0,0 +1,3 @@ +export function add(a: number, b: number) { + return a + b +} \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets new file mode 100755 index 0000000000..9309103627 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets @@ -0,0 +1 @@ +export { add } from './Calc' \ No newline at end of file diff --git a/ets2panda/driver/build_system/test/ut/plugins_driverTest/plugins_driver.test.ts b/ets2panda/driver/build_system/test/ut/plugins_driverTest/plugins_driver.test.ts new file mode 100755 index 0000000000..b5ade7c42b --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/plugins_driverTest/plugins_driver.test.ts @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2025 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 { PluginDriver, PluginHook} from '../../../src/plugins/plugins_driver'; + +jest.mock('../../../src/plugins/plugins_driver', () => { + const actual = jest.requireActual('../../../src/plugins/plugins_driver'); + + function MockPluginDriver(this: any, ...args: any[]) { + this.plugins = {}; + } + + MockPluginDriver.prototype = actual.PluginDriver.prototype; + + Object.getOwnPropertyNames(actual.PluginDriver).forEach((key) => { + if (!['prototype', 'length', 'name'].includes(key)) { + (MockPluginDriver as any)[key] = (actual.PluginDriver as any)[key]; + } + }); + + return { + ...actual, + PluginDriver: MockPluginDriver, + __esModule: true, + }; +}); +type PluginHandlerFunction = () => void; + +type PluginHandlerObject = { + order: 'pre' | 'post' | undefined + handler: PluginHandlerFunction +}; +type PluginHandler = PluginHandlerFunction | PluginHandlerObject; +interface Plugins { + name: string, + afterNew?: PluginHandler, + parsed?: PluginHandler, + scopeInited?: PluginHandler, + checked?: PluginHandler, + lowered?: PluginHandler, + asmGenerated?: PluginHandler, + binGenerated?: PluginHandler, + clean?: PluginHandler, +} + +jest.mock('path/to/valid/plugin', () => { + return { + validPlugin: () => { }, + }; +}, { virtual: true }); + +jest.mock('path/to/invalid/plugin', () => { + return { + invalidPlugin: {}, + }; +}, { virtual: true }); + +describe('test plugins_driver.ts file api', () => { + test('test initPlugins001', () => { + test_initPlugins001(); + }); + + test('test initPlugins002', () => { + test_initPlugins002(); + }); + + test('test getSortedPlugins', () => { + test_getSortedPlugins(); + }); +}); + +function test_initPlugins001() { + const driver = new PluginDriver(); + const result = driver.initPlugins(undefined as any); + expect(result).toBeUndefined(); +} + +function test_initPlugins002() { + const driver = new PluginDriver(); + const mockProjectConfig = { + plugins: { + invalidPlugin: 'path/to/invalid/plugin', + }, + compileFiles: [], + dependentModuleList: [], + buildType: 'build', + buildMode: 'debug', + packageName: 'test', + moduleRootPath: '/test/path', + sourceRoots: ['./'], + loaderOutPath: './dist', + cachePath: './dist/cache', + moduleType: 'har', + hasMainModule: false, + byteCodeHar: false, + arkts: {}, + arktsGlobal: {}, + declgenV1OutPath: './dist/declgen', + declgenV2OutPath: './dist/declgen/v2', + buildSdkPath: './sdk', + externalApiPaths: [], + enableDeclgenEts2Ts: false + }; + + let error; + try { + driver.initPlugins(mockProjectConfig as any); + } catch (e) { + error = e; + } + + expect(error).not.toBeUndefined(); +} + +function test_getSortedPlugins() { + const driver = new PluginDriver(); + const hook = PluginHook.PARSED; + + driver['allPlugins'] = new Map([ + [ + 'plugin1', + { + name: 'plugin1', + parsed: { order: 'pre', handler: jest.fn() }, + }, + ], + [ + 'plugin2', + { + name: 'plugin2', + parsed: jest.fn(), + }, + ], + [ + 'plugin3', + { + name: 'plugin3', + parsed: { order: 'post', handler: jest.fn() }, + }, + ], + ]); + + const result = driver['getSortedPlugins'](hook); + + expect(result).toEqual([ + { name: 'plugin1', handler: expect.any(Function) }, + { name: 'plugin2', handler: expect.any(Function) }, + { name: 'plugin3', handler: expect.any(Function) }, + ]); +} diff --git a/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts b/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts new file mode 100755 index 0000000000..aac65316a8 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2025 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 { ensurePathExists, getFileHash } from '../../../src/utils'; +import * as fs from 'fs'; +import * as path from 'path'; +import { + ErrorCode, +} from '../../../src/error_code'; +import { + isWindows, isLinux, isMac, + changeFileExtension, changeDeclgenFileExtension, + toUnixPath, readFirstLineSync, safeRealpath +} from '../../../src/utils'; +import { DECL_ETS_SUFFIX } from '../../../src/pre_define'; + +describe('test getFileHash', () => { + const testFile = path.join(__dirname, 'testFile.txt'); + + beforeEach(() => { + fs.writeFileSync(testFile, 'Hello, World!', 'utf8'); + }); + + afterEach(() => { + if (fs.existsSync(testFile)) { + fs.unlinkSync(testFile); + } + }); + + it('test getFileHash001', () => { + test_getFileHash001(testFile); + }); + + it('test getFileHash002', () => { + test_getFileHash002(); + }); +}); + +describe('test ensurePathExists', () => { + const testDir = path.join(__dirname, 'testDir'); + + beforeEach(() => { + if (fs.existsSync(testDir)) { + fs.rmdirSync(testDir, { recursive: true }); + } + }); + + afterEach(() => { + if (fs.existsSync(testDir)) { + fs.rmdirSync(testDir, { recursive: true }); + } + }); + + it('test ensurePathExists001', () => { + test_ensurePathExists001(testDir); + }); + + it('test ensurePathExists002', () => { + test_ensurePathExists002(testDir); + }); +}); + +describe('utils', () => { + describe('isWindows/isLinux/isMac', () => { + it('should detect Linux', () => { + expect(isLinux()).toBe(true); + expect(isWindows()).toBe(false); + expect(isMac()).toBe(false); + }); + }); + + describe('changeFileExtension', () => { + it('should change extension when originExt is empty', () => { + expect(changeFileExtension('a/b/c.txt', '.js')).toBe('a/b/c.js'); + }); + it('should change extension when originExt is provided', () => { + expect(changeFileExtension('a/b/c.txt', '.js', '.txt')).toBe('a/b/c.js'); + }); + }); + + describe('changeDeclgenFileExtension', () => { + it('should use DECL_ETS_SUFFIX branch', () => { + const file = `foo${DECL_ETS_SUFFIX}`; + expect(changeDeclgenFileExtension(file, '.ts')).toBe('foo.ts'); + }); + it('should use default branch', () => { + expect(changeDeclgenFileExtension('foo.ets', '.ts')).toBe('foo.ts'); + }); + }); + + describe('toUnixPath', () => { + it('should replace backslashes with slashes', () => { + expect(toUnixPath('a\\b\\c')).toBe('a/b/c'); + }); + }); +}); + +describe('readFirstLineSync', () => { + const testFile = path.join(__dirname, 'testReadFirstLine.txt'); + + afterEach(() => { + if (fs.existsSync(testFile)) { + fs.unlinkSync(testFile); + } + }); + + it('should read first line of a file with single line', () => { + const content = 'Hello, World!'; + fs.writeFileSync(testFile, content, 'utf8'); + + const result = readFirstLineSync(testFile); + expect(result).toBe('Hello, World!'); + }); + + it('should read only first line of a multi-line file', () => { + const content = 'First line\nSecond line\nThird line'; + fs.writeFileSync(testFile, content, 'utf8'); + + const result = readFirstLineSync(testFile); + expect(result).toBe('First line'); + }); + + it('should trim the first line', () => { + const content = ' Whitespace around \nSecond line'; + fs.writeFileSync(testFile, content, 'utf8'); + + const result = readFirstLineSync(testFile); + expect(result).toBe('Whitespace around'); + }); + + it('should handle CRLF line endings', () => { + const content = 'Windows line\r\nSecond line'; + fs.writeFileSync(testFile, content, 'utf8'); + + const result = readFirstLineSync(testFile); + expect(result).toBe('Windows line'); + }); + + it('should return empty string for empty file', () => { + fs.writeFileSync(testFile, '', 'utf8'); + + const result = readFirstLineSync(testFile); + expect(result).toBe(''); + }); + + it('should throw error for non-existent file', () => { + const nonExistentFile = path.join(__dirname, 'nonExistentFile.txt'); + + expect(() => { + readFirstLineSync(nonExistentFile); + }).toThrow(); + }); +}); + +describe('safeRealpath', () => { + it('test safeRealpath001', () => { + const testDir = path.join(__dirname); + const mockLogger = { printInfo: jest.fn(), printError: jest.fn() }; + + const result = safeRealpath(testDir, mockLogger as any); + expect(result).toBe(fs.realpathSync(testDir)); + expect(mockLogger.printError).not.toHaveBeenCalled(); + }); + + it('test safeRealpath002', () => { + const nonExistentPath = path.join(__dirname, 'non-existent-directory'); + const mockLogger = { printInfo: jest.fn(), printError: jest.fn() }; + + expect(() => { + safeRealpath(nonExistentPath, mockLogger as any); + }).toThrow(); + + expect(mockLogger.printError).toHaveBeenCalledWith( + expect.objectContaining({ + code: ErrorCode.BUILDSYSTEM_PATH_RESOLVE_FAIL, + description: expect.stringContaining(`Error resolving path "${nonExistentPath}"`) + }) + ); + }); +}); + +function test_getFileHash001(testFile: string) { + const hash = getFileHash(testFile); + expect(hash).toBe('dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f'); +} + +function test_getFileHash002() { + let error: Error | undefined; + try { + getFileHash('/nonexistent/file.txt'); + } catch (e) { + error = e as Error; + } + expect(error).not.toBe(undefined); +} + +function test_ensurePathExists001(testDir: string) { + expect(fs.existsSync(testDir)).toBe(false); + ensurePathExists(path.join(testDir, 'file.txt')); + expect(fs.existsSync(testDir)).toBe(true); +} + +function test_ensurePathExists002(testDir: string) { + fs.mkdirSync(testDir, { recursive: true }); + let error; + try { + ensurePathExists(path.join(testDir, 'file.txt')); + } catch (e) { + error = e; + } + expect(error).toBe(undefined); +} -- Gitee