diff --git a/ets2panda/driver/build_system/test/e2e/checkHash.test.ts b/ets2panda/driver/build_system/test/e2e/checkHash.test.ts index 7200e42e3d521b7eb4c89a840a4e9083976172d1..ea92580bb90456a4980b700ba51f98f04c07e91e 100755 --- a/ets2panda/driver/build_system/test/e2e/checkHash.test.ts +++ b/ets2panda/driver/build_system/test/e2e/checkHash.test.ts @@ -13,6 +13,9 @@ * limitations under the License. */ +// Depends on hardware environment, set a longer timeout +jest.setTimeout(20000); + import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; diff --git a/ets2panda/driver/build_system/test/e2e/compile.test.ts b/ets2panda/driver/build_system/test/e2e/compile.test.ts index 74c47e6b5d68df795e5e8a72859d42c8ec02621b..a2e6d8f9b9bd6944386d76bc37967d2e6825d25a 100755 --- a/ets2panda/driver/build_system/test/e2e/compile.test.ts +++ b/ets2panda/driver/build_system/test/e2e/compile.test.ts @@ -13,6 +13,9 @@ * limitations under the License. */ +// Depends on hardware environment, set a longer timeout +jest.setTimeout(20000); + import { execFile } from 'child_process'; import { promisify } from 'util'; import fs from 'fs'; 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 index 3213406d6f15d5cf53ed0b1e03c7f43b717aeae1..c6468d043a7a0525bde848b826bf225140a83479 100755 --- 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 @@ -13,14 +13,9 @@ * 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'; @@ -74,125 +69,1919 @@ beforeAll(() => { execSync('rimraf test/ut/mock/dist', { stdio: 'pipe' }); }); -function main(configFilePath?: string): void { - const buildConfigPath = configFilePath; +// Test the functions of the base_mode.ts file. +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 collectCompileFiles when test declaration files skip branch', () => { + test_collectCompileFiles_decl_ets_skip(); + }); + + test('test collectCompileFiles when test bytecode HAR branch', () => { + test_collectCompileFiles_bytecode_har(); + }); + + test('test collectCompileFiles when 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(); + }); + + test('test generateDeclaration method', () => { + return test_generateDeclaration(); + }); + + test('test run method', () => { + test_runMethod(); + }); + + test('test declgen method', () => { + test_declgen_method(); + }); + + test('test getDependentModules with missing module', () => { + test_getDependentModules_missing_module(); + }); + + test('test collectDependencyModules language branches', () => { + test_collectDependencyModules_language_branches(); + }); + + test('test runConcurrent method', () => { + test_runConcurrent(); + }); + + test('test processAfterCompile method', () => { + test_processAfterCompile(); + }); + + test('test checkAllTasksDone method', () => { + test_checkAllTasksDone(); + }); + + test('test initCompileQueues method', () => { + test_initCompileQueues(); + }); + + test('test addJobToQueues method', () => { + test_addJobToQueues(); + }); + + test('test dealWithDependants method', () => { + test_dealWithDependants(); + }); + + test('test collectCompileJobs method', () => { + test_collectCompileJobs(); + }); + + test('test getJobDependants method', () => { + test_getJobDependants(); + }); + + test('test getJobDependencies method', () => { + test_getJobDependencies(); + }); + + test('test setupWorkerMessageHandler', () => { + test_setupWorkerMessageHandler(); + }); + + test('test getSerializableConfig handles bigint values', () => { + test_getSerializableConfig(); + }); + + test('test collectDependentCompileFiles', () => { + test_collectDependentCompileFiles(); + }); + + test('test isFileChanged method branches', () => { + test_isFileChanged(); + }); + + test('test loadHashCache method branches', () => { + test_loadHashCache(); + }); + + test('test updateDependantJobs method', () => { + test_updateDependantJobs(); + }); + + test('test collectModuleInfos branches001', () => { + test_collectModuleInfos001(); + }); + + test('test collectCompileFiles enableDeclgenEts2Ts false branch', () => { + test_collectCompileFiles_enableDeclgenEts2Ts_false(); + }); + + test('test collectAbcFileFromByteCodeHar_missing_abc_path', () => { + test_collectAbcFileFromByteCodeHar_missing_abc_path(); + }); + + test('test collectDependentCompileFiles isFileChanged branch', () => { + test_collectDependentCompileFiles_isFileChanged_branch(); + }); + + test('collectCompileJobs should skip entry files not in compileFiles', () => { + test_collectCompileJobs_should_skip_entry_files_not_in_compileFiles(); + }); + +}); + +function test_collectCompileJobs_should_skip_entry_files_not_in_compileFiles() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + } + + const baseMode = new TestBaseMode(mockConfig as any); + + const entryFile = '/path/to/entry.ets'; + const includedFile = '/path/to/included.ets'; + + (baseMode as any).entryFiles = new Set([entryFile]); + (baseMode as any).compileFiles = new Set(); + + const dependencyFileMap: { + dependencies: { [key: string]: string[] }, + dependants: { [key: string]: string[] } + } = { + dependencies: { + [entryFile]: ['dependency1.ets'] + }, + dependants: { + [entryFile]: ['dependant1.ets'] + } + }; + (baseMode as any).dependencyFileMap = dependencyFileMap; + + const cycleGroups = new Map(); + jest.spyOn(baseMode as any, 'findStronglyConnectedComponents').mockReturnValue(cycleGroups); + + jest.spyOn(baseMode as any, 'getJobDependencies').mockReturnValue(new Set()); + jest.spyOn(baseMode as any, 'getJobDependants').mockReturnValue(new Set()); + jest.spyOn(baseMode as any, 'dealWithDependants').mockImplementation(() => { }); + jest.spyOn(baseMode as any, 'createExternalProgramJob').mockImplementation(() => { }); + jest.spyOn(baseMode as any, 'getAbcJobId').mockImplementation((file) => `abc_${file}`); + jest.spyOn(baseMode as any, 'getExternalProgramJobId').mockImplementation((file) => `external_${file}`); + + const jobs = {}; + + (baseMode as any).collectCompileJobs(jobs); + + expect(Object.keys(jobs).length).toBe(0); + expect((baseMode as any).getJobDependencies).not.toHaveBeenCalledWith(['dependency1.ets'], expect.anything()); + expect((baseMode as any).getJobDependants).not.toHaveBeenCalledWith(['dependant1.ets'], expect.anything()); + + (baseMode as any).entryFiles.add(includedFile); + (baseMode as any).compileFiles.add(includedFile); + + dependencyFileMap.dependencies = { + ...dependencyFileMap.dependencies, + [includedFile]: [] + }; + + dependencyFileMap.dependants = { + ...dependencyFileMap.dependants, + [includedFile]: [] + }; + + jest.clearAllMocks(); +} + +function test_collectDependentCompileFiles_isFileChanged_branch() { + 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", + dependentModuleList: [] + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testCollectDependentCompileFiles(): void { + (this as any).collectDependentCompileFiles(); + } + + public setIsFileChanged(fn: (file: string, abcFile: string) => boolean): void { + (this as any).isFileChanged = fn; + } + } + + const fs = require('fs'); + jest.spyOn(fs, 'statSync').mockReturnValue({ mtimeMs: Date.now() }); + jest.spyOn(fs, 'readFileSync').mockReturnValue('mocked file content'); + + const utils = require('../../../src/utils'); + jest.spyOn(utils, 'getFileHash').mockReturnValue("test-hash-123"); + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + + const baseMode = new TestBaseMode(mockConfig as any); + + const testFile1 = "/test/path/file1.ets"; + const testFile2 = "/test/path/file2.ets"; + + (baseMode as any).entryFiles = new Set([testFile1, testFile2]); + (baseMode as any).cacheDir = "./dist/cache"; + (baseMode as any).hashCache = {}; + (baseMode as any).abcFiles = new Set(); + (baseMode as any).compileFiles = new Map(); + (baseMode as any).allFiles = new Map(); + + (baseMode as any).moduleInfos = new Map(); + (baseMode as any).moduleInfos.set("test", { + packageName: "test", + moduleType: "har", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + arktsConfigFile: "./dist/cache/test/config.json", + compileFileInfos: [] + }); + + (baseMode as any).dependencyFileMap = { + dependencies: { + [testFile1]: [], + [testFile2]: [testFile1] + }, + dependants: { + [testFile1]: [testFile2], + [testFile2]: [] + } + }; + + baseMode.setIsFileChanged(() => true); + (baseMode as any).isBuildConfigModified = false; + + baseMode.testCollectDependentCompileFiles(); + + expect((baseMode as any).compileFiles.size).toBe(2); + expect((baseMode as any).compileFiles.has(testFile1)).toBe(true); + expect((baseMode as any).compileFiles.has(testFile2)).toBe(true); + + (baseMode as any).compileFiles.clear(); + (baseMode as any).abcFiles.clear(); + jest.restoreAllMocks(); +} + +function test_collectAbcFileFromByteCodeHar_missing_abc_path() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const LogDataFactory = { + newInstance: jest.fn().mockReturnValue({ + code: "11410101", + description: "abc file not found in bytecode har test-module." + }) + }; + + const ErrorCode = { + BUILDSYSTEM_ABC_FILE_MISSING_IN_BCHAR: '11410101' + }; + + const mockConfig = { + packageName: "main-package", + moduleType: OHOS_MODULE_TYPE.SHARED, + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + dependentModuleList: [] + }; + + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testCollectAbcFileFromByteCodeHar(): void { + this.collectAbcFileFromByteCodeHar(); + } + } + + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + + (global as any).LogDataFactory = LogDataFactory; + (global as any).ErrorCode = ErrorCode; + + const baseMode = new TestBaseMode(mockConfig as any); + (baseMode as any).abcFiles = new Set(); + + (baseMode as any).moduleInfos = new Map(); + (baseMode as any).moduleInfos.set("test-module", { + packageName: "test-module", + moduleType: OHOS_MODULE_TYPE.HAR, + byteCodeHar: true, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + arktsConfigFile: "./dist/cache/test/config.json", + compileFileInfos: [] + }); + + (baseMode as any).moduleInfos.set("test-module-2", { + packageName: "test-module-2", + moduleType: OHOS_MODULE_TYPE.HAR, + byteCodeHar: true, + abcPath: "/test/path/module2.abc", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + arktsConfigFile: "./dist/cache/test/config.json", + compileFileInfos: [] + }); + + baseMode.testCollectAbcFileFromByteCodeHar(); + expect((baseMode as any).abcFiles.has("/test/path/module2.abc")).toBe(true); + expect((baseMode as any).abcFiles.size).toBe(1); + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; +} + +function test_collectCompileFiles_enableDeclgenEts2Ts_false() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockConfig = { + packageName: "test", + moduleType: OHOS_MODULE_TYPE.HAR, + buildMode: BUILD_MODE.DEBUG, + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + enableDeclgenEts2Ts: false, + 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).entryFiles = new Set(['/test/path/file1.ets']); + (baseMode as any).moduleInfos = new Map(); + (baseMode as any).abcFiles = new Set(); + (baseMode as any).hashCache = {}; + (baseMode as any).compileFiles = new Map(); + + baseMode.testCollectCompileFiles(); +} + +class TestBaseModeMock extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + public getMainModuleInfo(): ModuleInfo { + const path = require('path'); + const ARKTSCONFIG_JSON_FILE = 'arktsconfig.json'; + return { + isMainModule: true, + packageName: this.packageName, + moduleRootPath: this.moduleRootPath, + sourceRoots: this.sourceRoots, + arktsConfigFile: path.resolve(this.cacheDir, this.packageName, ARKTSCONFIG_JSON_FILE), + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + entryFile: "index.ets", + byteCodeHar: false, + declgenV1OutPath: path.resolve(this.cacheDir, "declgen"), + declgenV2OutPath: path.resolve(this.cacheDir, "declgen/v2"), + declgenBridgeCodePath: path.resolve(this.cacheDir, "bridge") + }; + } + public testCollectModuleInfos(): void { + return (this as any).collectModuleInfos(); + } +} + +function test_collectModuleInfos1(mockLogger: any, LogDataFactory: any) { + const mockConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + hasMainModule: true, + dependentModuleList: [] + }; + const baseMode = new TestBaseModeMock(mockConfig as any); + (baseMode as any).logger = mockLogger; + (baseMode as any).cacheDir = "./dist/cache"; + baseMode.testCollectModuleInfos(); + LogDataFactory.newInstance.mockClear(); + mockLogger.printError.mockClear(); +} + +function test_collectModuleInfos2(mockLogger: any, LogDataFactory: any) { + const mockConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + hasMainModule: true, + dependentModuleList: [ + { + packageName: "dep1", + sourceRoots: ["./"], + entryFile: "index.ets" + } + ] + }; + + const baseMode = new TestBaseModeMock(mockConfig as any); + (baseMode as any).logger = mockLogger; + (baseMode as any).cacheDir = "./dist/cache"; + + baseMode.testCollectModuleInfos(); + LogDataFactory.newInstance.mockClear(); + mockLogger.printError.mockClear(); + +} + +function test_collectModuleInfos3(mockLogger: any, LogDataFactory: any) { + const mockConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + hasMainModule: true, + dependentModuleList: [ + { + packageName: "dep2", + modulePath: "/test/dep2", + entryFile: "index.ets" + } + ] + }; + + const baseMode = new TestBaseModeMock(mockConfig as any); + (baseMode as any).logger = mockLogger; + (baseMode as any).cacheDir = "./dist/cache"; + + baseMode.testCollectModuleInfos(); + LogDataFactory.newInstance.mockClear(); + mockLogger.printError.mockClear(); +} + +function test_collectModuleInfos4(mockLogger: any, LogDataFactory: any) { + const mockConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["test.ets"], + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + hasMainModule: true, + dependentModuleList: [ + { + packageName: "dep3", + modulePath: "/test/dep3", + sourceRoots: ["./"] + } + ] + }; + + const baseMode = new TestBaseModeMock(mockConfig as any); + (baseMode as any).logger = mockLogger; + (baseMode as any).cacheDir = "./dist/cache"; + baseMode.testCollectModuleInfos(); + LogDataFactory.newInstance.mockClear(); + mockLogger.printError.mockClear(); +} + +function test_collectModuleInfos001() { + const mockLogger = { printError: jest.fn(), printInfo: jest.fn() }; + const LogDataFactory = { newInstance: jest.fn().mockReturnValue({ code: "123", message: "Test error" }) }; + const ErrorCode = { + BUILDSYSTEM_MODULE_INFO_NOT_CORRECT_FAIL: '11410003', + BUILDSYSTEM_DEPENDENT_MODULE_INFO_NOT_CORRECT_FAIL: '11410004' + }; + const path = require('path'); + const ARKTSCONFIG_JSON_FILE = 'arktsconfig.json'; + (global as any).LogDataFactory = LogDataFactory; + (global as any).ErrorCode = ErrorCode; + + test_collectModuleInfos1(mockLogger as any, LogDataFactory as any); + test_collectModuleInfos2(mockLogger as any, LogDataFactory as any); + test_collectModuleInfos3(mockLogger as any, LogDataFactory as any); + test_collectModuleInfos4(mockLogger as any, LogDataFactory as any); + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; +} + +function test_updateDependantJobs1(baseMode: any) { + const jobId = "job1"; + const processingJobs = new Set([jobId, "job2"]); + const jobs: Record = { + "job1": { + id: "job1", + dependencies: [], + dependants: ["job2", "job3"], + fileList: ["/test/file1.ets"], + isAbcJob: true + }, + "job2": { + id: "job2", + dependencies: ["job1", "job4"], + dependants: [], + fileList: ["/test/file2.ets"], + isAbcJob: true + }, + "job3": { + id: "job3", + dependencies: ["job1"], + dependants: [], + fileList: ["/test/file3.ets"], + isAbcJob: true + } + }; + + const queues: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + + baseMode.testUpdateDependantJobs(jobId, processingJobs, jobs, queues); + + expect(processingJobs.has(jobId)).toBe(false); + expect(jobs["job2"].dependencies).not.toContain("job1"); + expect(jobs["job2"].dependencies).toContain("job4"); + expect(jobs["job3"].dependencies.length).toBe(0); + expect((baseMode as any).addJobToQueues).toHaveBeenCalledWith(jobs["job3"], queues); +} + +function test_updateDependantJobs2(global: any, baseMode: any) { + (global as any).finishedJob = []; + (baseMode as any).addJobToQueues.mockClear(); + + const jobId = "job5"; + const processingJobs = new Set([jobId]); + const jobs: Record = { + "job5": { + id: "job5", + dependencies: [], + dependants: ["job6", "nonExistingJob"], + fileList: ["/test/file5.ets"], + isAbcJob: true + }, + "job6": { + id: "job6", + dependencies: ["job5"], + dependants: [], + fileList: ["/test/file6.ets"], + isAbcJob: true + } + }; + + const queues: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + + baseMode.testUpdateDependantJobs(jobId, processingJobs, jobs, queues); + + expect(processingJobs.has(jobId)).toBe(false); + expect((baseMode as any).addJobToQueues).toHaveBeenCalledWith(jobs["job6"], queues); +} + +function test_updateDependantJobs3(global: any, baseMode: any) { + (global as any).finishedJob = []; + (baseMode as any).addJobToQueues.mockClear(); + + const jobId = "job7"; + const processingJobs = new Set([jobId]); + const jobs: Record = { + "job7": { + id: "job7", + dependencies: [], + dependants: ["job8"], + fileList: ["/test/file7.ets"], + isAbcJob: true + }, + "job8": { + id: "job8", + dependencies: ["job9"], + dependants: [], + fileList: ["/test/file8.ets"], + isAbcJob: true + } + }; + + const queues: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + + baseMode.testUpdateDependantJobs(jobId, processingJobs, jobs, queues); + + expect(jobs["job8"].dependencies).toEqual(["job9"]); + expect((baseMode as any).addJobToQueues).not.toHaveBeenCalled(); +} + +function test_updateDependantJobs() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + (global as any).finishedJob = []; + class TestBuildMode extends BuildMode { + public testUpdateDependantJobs(jobId: string, processingJobs: Set, jobs: Record, queues: Queues): void { + return (this as any).updateDependantJobs(jobId, processingJobs, jobs, queues); + } + } + const baseMode = new TestBuildMode(mockConfig as any); + (baseMode as any).addJobToQueues = jest.fn(); + test_updateDependantJobs1(baseMode as any); + test_updateDependantJobs2(global as any, baseMode as any); + test_updateDependantJobs3(global as any, baseMode as any); + + delete (global as any).finishedJob; +} + +function test_loadHashCache() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + const fs = require('fs'); + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const mockLogData = { code: "123", message: "Test error" }; + + const LogDataFactory = { + newInstance: jest.fn().mockReturnValue(mockLogData) + }; + + const ErrorCode = { + BUILDSYSTEM_LOAD_HASH_CACHE_FAIL: '11410100' + }; + + class TestBuildMode extends BuildMode { + public testLoadHashCache(): Record { + return (this as any).loadHashCache(); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + (baseMode as any).logger = mockLogger; + (baseMode as any).hashCacheFile = "/test/cache/hash_cache.json"; + + (global as any).LogDataFactory = LogDataFactory; + (global as any).ErrorCode = ErrorCode; + + jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false); + let result = baseMode.testLoadHashCache(); + expect(result).toEqual({}); + + jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true); + jest.spyOn(fs, 'readFileSync').mockReturnValueOnce('{"file1.ets":"hash1","file2.ets":"hash2"}'); + result = baseMode.testLoadHashCache(); + expect(result).toEqual({ + "file1.ets": "hash1", + "file2.ets": "hash2" + }); + + jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true); + jest.spyOn(fs, 'readFileSync').mockImplementationOnce(() => { + throw new Error("File read error"); + }); + result = baseMode.testLoadHashCache(); + expect(result).toEqual({}); + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + jest.restoreAllMocks(); +} + +function test_isFileChanged() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBuildMode extends BuildMode { + public testIsFileChanged(etsFilePath: string, abcFilePath: string): boolean { + return (this as any).isFileChanged(etsFilePath, abcFilePath); + } + } + + const fs = require('fs'); + const existsSyncSpy = jest.spyOn(fs, 'existsSync'); + const statSyncSpy = jest.spyOn(fs, 'statSync'); + + (global as any).getFileHash = jest.fn(); + + const baseMode = new TestBuildMode(mockConfig as any); + (baseMode as any).hashCache = {}; + + existsSyncSpy.mockReturnValueOnce(false); + let result = baseMode.testIsFileChanged('/test/file1.ets', '/test/file1.abc'); + expect(result).toBe(true); + expect(existsSyncSpy).toHaveBeenCalledWith('/test/file1.abc'); + expect(statSyncSpy).not.toHaveBeenCalled(); + + existsSyncSpy.mockReturnValueOnce(true); + statSyncSpy.mockReturnValueOnce({ mtimeMs: 200 }); + statSyncSpy.mockReturnValueOnce({ mtimeMs: 100 }); + result = baseMode.testIsFileChanged('/test/file2.ets', '/test/file2.abc'); + expect(result).toBe(true); + expect(statSyncSpy).toHaveBeenCalledWith('/test/file2.ets'); + expect(statSyncSpy).toHaveBeenCalledWith('/test/file2.abc'); + + jest.restoreAllMocks(); + delete (global as any).getFileHash; +} + +function test_collectDependentCompileFiles() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn() + }; + + const LogDataFactory = { + newInstance: jest.fn().mockReturnValue({ code: "123", message: "Test error" }) + }; + + const ErrorCode = { + BUILDSYSTEM_Dependency_Analyze_FAIL: '11410001', + BUILDSYSTEM_FILE_NOT_BELONG_TO_ANY_MODULE_FAIL: '11410002' + }; + + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBuildMode extends BuildMode { + public testCollectDependentCompileFiles(): void { + return (this as any).collectDependentCompileFiles(); + } + } + + (global as any).LogDataFactory = LogDataFactory; + (global as any).ErrorCode = ErrorCode; + (global as any).getFileHash = jest.fn().mockReturnValue("hash123"); + + const baseMode = new TestBuildMode(mockConfig as any); + (baseMode as any).logger = mockLogger; + (baseMode as any).cacheDir = "/test/cache"; + (baseMode as any).hashCache = {}; + (baseMode as any).abcFiles = new Set(); + (baseMode as any).allFiles = new Map(); + (baseMode as any).compileFiles = new Map(); + + { + (baseMode as any).dependencyFileMap = null; + + baseMode.testCollectDependentCompileFiles(); + + mockLogger.printError.mockClear(); + LogDataFactory.newInstance.mockClear(); + } + + { + (baseMode as any).dependencyFileMap = { + dependants: { + "/test/other/path/file.ets": [] + } + }; + (baseMode as any).entryFiles = new Set(["/test/other/path/file.ets"]); + (baseMode as any).moduleInfos = new Map([ + ["test", { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"] + }] + ]); + + baseMode.testCollectDependentCompileFiles(); + + mockLogger.printError.mockClear(); + LogDataFactory.newInstance.mockClear(); + } + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + delete (global as any).getFileHash; + jest.restoreAllMocks(); +} + +function test_getSerializableConfig() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG, + arkts: { + someFunction: () => { } + }, + bigIntValue: BigInt(9007199254740991) + }; + + class TestBuildMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testGetSerializableConfig(): Object { + return (this as any).getSerializableConfig(); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + + const result = baseMode.testGetSerializableConfig(); + + expect(result).not.toHaveProperty('arkts'); + + expect(result).not.toHaveProperty('bigIntValue'); + + expect(result).toHaveProperty('packageName', 'test'); + expect(result).toHaveProperty('moduleRootPath', '/test/path'); + expect(result).toHaveProperty('sourceRoots'); +} + +function test_setupWorkerMessageHandler() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + const mockLogData = { code: "123", message: "Test message" }; + const mockLogger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + + const LogDataFactory = { + newInstance: jest.fn().mockReturnValue(mockLogData) + }; + + const ErrorCode = { + BUILDSYSTEM_COMPILE_ABC_FAIL: '11410099' + }; + + class TestBuildMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + + public testSetupWorkerMessageHandler(worker: any): void { + (this as any).setupWorkerMessageHandler(worker); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + (baseMode as any).logger = mockLogger; + const errorLogDataSpy = jest.spyOn(LogDataFactory, 'newInstance'); + + const mockWorker = { + on: jest.fn(), + callbacks: {} as Record + }; + + mockWorker.on = jest.fn().mockImplementation((event, callback) => { + mockWorker.callbacks[event] = callback; + return mockWorker; + }); + + (global as any).LogDataFactory = LogDataFactory; + (global as any).ErrorCode = ErrorCode; + + baseMode.testSetupWorkerMessageHandler(mockWorker); + + expect(mockWorker.on).toHaveBeenCalledWith('message', expect.any(Function)); + + if (mockWorker.callbacks['message']) { + mockWorker.callbacks['message']({ success: true }); + } + expect(mockLogger.printError).not.toHaveBeenCalled(); + + if (mockWorker.callbacks['message']) { + mockWorker.callbacks['message']({ + success: false, + filePath: '/test/file.ets', + error: 'Test error' + }); + } + mockLogger.printError.mockClear(); + errorLogDataSpy.mockClear(); + + if (mockWorker.callbacks['message']) { + mockWorker.callbacks['message']({ + success: false, + filePath: '/test/file2.ets' + }); + } + + jest.restoreAllMocks(); +} + +function test_getJobDependencies() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBuildMode extends BuildMode { + public testGetJobDependencies(fileDeps: string[], cycleFiles: Map): Set { + return (this as any).getJobDependencies(fileDeps, cycleFiles); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + { + const fileDeps = ['/test/path/file1.ets', '/test/path/file2.ets']; + const cycleFiles = new Map(); + const result = baseMode.testGetJobDependencies(fileDeps, cycleFiles); + expect(result.size).toBe(2); + expect(result.has('0/test/path/file1.ets')).toBe(true); + expect(result.has('0/test/path/file2.ets')).toBe(true); + } + + { + const fileDeps = ['/test/path/file1.ets', '/test/path/cycle1.ets']; + const cycleFiles = new Map(); + cycleFiles.set('/test/path/cycle1.ets', ['cycle-group-1', 'cycle-group-2']); + const result = baseMode.testGetJobDependencies(fileDeps, cycleFiles); + expect(result.size).toBe(3); + expect(result.has('0/test/path/file1.ets')).toBe(true); + expect(result.has('cycle-group-1')).toBe(true); + expect(result.has('cycle-group-2')).toBe(true); + expect(result.has('0/test/path/cycle1.ets')).toBe(false); + } + + { + const fileDeps = ['/test/path/cycle1.ets', '/test/path/cycle2.ets']; + const cycleFiles = new Map(); + cycleFiles.set('/test/path/cycle1.ets', ['cycle-group-1']); + cycleFiles.set('/test/path/cycle2.ets', ['cycle-group-2']); + const result = baseMode.testGetJobDependencies(fileDeps, cycleFiles); + expect(result.size).toBe(2); + expect(result.has('cycle-group-1')).toBe(true); + expect(result.has('cycle-group-2')).toBe(true); + expect(result.has('0/test/path/cycle1.ets')).toBe(false); + expect(result.has('0/test/path/cycle2.ets')).toBe(false); + } +} + +function test_getJobDependants() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + class TestBuildMode extends BuildMode { + public testGetJobDependants(fileDeps: string[], cycleFiles: Map): Set { + return (this as any).getJobDependants(fileDeps, cycleFiles); + } + } + const baseMode = new TestBuildMode(mockConfig as any); + { + const fileDeps = ['/test/path/file1.ets', '/test/path/file2.ets']; + const cycleFiles = new Map(); + const result = baseMode.testGetJobDependants(fileDeps, cycleFiles); + expect(result.size).toBe(4); + expect(result.has('1/test/path/file1.ets')).toBe(true); + expect(result.has('0/test/path/file1.ets')).toBe(true); + expect(result.has('1/test/path/file2.ets')).toBe(true); + expect(result.has('0/test/path/file2.ets')).toBe(true); + } + + { + const fileDeps = ['/test/path/file1.d.ets', '/test/path/file2.ets']; + const cycleFiles = new Map(); + const result = baseMode.testGetJobDependants(fileDeps, cycleFiles); + expect(result.size).toBe(3); + expect(result.has('1/test/path/file1.d.ets')).toBe(false); + expect(result.has('0/test/path/file1.d.ets')).toBe(true); + expect(result.has('1/test/path/file2.ets')).toBe(true); + expect(result.has('0/test/path/file2.ets')).toBe(true); + } + + { + const fileDeps = ['/test/path/file1.ets', '/test/path/cycle1.ets']; + const cycleFiles = new Map(); + cycleFiles.set('/test/path/cycle1.ets', ['cycle-group-1', 'cycle-group-2']); + const result = baseMode.testGetJobDependants(fileDeps, cycleFiles); + expect(result.size).toBe(5); + expect(result.has('1/test/path/file1.ets')).toBe(true); + expect(result.has('0/test/path/file1.ets')).toBe(true); + expect(result.has('cycle-group-1')).toBe(true); + expect(result.has('cycle-group-2')).toBe(true); + expect(result.has('0/test/path/cycle1.ets')).toBe(false); + } +} + +function test_collectCompileJobs() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBuildMode extends BuildMode { + public testCollectCompileJobs(jobs: Record): void { + return (this as any).collectCompileJobs(jobs); + } + + constructor(buildConfig: any) { + super(buildConfig); + (this as any).dependencyFileMap = { + dependencies: { + '/test/path/file1.ets': ['/test/path/file2.ets'], + '/test/path/file3.ets': ['/test/path/file4.ets'], + '/test/path/file5.d.ets': [] + }, + dependants: { + '/test/path/file2.ets': ['/test/path/file1.ets'], + '/test/path/file4.ets': ['/test/path/file3.ets'] + } + }; + + (this as any).entryFiles = new Set(['/test/path/file1.ets']); + (this as any).compileFiles = new Map([ + ['/test/path/file1.ets', { filePath: '/test/path/file1.ets' }] + ]); + + (this as any).moduleInfos = new Map(); + (this as any).moduleInfos.set("test", { + packageName: "test", + moduleRootPath: "/test/path", + arktsConfigFile: "/test/path/config.json" + }); + + (this as any).allFiles = new Map(); + + (this as any).getJobDependencies = jest.fn().mockImplementation(() => new Set(['dep1', 'dep2'])); + (this as any).getJobDependants = jest.fn().mockImplementation(() => new Set(['dep3', 'dep4'])); + (this as any).getAbcJobId = jest.fn().mockImplementation((file) => '1' + file); + (this as any).getExternalProgramJobId = jest.fn().mockImplementation((file) => '0' + file); + (this as any).createExternalProgramJob = jest.fn(); + (this as any).dealWithDependants = jest.fn(); + (this as any).findStronglyConnectedComponents = jest.fn().mockImplementation(() => { + const cycleGroups = new Map(); + const cycle1 = new Set(['/test/path/cycle1.ets', '/test/path/cycle2.ets']); + cycleGroups.set('cycle-group-1', cycle1); + return cycleGroups; + }); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + + (baseMode as any).dependencyFileMap.dependants['/test/path/file6.ets'] = ['/test/path/file7.ets']; + + const jobs: Record = {}; + + const findComponentsSpy = jest.spyOn(baseMode as any, 'findStronglyConnectedComponents'); + const getJobDependenciesSpy = jest.spyOn(baseMode as any, 'getJobDependencies'); + const getJobDependantsSpy = jest.spyOn(baseMode as any, 'getJobDependants'); + const getAbcJobIdSpy = jest.spyOn(baseMode as any, 'getAbcJobId'); + const getExternalProgramJobIdSpy = jest.spyOn(baseMode as any, 'getExternalProgramJobId'); + const createExternalProgramJobSpy = jest.spyOn(baseMode as any, 'createExternalProgramJob'); + const dealWithDependantsSpy = jest.spyOn(baseMode as any, 'dealWithDependants'); + + baseMode.testCollectCompileJobs(jobs); + + expect((baseMode as any).dependencyFileMap.dependencies['/test/path/file6.ets']).toEqual([]); + + expect(findComponentsSpy).toHaveBeenCalledWith((baseMode as any).dependencyFileMap); + + const cycleFiles = new Map(); + cycleFiles.set('/test/path/cycle1.ets', ['cycle-group-1']); + cycleFiles.set('/test/path/cycle2.ets', ['cycle-group-1']); + + expect(getJobDependenciesSpy).toHaveBeenCalled(); + + expect(getAbcJobIdSpy).toHaveBeenCalledWith('/test/path/file1.ets'); + expect(getAbcJobIdSpy).toHaveBeenCalledWith('/test/path/file3.ets'); + expect(getAbcJobIdSpy).not.toHaveBeenCalledWith('/test/path/file5.d.ets'); + + expect(jobs['1/test/path/file1.ets']).toBeDefined(); + expect(jobs['1/test/path/file3.ets']).toBeDefined(); + + expect(createExternalProgramJobSpy).toHaveBeenCalled(); + + expect((baseMode as any).allFiles.has('/test/path/file5.d.ets')).toBe(true); + + expect(getJobDependantsSpy).toHaveBeenCalled(); + expect(dealWithDependantsSpy).toHaveBeenCalled(); + + jest.restoreAllMocks(); +} + +function test_dealWithDependants() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + class TestBuildMode extends BuildMode { + public testDealWithDependants(cycleFiles: Map, key: string, jobs: Record, dependants: Set): void { + return (this as any).dealWithDependants(cycleFiles, key, jobs, dependants); + } + } + const baseMode = new TestBuildMode(mockConfig as any); + { + const cycleFiles = new Map(); + cycleFiles.set('file1.ets', ['cycle-1', 'cycle-2']); + const jobs: Record = { + 'cycle-1': { + id: 'cycle-1', + fileList: ['file1.ets'], + dependencies: [], + dependants: ['dep1', 'dep2'], + isAbcJob: false + }, + 'cycle-2': { + id: 'cycle-2', + fileList: ['file1.ets', 'file2.ets'], + dependencies: [], + dependants: ['dep3'], + isAbcJob: false + } + }; + const dependants = new Set(['dep4', 'dep5', 'cycle-1']); + baseMode.testDealWithDependants(cycleFiles, 'file1.ets', jobs, dependants); + expect(jobs['cycle-1'].dependants).toEqual(expect.arrayContaining(['dep1', 'dep2', 'dep4', 'dep5'])); + expect(jobs['cycle-1'].dependants).not.toContain('cycle-1'); + expect(jobs['cycle-2'].dependants).toEqual(expect.arrayContaining(['dep3', 'dep4', 'dep5'])); + expect(jobs['cycle-2'].dependants).not.toContain('cycle-1'); + } + { + const cycleFiles = new Map(); + const jobs: Record = { + '0file2.ets': { + id: '0file2.ets', + fileList: ['file2.ets'], + dependencies: [], + dependants: ['dep1', 'dep2'], + isAbcJob: false + } + }; + const dependants = new Set(['dep3', 'dep4', '0file2.ets']); + baseMode.testDealWithDependants(cycleFiles, 'file2.ets', jobs, dependants); + expect(jobs['0file2.ets'].dependants).toEqual(expect.arrayContaining(['dep1', 'dep2', 'dep3', 'dep4'])); + expect(jobs['0file2.ets'].dependants).not.toContain('0file2.ets'); + } +} + +function test_addJobToQueues() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBuildMode extends BuildMode { + public testAddJobToQueues(job: Job, queues: Queues): void { + return (this as any).addJobToQueues(job, queues); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + + const job1: Job = { + id: 'job1', + fileList: ['/test/path/file1.ets'], + dependencies: [], + dependants: [], + isDeclFile: true, + isAbcJob: false + }; + const queues1: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + baseMode.testAddJobToQueues(job1, queues1); + expect(queues1.externalProgramQueue.length).toBe(1); + expect(queues1.externalProgramQueue[0].id).toBe('job1'); + expect(queues1.abcQueue.length).toBe(0); + + const job2: Job = { + id: 'job2', + fileList: ['/test/path/file2.ets'], + dependencies: [], + dependants: [], + isDeclFile: false, + isAbcJob: true + }; + const queues2: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + baseMode.testAddJobToQueues(job2, queues2); + expect(queues2.externalProgramQueue.length).toBe(0); + expect(queues2.abcQueue.length).toBe(1); + expect(queues2.abcQueue[0].id).toBe('job2'); + + const job3: Job = { + id: 'job3', + fileList: ['/test/path/file3.ets'], + dependencies: [], + dependants: [], + isDeclFile: true, + isAbcJob: false + }; + const queues3: Queues = { + externalProgramQueue: [job3], + abcQueue: [] + }; + baseMode.testAddJobToQueues(job3, queues3); + expect(queues3.externalProgramQueue.length).toBe(1); + expect(queues3.abcQueue.length).toBe(0); + + const job4: Job = { + id: 'job4', + fileList: ['/test/path/file4.ets'], + dependencies: [], + dependants: [], + isDeclFile: false, + isAbcJob: true + }; + const queues4: Queues = { + externalProgramQueue: [], + abcQueue: [job4] + }; + baseMode.testAddJobToQueues(job4, queues4); + expect(queues4.externalProgramQueue.length).toBe(0); + expect(queues4.abcQueue.length).toBe(1); +} + +function test_initCompileQueues() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBuildMode extends BuildMode { + public testInitCompileQueues(jobs: Record, queues: Queues): void { + return (this as any).initCompileQueues(jobs, queues); + } + + constructor(buildConfig: any) { + super(buildConfig); + (this as any).collectCompileJobs = jest.fn().mockImplementation((jobs: Record) => { + jobs['job1'] = { + id: 'job1', + dependencies: [], + dependants: ['job3'], + fileList: ['/test/path/file1.ets'], + isAbcJob: true, + isDeclFile: false + }; + + jobs['job2'] = { + id: 'job2', + dependencies: [], + dependants: [], + fileList: ['/test/path/file2.ets'], + isAbcJob: false, + isDeclFile: true + }; + + jobs['job3'] = { + id: 'job3', + dependencies: ['job1'], + dependants: [], + fileList: ['/test/path/file3.ets'], + isAbcJob: true, + isDeclFile: false + }; + }); + + (this as any).addJobToQueues = jest.fn().mockImplementation((job: Job, queues: Queues) => { + if (job.isAbcJob) { + queues.abcQueue.push(job); + } else { + queues.externalProgramQueue.push(job); + } + }); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + + const jobs: Record = {}; + const queues: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + + const collectCompileJobsSpy = jest.spyOn(baseMode as any, 'collectCompileJobs'); + const addJobToQueuesSpy = jest.spyOn(baseMode as any, 'addJobToQueues'); + + baseMode.testInitCompileQueues(jobs, queues); + + expect(collectCompileJobsSpy).toHaveBeenCalledWith(jobs); + + expect(addJobToQueuesSpy).toHaveBeenCalledTimes(2); + + expect(queues.abcQueue.length).toBe(1); + expect(queues.abcQueue[0].id).toBe('job1'); + expect(queues.externalProgramQueue.length).toBe(1); + expect(queues.externalProgramQueue[0].id).toBe('job2'); + + expect(queues.abcQueue.find(job => job.id === 'job3')).toBeUndefined(); + + jest.restoreAllMocks(); +} + +function test_checkAllTasksDone() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + + class TestBuildMode extends BuildMode { + public testCheckAllTasksDone(queues: Queues, workerPool: WorkerInfo[]): boolean { + return (this as any).checkAllTasksDone(queues, workerPool); + } + } + + const baseMode = new TestBuildMode(mockConfig as any); + const queues2: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + const workerPool2 = [ + { worker: {} as ThreadWorker, isIdle: true }, + { worker: {} as ThreadWorker, isIdle: false } + ]; + + expect(baseMode.testCheckAllTasksDone(queues2, workerPool2)).toBe(false); + const queues3: Queues = { + externalProgramQueue: [], + abcQueue: [] + }; + const workerPool3 = [ + { worker: {} as ThreadWorker, isIdle: true }, + { worker: {} as ThreadWorker, isIdle: true } + ]; + expect(baseMode.testCheckAllTasksDone(queues3, workerPool3)).toBe(true); + expect(baseMode.testCheckAllTasksDone(queues3, workerPool3)).toBe(true); +} + +function test_processAfterCompile() { + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG, + arkts: { + destroyConfig: jest.fn() + }, + arktsGlobal: { + es2panda: { + _DestroyGlobalContext: jest.fn(), + _MemFinalize: jest.fn() + } + } + }; - const projectConfig: BuildConfig = JSON.parse(fs.readFileSync(buildConfigPath!, 'utf-8')); + class TestBuildMode extends BuildMode { + public testProcessAfterCompile(config: any, globalContext: any): void { + (this as any).processAfterCompile(config, globalContext); + } - entryModule.build(projectConfig); -} + public mergeAbcFiles(): void { + } + } -describe('test mock isWindows', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.spyOn(os, 'type').mockReturnValue('Windows_NT'); - }); + const baseMode = new TestBuildMode(mockConfig as any); + const mergeAbcFilesSpy = jest.spyOn(baseMode, 'mergeAbcFiles').mockImplementation(() => { }); - afterEach(() => { - jest.restoreAllMocks(); - }); + baseMode.testProcessAfterCompile('mockConfig', 'mockGlobalContext'); + expect(mockConfig.arktsGlobal.es2panda._DestroyGlobalContext).toHaveBeenCalledWith('mockGlobalContext'); + expect(mockConfig.arkts.destroyConfig).toHaveBeenCalledWith('mockConfig'); + expect(mockConfig.arktsGlobal.es2panda._MemFinalize).toHaveBeenCalled(); + expect(mergeAbcFilesSpy).toHaveBeenCalledTimes(1); + expect((baseMode as any).hasCleanWorker).toBe(true); - test('test mac001', () => { - test_mac001(); - }); -}); + jest.clearAllMocks(); + baseMode.testProcessAfterCompile('mockConfig2', 'mockGlobalContext2'); + expect(mockConfig.arktsGlobal.es2panda._DestroyGlobalContext).not.toHaveBeenCalled(); + expect(mockConfig.arkts.destroyConfig).not.toHaveBeenCalled(); + expect(mockConfig.arktsGlobal.es2panda._MemFinalize).not.toHaveBeenCalled(); + expect(mergeAbcFilesSpy).not.toHaveBeenCalled(); -function test_mac001() { - const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config4.json"; - main(buildConfigPath); - expect(isWindows()).toBe(true); + jest.restoreAllMocks(); } -describe('test mock isMac', () => { - beforeEach(() => { - jest.clearAllMocks(); - jest.spyOn(os, 'type').mockReturnValue('Darwin'); +function test_runConcurrent() { + const mockConfig = { + packageName: "test", compileFiles: ["/test/path/file1.ets"], + moduleRootPath: "/test/path", sourceRoots: ["./"], + loaderOutPath: "./dist", cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + } as any; + + const Logger = require('../../../src/logger').Logger; + Logger.getInstance = jest.fn().mockReturnValue({ + printInfo: jest.fn(), printError: jest.fn(), hasErrors: jest.fn().mockReturnValue(false) }); - afterEach(() => { + class TestBuildMode extends BuildMode { + public async testRunConcurrent(): Promise { return this.runConcunrent(); } + + public generateModuleInfos(): void { + (this as any).compileFiles = new Map([ + ['/test/path/file1.ets', { + filePath: '/test/path/file1.ets', packageName: 'test', + abcFilePath: '/test/path/output.abc', arktsConfigFile: '/test/arktsconfig.json' + }] + ]); + (this as any).allFiles = (this as any).compileFiles; + } + public generateArkTSConfigForModules(): void { } + + constructor(buildConfig: any) { + super(buildConfig); + const self = this as any; + self.initCompileQueues = function (jobs: any, queues: any): void { + queues.externalProgramQueue.push({ + id: '0/test/path/file1.ets', fileList: ['/test/path/file1.ets'], + dependencies: [], dependants: [], isDeclFile: true, isAbcJob: false + }); + }; + self.invokeWorkers = async function (): Promise { return Promise.resolve(); }; + } + } + + const baseMode = new TestBuildMode(mockConfig); + const genModuleSpy = jest.spyOn(baseMode, 'generateModuleInfos'); + const genConfigSpy = jest.spyOn(baseMode, 'generateArkTSConfigForModules'); + const initQueuesSpy = jest.spyOn(baseMode, 'initCompileQueues' as any); + const invokeWorkersSpy = jest.spyOn(baseMode, 'invokeWorkers' as any); + return baseMode.testRunConcurrent().then(() => { + expect(genModuleSpy).toHaveBeenCalledTimes(1); + expect(genConfigSpy).toHaveBeenCalledTimes(1); + expect(initQueuesSpy).toHaveBeenCalledTimes(1); + expect(invokeWorkersSpy).toHaveBeenCalledTimes(1); jest.restoreAllMocks(); }); +} - test('test mac002', () => { - test_mac002(); - }); -}); +function test_collectDependencyModules_language_branches() { + const { LANGUAGE_VERSION } = require('../../../src/pre_define'); + class TestBaseMode extends BaseMode { + public run(): Promise { return Promise.resolve(); } + public testCollectDependencyModules( + packageName: string, module: ModuleInfo, + dynamicDepModules: Map, + staticDepModules: Map + ): void { + (this as any).collectDependencyModules(packageName, module, dynamicDepModules, staticDepModules); + } + } + + const baseMode = new TestBaseMode({ + packageName: "test", moduleRootPath: "/test/path", sourceRoots: ["./"], + loaderOutPath: "./dist", cachePath: "./dist/cache", buildMode: BUILD_MODE.DEBUG + } as any); + + { + const packageName = "mod_1_1"; + const module = { packageName: "mod_1_1", language: LANGUAGE_VERSION.ARKTS_1_1 } as ModuleInfo; + const dynamicDepModules = new Map(); + const staticDepModules = new Map(); + + baseMode.testCollectDependencyModules(packageName, module, dynamicDepModules, staticDepModules); + + expect(dynamicDepModules.has(packageName)).toBe(true); + expect(staticDepModules.has(packageName)).toBe(false); + expect(dynamicDepModules.get(packageName)).toBe(module); + } + + { + const packageName = "mod_1_2"; + const module = { packageName: "mod_1_2", language: LANGUAGE_VERSION.ARKTS_1_2 } as ModuleInfo; + const dynamicDepModules = new Map(); + const staticDepModules = new Map(); -function test_mac002() { - const buildConfigPath = "test/ut/mock/demo_1.2_dep_hsp1.2/build_config5.json"; - main(buildConfigPath); - expect(isMac()).toBe(true); + baseMode.testCollectDependencyModules(packageName, module, dynamicDepModules, staticDepModules); + + expect(staticDepModules.has(packageName)).toBe(true); + } + + { + const packageName = "mod_hybrid"; + const module = { packageName: "mod_hybrid", language: LANGUAGE_VERSION.ARKTS_HYBRID } as ModuleInfo; + const dynamicDepModules = new Map(); + const staticDepModules = new Map(); + + baseMode.testCollectDependencyModules(packageName, module, dynamicDepModules, staticDepModules); + + expect(dynamicDepModules.has(packageName)).toBe(true); + expect(staticDepModules.has(packageName)).toBe(true); + expect(dynamicDepModules.get(packageName)).toBe(module); + expect(staticDepModules.get(packageName)).toBe(module); + } } -describe('test base_mode.ts file api', () => { - test('test collectModuleInfos', () => { - test_collectModuleInfos(); - }); +function test_getDependentModules_missing_module() { + const mockLogger = { + printInfo: jest.fn(), + printError: jest.fn(), + printErrorAndExit: jest.fn() + }; + const ErrorCode = { + BUILDSYSTEM_DEPENDENT_MODULE_INFO_NOT_FOUND: 'BUILDSYSTEM_DEPENDENT_MODULE_INFO_NOT_FOUND' + }; + jest.mock('../../../src/error_code', () => ({ + ErrorCode + })); + const mockConfig = { + packageName: "test", + moduleRootPath: "/test/path", + sourceRoots: ["./"], + loaderOutPath: "./dist", + cachePath: "./dist/cache", + buildMode: BUILD_MODE.DEBUG + }; + const Logger = require('../../../src/logger').Logger; + Logger.getInstance = jest.fn().mockReturnValue(mockLogger); + class TestBaseMode extends BaseMode { + public run(): Promise { + return Promise.resolve(); + } + public testGetDependentModules(moduleInfo: ModuleInfo): Map[] { + return (this as any).getDependentModules(moduleInfo); + } + } + const baseMode = new TestBaseMode(mockConfig as any); + (baseMode as any).logger = mockLogger; + const testModuleInfo = { + isMainModule: false, + dependencies: ['nonExistingModule'], + packageName: 'testModule' + } as ModuleInfo; + baseMode.testGetDependentModules(testModuleInfo); + expect(mockLogger.printErrorAndExit).toHaveBeenCalledWith( + expect.objectContaining({ + cause: "", + code: "11410011", + description: 'Module nonExistingModule not found in moduleInfos' + }) + ); +} - test('test collectDependentCompileFiles002', () => { - test_collectDependentCompileFiles002(); +function test_declgen_method() { + jest.resetAllMocks(); + jest.restoreAllMocks(); + const fs = require('fs'); + jest.spyOn(fs, 'readFileSync').mockReturnValue('test source code'); + const mockConfig = { + packageName: "test", moduleRootPath: "/test/path", + loaderOutPath: "./dist", cachePath: "./dist/cache", + arkts: { + Config: { create: jest.fn().mockReturnValue({ peer: 'mockConfigPeer' }) }, + Context: { createFromString: jest.fn().mockReturnValue({ peer: 'mockContextPeer', program: 'mockProgram' }) }, + proceedToState: jest.fn(), EtsScript: { fromContext: jest.fn().mockReturnValue('mockAst') }, + Es2pandaContextState: { ES2PANDA_STATE_PARSED: 'parsed', ES2PANDA_STATE_CHECKED: 'checked' }, + generateTsDeclarationsFromContext: jest.fn(), destroyConfig: jest.fn() + }, + arktsGlobal: { es2panda: { _DestroyContext: jest.fn() } } + }; + const Logger = require('../../../src/logger').Logger; + const PluginDriver = require('../../../src/plugins/plugins_driver').PluginDriver; + const utils = require('../../../src/utils'); + const path = require('path'); + Logger.getInstance = jest.fn().mockReturnValue({ printInfo: jest.fn(), printError: jest.fn() }); + PluginDriver.getInstance = jest.fn().mockReturnValue({ + getPluginContext: jest.fn().mockReturnValue({ setArkTSProgram: jest.fn(), setArkTSAst: jest.fn() }), + runPluginHook: jest.fn() }); + jest.spyOn(utils, 'ensurePathExists').mockImplementation(() => { }); + jest.spyOn(utils, 'changeDeclgenFileExtension').mockReturnValueOnce('/test/path/output.d.ets').mockReturnValueOnce('/test/path/output.ts'); + jest.spyOn(path, 'relative').mockReturnValue('file1.ets'); + jest.spyOn(path, 'join').mockReturnValue('/test/path/output'); + class TestBuildMode extends BuildMode { + constructor(buildConfig: any) { + super(buildConfig); + (this as any).outputDir = './dist'; (this as any).cacheDir = './dist/cache'; + } + public testDeclgen(fileInfo: any): void { return this.declgen(fileInfo); } + } + const baseMode = new TestBuildMode(mockConfig as any); + (baseMode as any).logger = Logger.getInstance(); + (baseMode as any).moduleInfos = new Map([['test', { + packageName: 'test', moduleRootPath: '/test/path', + declgenV1OutPath: './dist/declgen', declgenBridgeCodePath: './dist/bridge' + }]]); + baseMode.testDeclgen({ filePath: '/test/path/file1.ets', packageName: 'test', arktsConfigFile: '/test/path/arktsconfig.json' }); + expect(fs.readFileSync).toHaveBeenCalledWith('/test/path/file1.ets', 'utf8'); + expect(mockConfig.arkts.Context.createFromString).toHaveBeenCalled(); + expect(mockConfig.arkts.proceedToState).toHaveBeenCalledWith('parsed', 'mockContextPeer', true); + expect(mockConfig.arkts.proceedToState).toHaveBeenCalledWith('checked', 'mockContextPeer', true); + expect(mockConfig.arkts.generateTsDeclarationsFromContext).toHaveBeenCalled(); + jest.restoreAllMocks(); +} - test('test shouldSkipFile', () => { - test_shouldSkipFile(); - }); +function test_generateDeclaration() { + const mockConfig: BuildConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["ets2panda/driver/build_system/test/ut/mock/a.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 + } as any; - test('test setupCluster', () => { - test_setupCluster(); - }); + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance(mockConfig); - test('test terminateAllWorkers', () => { - test_terminateAllWorkers(); - }); + class TestBuildMode extends BuildMode { + public async testGenerateDeclaration(): Promise { + return this.generateDeclaration(); + } - test('test collectModuleInfos 001', () => { - test_collectDependencyModules001(); - }); + public generateModuleInfos(): void { + } - test('test collectModuleInfos 002', () => { - test_collectDependencyModules002(); - }); + public declgen(fileInfo: any): void { + } + } - test('test runall new', () => { - test_runParallell1(); - }); + const baseMode = new TestBuildMode(mockConfig); - test('test test_collectAbcFileFromByteCodeHar', () => { - test_collectAbcFileFromByteCodeHar(); - }); + (baseMode as any).logger = { printInfo: jest.fn(), printError: jest.fn() }; - test('test getDependentModules', async () => { - test_getDependentModules(); - }); + const generateModuleInfosSpy = jest.spyOn(baseMode, 'generateModuleInfos').mockImplementation(() => { }); + const declgenSpy = jest.spyOn(baseMode, 'declgen').mockImplementation(() => { }); - test('collectCompileFiles: test declaration files skip branch', () => { - test_collectCompileFiles_decl_ets_skip(); + return baseMode.testGenerateDeclaration().then(() => { + expect(generateModuleInfosSpy).toHaveBeenCalledTimes(1); + generateModuleInfosSpy.mockRestore(); + declgenSpy.mockRestore(); }); +} - test('collectCompileFiles: test bytecode HAR branch', () => { - test_collectCompileFiles_bytecode_har(); - }); +function test_runMethod() { + const mockConfig: BuildConfig = { + buildMode: BUILD_MODE.DEBUG, + compileFiles: ["/test/path/file1.ets", "/test/path/file2.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: { + compiler: '/path/to/compiler', + args: [], + destroyConfig: jest.fn() + } as any, + arktsGlobal: { + config: {} + } as any, + declgenV1OutPath: "./dist/declgen", + declgenV2OutPath: "./dist/declgen/v2", + buildSdkPath: "./sdk", + externalApiPaths: [], + enableDeclgenEts2Ts: false + } as any; - test('collectCompileFiles: test file not in module path branch', () => { - test_collectCompileFiles_file_not_in_module(); + const Logger = require('../../../src/logger').Logger; + Logger.instance = null; + Logger.getInstance = jest.fn().mockReturnValue({ + printInfo: jest.fn(), + printError: jest.fn(), + hasErrors: jest.fn().mockReturnValue(false) }); - test('test createExternalProgramJob method branches', () => { - test_createExternalProgramJob_branches(); + const PluginDriver = require('../../../src/plugins/plugins_driver').PluginDriver; + PluginDriver.getInstance = jest.fn().mockReturnValue({ + runPluginHook: jest.fn() }); - test('test findStronglyConnectedComponents method branches', () => { - test_findStronglyConnectedComponents_branches(); - }); + class TestBuildMode extends BuildMode { + public compile(fileInfo: any): void { + super.compile(fileInfo); + } - test('test assignTaskToIdleWorker abcQueue branch without job', () => { - test_assignTaskToIdleWorker_abcQueue_no_job(); - }); + protected executeCommand(command: string, args: string[], options?: any): Promise { + return Promise.resolve({ stdout: "mock stdout", stderr: "" }); + } - test('test assignTaskToIdleWorker with empty queues', () => { - test_assignTaskToIdleWorker_empty_queues(); + protected getCompileCommand(fileInfo: any): { command: string, args: string[] } { + return { + command: 'node', + args: ['/path/to/compiler', fileInfo.filePath] + }; + } + } + + const baseMode = new TestBuildMode(mockConfig); + + const mockFileInfo1 = { + filePath: '/test/path/file1.ets', + abcFilePath: '/test/path/file1.abc', + packageName: 'test', + arktsConfigFile: '/test/path/arktsconfig.json', + dependentFiles: [] + }; + const mockFileInfo2 = { + filePath: '/test/path/file2.ets', + abcFilePath: '/test/path/file2.abc', + packageName: 'test', + arktsConfigFile: '/test/path/arktsconfig.json', + dependentFiles: [] + }; + + const generateModuleInfosSpy = jest.spyOn(baseMode as any, 'generateModuleInfos') + .mockImplementation(() => { + (baseMode as any).compileFiles = new Map([ + ['/test/path/file1.ets', mockFileInfo1], + ['/test/path/file2.ets', mockFileInfo2] + ]); + }); + + const compileSpy = jest.spyOn(baseMode, 'compile'); + + const executeCommandSpy = jest.spyOn(baseMode as any, 'executeCommand'); + + const mergeAbcFilesSpy = jest.spyOn(baseMode as any, 'mergeAbcFiles') + .mockImplementation(() => { + }); + + const promiseAllSpy = jest.spyOn(Promise, 'all'); + + return baseMode.run().then(() => { + expect(generateModuleInfosSpy).toHaveBeenCalledTimes(1); + + expect(compileSpy).toHaveBeenCalledTimes(2); + expect(compileSpy).toHaveBeenCalledWith(mockFileInfo1); + expect(compileSpy).toHaveBeenCalledWith(mockFileInfo2); + + + generateModuleInfosSpy.mockRestore(); + compileSpy.mockRestore(); + executeCommandSpy.mockRestore(); + mergeAbcFilesSpy.mockRestore(); + promiseAllSpy.mockRestore(); }); -}); +} function test_assignTaskToIdleWorker_empty_queues() { const mockLogger = { @@ -441,7 +2230,8 @@ function test_createExternalProgramJob_branches() { return Promise.resolve(); } - public testCreateExternalProgramJob(id: string, fileList: string[], jobs: Record, dependencies: Set, isInCycle?: boolean): void { + public testCreateExternalProgramJob(id: string, fileList: string[], + jobs: Record, dependencies: Set, isInCycle?: boolean): void { return (this as any).createExternalProgramJob(id, fileList, jobs, dependencies, isInCycle); } } @@ -551,7 +2341,7 @@ function test_collectCompileFiles_bytecode_har() { (baseMode as any).compileFiles = new Map(); (baseMode as any).entryFiles = new Set([ - '/test/path/test.ets' + './test/ut/mock/a.ets', ]); (baseMode as any).moduleInfos = new Map(); @@ -677,8 +2467,8 @@ function test_collectCompileFiles_decl_ets_skip() { (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' + 'index.ets', + '/test/ut/mock/web.d.ets' ]); (baseMode as any).moduleInfos = new Map(); @@ -696,88 +2486,6 @@ function test_collectCompileFiles_decl_ets_skip() { 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(), @@ -820,14 +2528,14 @@ function test_collectModuleInfos() { "packageName": "", "moduleName": "hspA", "moduleType": "shared", - "modulePath": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA", + "modulePath": "hspA", "sourceRoots": ["./"], - "entryFile": "test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets", + "entryFile": "index.ets", "language": "11.2", "byteCodeHar": false } ] - }; + } as any; const Logger = require('../../../src/logger').Logger; Logger.instance = null; Logger.getInstance(mockConfig); @@ -849,7 +2557,7 @@ function test_collectDependentCompileFiles002() { hasErrors: jest.fn().mockReturnValue(false) }; - const moduleRootPath = "test/ut/mock/demo_1.2_dep_hsp1.2/entry/"; + const moduleRootPath = "test/ut/mock/"; const testFile = `${moduleRootPath}a.ets`; const mockConfig: BuildConfig = { @@ -873,9 +2581,8 @@ function test_collectDependentCompileFiles002() { declgenV2OutPath: "./dist/declgen/v2", buildSdkPath: "./sdk", externalApiPaths: [] - }; + } as any; - 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; @@ -944,7 +2651,7 @@ function test_shouldSkipFile() { buildSdkPath: "./sdk", externalApiPaths: [], enableDeclgenEts2Ts: false - }; + } as any; const Logger = require('../../../src/logger').Logger; Logger.instance = null; Logger.getInstance(mockConfig); @@ -1005,7 +2712,7 @@ function test_setupCluster() { buildSdkPath: "./sdk", byteCodeHar: false, externalApiPaths: [] - }; + } as any; const Logger = require('../../../src/logger').Logger; Logger.instance = null; Logger.getInstance(mockConfig); @@ -1060,7 +2767,7 @@ function test_terminateAllWorkers() { buildSdkPath: "./sdk", byteCodeHar: false, externalApiPaths: [] - }; + } as any; const Logger = require('../../../src/logger').Logger; Logger.instance = null; Logger.getInstance(mockConfig); diff --git a/ets2panda/driver/build_system/test/ut/build_framework_modeTest/build_framework_mode.test.ts b/ets2panda/driver/build_system/test/ut/build_framework_modeTest/build_framework_mode.test.ts new file mode 100755 index 0000000000000000000000000000000000000000..d5f1c84eb00399d8ec86c185ea8edcf26e85768e --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/build_framework_modeTest/build_framework_mode.test.ts @@ -0,0 +1,214 @@ +/* + * 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 { BuildFrameworkMode } from '../../../src/build/build_framework_mode'; +import { BUILD_TYPE, BuildConfig, OHOS_MODULE_TYPE, BUILD_MODE, ModuleInfo, PluginsConfig } from '../../../src/types'; +import { BaseMode } from '../../../src/build/base_mode'; + +jest.mock('../../../src/logger', () => ({ + Logger: { + getInstance: jest.fn().mockReturnValue({ + printInfo: jest.fn(), + printError: jest.fn(), + hasErrors: jest.fn().mockReturnValue(false) + }) + } +})); + +// Test the functions of the build_framework_mode.ts file +describe('test build_framework_mode.ts file api', () => { + const createMockConfig = { + buildType: BUILD_TYPE.BUILD, + packageName: 'test', + compileFiles: ['test.ets'], + enableDeclgenEts2Ts: false, + frameworkMode: true, + useEmptyPackage: true, + loaderOutPath: './dist', + cachePath: './dist/cache', + moduleType: OHOS_MODULE_TYPE.HAR, + sourceRoots: ['./'], + moduleRootPath: '/test/path', + buildMode: BUILD_MODE.DEBUG, + plugins: {} as PluginsConfig, + dependentModuleList: [], + hasMainModule: false, + byteCodeHar: false, + arkts: {} as any, + arktsGlobal: {} as any, + declgenV1OutPath: "./dist/declgen", + declgenV2OutPath: "./dist/declgen/v2", + buildSdkPath: "./sdk", + externalApiPaths: [] + } as any; + + test('constructor should set properties correctly', () => { + class TestBuildFrameworkMode extends BuildFrameworkMode { + public async run(): Promise { } + } + + const buildFrameworkMode = new TestBuildFrameworkMode(createMockConfig); + + expect(buildFrameworkMode.frameworkMode).toBe(true); + expect(buildFrameworkMode.useEmptyPackage).toBe(true); + expect((buildFrameworkMode as any).mergedAbcFile).toBe('./dist'); + + const configWithUndefined = { + ...createMockConfig, + frameworkMode: undefined, + useEmptyPackage: undefined + }; + const defaultBuildMode = new TestBuildFrameworkMode(configWithUndefined as BuildConfig); + expect(defaultBuildMode.frameworkMode).toBe(false); + expect(defaultBuildMode.useEmptyPackage).toBe(false); + }); + + test('run method should call super.run', async () => { + class TestBuildFrameworkMode extends BuildFrameworkMode { + public superRunCalled = false; + + public async run(): Promise { + this.superRunCalled = true; + } + } + + const buildFrameworkMode = new TestBuildFrameworkMode(createMockConfig); + await buildFrameworkMode.run(); + + expect(buildFrameworkMode.superRunCalled).toBe(true); + }); + + test('getMainModuleInfo should set framework properties', () => { + class TestBuildFrameworkMode extends BuildFrameworkMode { + public testGetMainModuleInfo(): ModuleInfo { + const moduleInfo = { + packageName: 'test', + moduleRootPath: '/test/path' + } as ModuleInfo; + + moduleInfo.frameworkMode = this.frameworkMode; + moduleInfo.useEmptyPackage = this.useEmptyPackage; + + return moduleInfo; + } + } + + const buildFrameworkMode = new TestBuildFrameworkMode(createMockConfig); + const moduleInfo = buildFrameworkMode.testGetMainModuleInfo(); + + expect(moduleInfo.frameworkMode).toBe(true); + expect(moduleInfo.useEmptyPackage).toBe(true); + }); + + test('generateModuleInfos should call expected methods', async () => { + class TestBuildFrameworkMode extends BuildFrameworkMode { + public methodsCalled = { + collectModuleInfos: false, + generateArkTSConfigForModules: false, + collectCompileFiles: false + }; + + protected collectModuleInfos(): void { + this.methodsCalled.collectModuleInfos = true; + } + + protected generateArkTSConfigForModules(): void { + this.methodsCalled.generateArkTSConfigForModules = true; + } + + protected collectCompileFiles(): void { + this.methodsCalled.collectCompileFiles = true; + } + + public testGenerateModuleInfos(): void { + this.generateModuleInfos(); + } + } + + const buildFrameworkMode = new TestBuildFrameworkMode(createMockConfig); + buildFrameworkMode.testGenerateModuleInfos(); + + expect(buildFrameworkMode.methodsCalled.collectModuleInfos).toBe(true); + expect(buildFrameworkMode.methodsCalled.generateArkTSConfigForModules).toBe(true); + expect(buildFrameworkMode.methodsCalled.collectCompileFiles).toBe(true); + }); + + test('getMainModuleInfo should extend super.getMainModuleInfo and set framework properties', () => { + class TestBuildFrameworkMode extends BuildFrameworkMode { + public testGetMainModuleInfo(): ModuleInfo { + return this.getMainModuleInfo(); + } + + protected getMainModuleInfoFromSuper(): ModuleInfo { + return { + isMainModule: true, + packageName: 'test', + moduleRootPath: '/test/path', + sourceRoots: ['./'], + arktsConfigFile: '/test/config.json', + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + entryFile: 'index.ets', + byteCodeHar: false, + declgenV1OutPath: '/test/declgen', + declgenV2OutPath: '/test/declgen/v2', + declgenBridgeCodePath: '/test/bridge' + }; + } + } + + jest.spyOn((BaseMode as any).prototype, 'getMainModuleInfo').mockImplementation(function () { + return { + isMainModule: true, + packageName: 'test', + moduleRootPath: '/test/path', + sourceRoots: ['./'], + arktsConfigFile: '/test/config.json', + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + entryFile: 'index.ets', + byteCodeHar: false, + declgenV1OutPath: '/test/declgen', + declgenV2OutPath: '/test/declgen/v2', + declgenBridgeCodePath: '/test/bridge' + }; + }); + + const buildFrameworkMode = new TestBuildFrameworkMode(createMockConfig); + const moduleInfo = buildFrameworkMode.testGetMainModuleInfo(); + + expect(moduleInfo.frameworkMode).toBe(true); + expect(moduleInfo.useEmptyPackage).toBe(true); + expect(moduleInfo.packageName).toBe('test'); + expect(moduleInfo.moduleRootPath).toBe('/test/path'); + expect(moduleInfo.isMainModule).toBe(true); + + const configWithoutFramework = { + ...createMockConfig, + frameworkMode: false, + useEmptyPackage: false + }; + + const buildFrameworkMode2 = new TestBuildFrameworkMode(configWithoutFramework as BuildConfig); + const moduleInfo2 = buildFrameworkMode2.testGetMainModuleInfo(); + + expect(moduleInfo2.frameworkMode).toBe(false); + expect(moduleInfo2.useEmptyPackage).toBe(false); + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/compile_WorkerTest/compile_worker.test.ts b/ets2panda/driver/build_system/test/ut/compile_WorkerTest/compile_worker.test.ts new file mode 100755 index 0000000000000000000000000000000000000000..93d11c757ff4dfe2f4741f231038d4978121285a --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/compile_WorkerTest/compile_worker.test.ts @@ -0,0 +1,166 @@ +/* + * 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. + */ + +// This file has to mock a lot because compile_worker.ts only runs a `process.on`. + +jest.mock('fs'); +jest.mock('path'); +jest.mock('../../../src/utils', () => ({ + changeFileExtension: jest.fn((p, ext) => p.replace(/\.[^/.]+$/, ext)), + ensurePathExists: jest.fn() +})); +jest.mock('../../../src/plugins/plugins_driver', () => { + const mPluginDriver = { + initPlugins: jest.fn(), + getPluginContext: jest.fn(() => ({ setArkTSProgram: jest.fn() })), + runPluginHook: jest.fn(), + getInstance: jest.fn((): any => mPluginDriver) + } as any; + return { PluginDriver: mPluginDriver, PluginHook: { PARSED: 1, CHECKED: 2, CLEAN: 3 } }; +}); +jest.mock('../../../src/logger', () => { + const mLogger = { + printError: jest.fn(), + getInstance: jest.fn((): any => mLogger) + } as any; + return { + Logger: mLogger, + LogDataFactory: { newInstance: jest.fn(() => ({ + code: '001', description: '', cause: '', position: '', solutions: [], moreInfo: {} })) } + }; +}); +jest.mock('../../../src/pre_define', () => ({ + DECL_ETS_SUFFIX: '.d.ets', + KOALA_WRAPPER_PATH_FROM_SDK: 'koala' +})); +jest.mock('/sdk/koala', () => ({ + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal +}), { virtual: true }); + +const fakeArkts = { + Config: { create: jest.fn(() => ({ peer: 'peer' })) }, + Context: { createFromString: jest.fn(() => ({ program: {}, peer: 'peer' })) }, + proceedToState: jest.fn(), + Es2pandaContextState: { ES2PANDA_STATE_PARSED: 1, ES2PANDA_STATE_CHECKED: 2, ES2PANDA_STATE_BIN_GENERATED: 3 }, + generateStaticDeclarationsFromContext: jest.fn(), + destroyConfig: jest.fn() +}; +const fakeArktsGlobal = { + es2panda: { _DestroyContext: jest.fn() } +}; +jest.mock('path', () => ({ + ...jest.requireActual('path'), + resolve: jest.fn((...args) => args.join('/')), + relative: jest.fn((from, to) => to.replace(from, '')), + join: jest.fn((...args) => args.join('/')), + basename: jest.fn((p) => p.split('/').pop()) +})); + +jest.mock('module', () => ({ + createRequire: () => () => ({ arkts: fakeArkts, arktsGlobal: fakeArktsGlobal }) +})); + +beforeEach(() => { + jest.resetModules(); + (process as any).send = jest.fn(); + jest.spyOn(process, 'exit').mockImplementation((() => { throw new Error('exit'); }) as any); +}); +afterEach(() => { + jest.clearAllMocks(); +}); + +// Test the functions of the compile_worker.ts file +describe('compile_worker', () => { + const fileInfo = { + filePath: '/src/foo.ets', + abcFilePath: '/out/foo.abc', + arktsConfigFile: '/src/arktsconfig.json' + }; + const buildConfig = { + buildMode: 0, + hasMainModule: true, + byteCodeHar: true, + moduleType: 0, + declgenV2OutPath: '/decl', + packageName: 'pkg', + moduleRootPath: '/src', + buildSdkPath: '/sdk' + }; + const moduleInfos: any[] = []; + + test('compile all files && exit(0)', () => { + require('fs').readFileSync.mockReturnValue(Buffer.from('source code')); + require('path').relative.mockImplementation((from: string, to: string) => to.replace(from, '').replace(/^\//, '')); + require('path').join.mockImplementation((...args: string[]) => args.join('/')); + require('path').resolve.mockImplementation((...args: string[]) => args.join('/')); + require('path').basename.mockImplementation((p: string) => p.split('/').pop()); + require('../../../src/build/compile_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); + }).toThrow('exit'); + expect(require('../../../src/utils').ensurePathExists).toHaveBeenCalled(); + expect(require('fs').readFileSync).toHaveBeenCalledWith(fileInfo.filePath); + expect(fakeArkts.Config.create).toHaveBeenCalled(); + expect(fakeArkts.Context.createFromString).toHaveBeenCalled(); + expect(fakeArkts.proceedToState).toHaveBeenCalledTimes(3); + expect(fakeArkts.generateStaticDeclarationsFromContext).toHaveBeenCalled(); + expect(fakeArkts.destroyConfig).toHaveBeenCalled(); + expect(fakeArktsGlobal.es2panda._DestroyContext).toHaveBeenCalled(); + expect(process.exit).toHaveBeenCalledWith(0); + }); + + test('handle error && send fail message', () => { + jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); + + require('fs').readFileSync.mockImplementation(() => { throw new Error('fail'); }); + require('../../../src/build/compile_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); + }).not.toThrow(); + + expect(require('../../../src/logger').Logger.printError).toHaveBeenCalled(); + expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ + success: false, + filePath: fileInfo.filePath, + error: expect.any(String) + })); + }); + + test('generate decl file', () => { + require('fs').readFileSync.mockReturnValue(Buffer.from('source code')); + let config = { ...buildConfig, hasMainModule: false }; + require('../../../src/build/compile_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig: config, moduleInfos }); + }).toThrow('exit'); + expect(fakeArkts.generateStaticDeclarationsFromContext).not.toHaveBeenCalled(); + require('fs').readFileSync.mockReturnValue(Buffer.from('source code')); + config = { ...buildConfig, byteCodeHar: false, moduleType: 9999 }; + require('../../../src/build/compile_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig: config, moduleInfos }); + }).toThrow('exit'); + expect(fakeArkts.generateStaticDeclarationsFromContext).not.toHaveBeenCalled(); + }); + + test('throw while process.send is undefined', () => { + delete (process as any).send; + require('../../../src/build/compile_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); + }).toThrow('process.send is undefined. This worker must be run as a forked process.'); + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/compile_thread_workerTest/compile_thread_worker.test.ts b/ets2panda/driver/build_system/test/ut/compile_thread_workerTest/compile_thread_worker.test.ts new file mode 100755 index 0000000000000000000000000000000000000000..7b2783a1cbfa9a5513dcf28c169a053e69b44fb1 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/compile_thread_workerTest/compile_thread_worker.test.ts @@ -0,0 +1,210 @@ +/* + * 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. + */ + +// This file has to mock a lot because compile_thread_worker.ts only runs a `process.on`. + +import { EventEmitter } from 'events'; + +jest.mock('fs'); +jest.mock('path'); +jest.mock('../../../src/utils', () => ({ + changeFileExtension: jest.fn((p: string, ext: string) => p.replace(/\.[^/.]+$/, ext)), + ensurePathExists: jest.fn() +})); +jest.mock('../../../src/plugins/plugins_driver', () => { + const mPluginDriver = { + initPlugins: jest.fn(), + getPluginContext: jest.fn(() => ({ + setContextPtr: jest.fn() + })), + runPluginHook: jest.fn(), + getInstance: jest.fn((): any => mPluginDriver) + } as any; + return { PluginDriver: mPluginDriver, PluginHook: { PARSED: 1, CHECKED: 2, CLEAN: 3 } }; +}); +jest.mock('../../../src/logger', () => { + const mLogger = { + printError: jest.fn(), + getInstance: jest.fn((): any => mLogger) + } as any; + return { + Logger: mLogger, + LogDataFactory: { newInstance: jest.fn(() => ({ + code: '001', description: '', cause: '', position: '', solutions: [], moreInfo: {} })) } + }; +}); +jest.mock('../../../src/pre_define', () => ({ + DECL_ETS_SUFFIX: '.d.ets', + KOALA_WRAPPER_PATH_FROM_SDK: 'koala' +})); + +const fakeArkts = { + Config: { create: jest.fn(() => ({ peer: 'peer' })) }, + Context: { createCacheContextFromFile: jest.fn(() => ({ peer: 'contextPeer' })) }, + proceedToState: jest.fn(), + Es2pandaContextState: { + ES2PANDA_STATE_PARSED: 1, + ES2PANDA_STATE_CHECKED: 2, + ES2PANDA_STATE_BIN_GENERATED: 3, + ES2PANDA_STATE_LOWERED: 4 + }, + generateStaticDeclarationsFromContext: jest.fn(), + destroyConfig: jest.fn() +}; +const fakeArktsGlobal = { + es2panda: { + _DestroyContext: jest.fn() + }, + config: '', + compilerContext: { peer: 'contextPeer' } +}; + +jest.mock('/sdk/koala', () => ({ + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal +}), { virtual: true }); + +beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + require('path').resolve.mockImplementation((...args: string[]) => { + while (args.includes('koala')) return '/sdk/koala'; + return args.join('/'); + }); + require('path').join.mockImplementation((...args: string[]) => args.join('/')); + require('path').relative.mockImplementation((from: string, to: string) => to.replace(from, '').replace(/^\//, '')); + require('path').basename.mockImplementation((p: string) => p.split('/').pop()); +}); + +// Test the functions of the compile_thread_worker.ts file +describe('compile_thread_worker', () => { + let parentPort: EventEmitter & { postMessage?: jest.Mock }; + const workerData = { workerId: 1 }; + const fileInfo = { + filePath: '/src/foo.ets', + abcFilePath: '/out/foo.abc', + arktsConfigFile: '/src/arktsconfig.json' + }; + const buildConfig = { + buildMode: 0, + hasMainModule: true, + byteCodeHar: true, + moduleType: 0, + declgenV2OutPath: '/decl', + packageName: 'pkg', + moduleRootPath: '/src', + buildSdkPath: '/sdk' + }; + const jobInfo = { + id: 123, + isCompileAbc: true, + buildConfig, + compileFileInfo: fileInfo, + globalContextPtr: 0 + }; + + beforeEach(() => { + parentPort = new EventEmitter() as any; + parentPort.postMessage = jest.fn(); + jest.doMock('worker_threads', () => ({ + parentPort, + workerData + })); + }); + + afterEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + jest.dontMock('worker_threads'); + }); + + test('compile abc && post TASK_FINISH', () => { + require('../../../src/build/compile_thread_worker'); + parentPort.emit('message', { type: 'ASSIGN_TASK', jobInfo }); + + expect(require('../../../src/utils').ensurePathExists).toHaveBeenCalledWith(fileInfo.abcFilePath); + expect(fakeArkts.Config.create).toHaveBeenCalled(); + expect(fakeArkts.Context.createCacheContextFromFile).toHaveBeenCalled(); + expect(fakeArkts.proceedToState).toHaveBeenCalledWith(1, 'contextPeer'); + expect(fakeArkts.proceedToState).toHaveBeenCalledWith(2, 'contextPeer'); + expect(fakeArkts.proceedToState).toHaveBeenCalledWith(3, 'contextPeer'); + expect(fakeArkts.generateStaticDeclarationsFromContext).toHaveBeenCalled(); + expect(fakeArkts.destroyConfig).toHaveBeenCalled(); + expect(fakeArktsGlobal.es2panda._DestroyContext).toHaveBeenCalled(); + expect(parentPort.postMessage).toHaveBeenCalledWith(expect.objectContaining({ + type: 'TASK_FINISH', + jobId: jobInfo.id, + workerId: workerData.workerId + })); + }); + + test('generate decl file', () => { + let config = { ...buildConfig, hasMainModule: false }; + let job = { ...jobInfo, buildConfig: config }; + require('../../../src/build/compile_thread_worker'); + parentPort.emit('message', { type: 'ASSIGN_TASK', jobInfo: job }); + expect(fakeArkts.generateStaticDeclarationsFromContext).not.toHaveBeenCalled(); + config = { ...buildConfig, byteCodeHar: false, moduleType: 9999 }; + job = { ...jobInfo, buildConfig: config }; + require('../../../src/build/compile_thread_worker'); + parentPort.emit('message', { type: 'ASSIGN_TASK', jobInfo: job }); + expect(fakeArkts.generateStaticDeclarationsFromContext).not.toHaveBeenCalled(); + }); + + test('call plugin hooks', () => { + require('../../../src/build/compile_thread_worker'); + parentPort.emit('message', { type: 'ASSIGN_TASK', jobInfo }); + const pluginDriver = require('../../../src/plugins/plugins_driver').PluginDriver; + expect(pluginDriver.initPlugins).toHaveBeenCalled(); + expect(pluginDriver.runPluginHook).toHaveBeenCalledWith(1); + expect(pluginDriver.runPluginHook).toHaveBeenCalledWith(2); + expect(pluginDriver.runPluginHook).toHaveBeenCalledWith(3); + }); + + test('handle error && printError', () => { + fakeArkts.Config.create.mockImplementation(() => { throw new Error('fail'); }); + require('../../../src/build/compile_thread_worker'); + parentPort.emit('message', { type: 'ASSIGN_TASK', jobInfo }); + expect(require('../../../src/logger').Logger.printError).toHaveBeenCalled(); + expect(parentPort.postMessage).toHaveBeenCalledWith(expect.objectContaining({ + type: 'TASK_FINISH', + jobId: jobInfo.id, + workerId: workerData.workerId + })); + }); + + test('handle compileExternalProgram', () => { + const job = { ...jobInfo, isCompileAbc: false }; + require('../../../src/build/compile_thread_worker'); + parentPort.emit('message', { type: 'ASSIGN_TASK', jobInfo: job }); + const { arkts } = require('/sdk/koala'); + expect(arkts.Config.create).toHaveBeenCalled(); + expect(arkts.generateStaticDeclarationsFromContext).not.toHaveBeenCalled(); + expect(parentPort.postMessage).toHaveBeenCalledWith(expect.objectContaining({ + type: 'TASK_FINISH', + jobId: jobInfo.id, + workerId: workerData.workerId + })); + }); + + test('exit on EXIT message', () => { + const spy = jest.spyOn(process, 'exit').mockImplementation(() => { throw new Error('exit'); }); + require('../../../src/build/compile_thread_worker'); + expect(() => { + parentPort.emit('message', { type: 'EXIT' }); + }).toThrow('exit'); + spy.mockRestore(); + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/declgen_workerTest/declgen_worker.test.ts b/ets2panda/driver/build_system/test/ut/declgen_workerTest/declgen_worker.test.ts new file mode 100755 index 0000000000000000000000000000000000000000..45c8fdd458d0bd8337fd98e0786521f5667521e2 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/declgen_workerTest/declgen_worker.test.ts @@ -0,0 +1,169 @@ +/* + * 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. + */ + +// This file has to mock a lot because declgen_worker.ts only runs a `process.on`. + +jest.mock('fs'); +jest.mock('path'); +jest.mock('../../../src/utils', () => ({ + changeFileExtension: jest.fn((p: string, ext: string) => p.replace(/\.[^/.]+$/, ext)), + ensurePathExists: jest.fn() +})); +jest.mock('../../../src/plugins/plugins_driver', () => { + const mPluginDriver = { + initPlugins: jest.fn(), + getPluginContext: jest.fn(() => ({ + setArkTSProgram: jest.fn(), + setArkTSAst: jest.fn() + })), + runPluginHook: jest.fn(), + getInstance: jest.fn((): any => mPluginDriver) + } as any; + return { PluginDriver: mPluginDriver, PluginHook: { PARSED: 1, CHECKED: 2 } }; +}); +jest.mock('../../../src/logger', () => { + const mLogger = { + printError: jest.fn(), + printInfo: jest.fn(), + getInstance: jest.fn((): any => mLogger) + } as any; + return { + Logger: mLogger, + LogDataFactory: { newInstance: jest.fn(() => ({ + code: '001', description: '', cause: '', position: '', solutions: [], moreInfo: {} })) } + }; +}); +jest.mock('../../../src/pre_define', () => ({ + DECL_ETS_SUFFIX: '.d.ets', + TS_SUFFIX: '.ts', + KOALA_WRAPPER_PATH_FROM_SDK: 'koala' +})); + +const fakeArkts = { + Config: { create: jest.fn(() => ({ peer: 'peer' })) }, + Context: { createFromString: jest.fn(() => ({ program: {}, peer: 'peer' })) }, + proceedToState: jest.fn(), + Es2pandaContextState: { ES2PANDA_STATE_PARSED: 1, ES2PANDA_STATE_CHECKED: 2 }, + generateTsDeclarationsFromContext: jest.fn(), + destroyConfig: jest.fn(), + EtsScript: { fromContext: jest.fn(() => ({})) } +}; +const fakeArktsGlobal = { + es2panda: { + _SetUpSoPath: jest.fn(), + _DestroyContext: jest.fn() + }, + filePath: '', + config: '', + compilerContext: { program: {}, peer: 'peer' } +}; + +jest.mock('/sdk/koala', () => ({ + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal +}), { virtual: true }); + +beforeEach(() => { + jest.resetModules(); + (process as any).send = jest.fn(); + jest.spyOn(process, 'exit').mockImplementation((() => { throw new Error('exit'); }) as any); + require('path').resolve.mockImplementation((...args: string[]) => { + if (args.includes('koala')) return '/sdk/koala'; + return args.join('/'); + }); + require('path').join.mockImplementation((...args: string[]) => args.join('/')); + require('path').relative.mockImplementation((from: string, to: string) => to.replace(from, '').replace(/^\//, '')); + require('path').basename.mockImplementation((p: string) => p.split('/').pop()); +}); +afterEach(() => { + jest.clearAllMocks(); +}); + +// Test the functions of the declgen_worker.ts file +describe('declgen_worker', () => { + const fileInfo = { + filePath: '/src/foo.ets', + arktsConfigFile: '/src/arktsconfig.json', + packageName: 'pkg' + }; + const buildConfig = { + buildSdkPath: '/sdk', + pandaSdkPath: '/panda' + }; + const moduleInfo = { + moduleRootPath: '/src', + declgenV1OutPath: '/decl', + declgenBridgeCodePath: '/bridge', + packageName: 'pkg' + }; + const moduleInfos = [['pkg', moduleInfo]]; + + test('generate declaration && glue files && exit', () => { + require('fs').readFileSync.mockReturnValue('source code'); + require('../../../src/build/declgen_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); + }).toThrow('exit'); + + expect(require('../../../src/utils').ensurePathExists).toHaveBeenCalledTimes(2); + expect(fakeArkts.Config.create).toHaveBeenCalled(); + expect(fakeArkts.Context.createFromString).toHaveBeenCalled(); + expect(fakeArkts.proceedToState).toHaveBeenCalledWith(1, 'peer', true); + expect(fakeArkts.proceedToState).toHaveBeenCalledWith(2, 'peer', true); + expect(fakeArkts.EtsScript.fromContext).toHaveBeenCalled(); + expect(fakeArkts.generateTsDeclarationsFromContext).toHaveBeenCalled(); + expect(fakeArkts.destroyConfig).toHaveBeenCalled(); + expect(fakeArktsGlobal.es2panda._DestroyContext).toHaveBeenCalled(); + expect(process.exit).toHaveBeenCalledWith(0); + expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ + success: true, + filePath: fileInfo.filePath + })); + }); + + test('handle error && send fail message', () => { + jest.spyOn(process, 'exit').mockImplementation(() => undefined as never); + require('fs').readFileSync.mockImplementation(() => { throw new Error('fail'); }); + require('../../../src/build/declgen_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); + }).not.toThrow(); + + expect(require('../../../src/logger').Logger.printError).toHaveBeenCalled(); + expect(process.send).toHaveBeenCalledWith(expect.objectContaining({ + success: false, + filePath: fileInfo.filePath, + error: expect.any(String) + })); + }); + + test('throw if process.send is undefined', () => { + delete (process as any).send; + require('../../../src/build/declgen_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); + }).toThrow('process.send is undefined. This worker must be run as a forked process.'); + }); + + test('destroy context && config', () => { + require('fs').readFileSync.mockReturnValue('source code'); + require('../../../src/build/declgen_worker'); + expect(() => { + (process as any).emit('message', { taskList: [fileInfo], buildConfig, moduleInfos }); + }).toThrow('exit'); + expect(fakeArkts.destroyConfig).toHaveBeenCalled(); + expect(fakeArktsGlobal.es2panda._DestroyContext).toHaveBeenCalled(); + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts b/ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts index 22feac87f48efdb7d0b01641b5e498c427f41a64..060d7179579e936ed20da410942632ba19f018e1 100755 --- a/ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts +++ b/ets2panda/driver/build_system/test/ut/entryTest/entry.test.ts @@ -13,72 +13,238 @@ * limitations under the License. */ -import * as fs from 'fs'; import * as entryModule from '../../../src/entry'; -import { BuildConfig } from '../../../src/types'; +import { BUILD_TYPE, BuildConfig, OHOS_MODULE_TYPE, BUILD_MODE } from '../../../src/types'; +import { Logger } from '../../../src/logger'; + +jest.mock('../../../src/build/build_mode'); +jest.mock('../../../src/build/build_framework_mode'); +jest.mock('../../../src/logger'); +jest.mock('../../../src/init/process_build_config', () => ({ + processBuildConfig: jest.fn((config) => config) +})); beforeEach(() => { jest.clearAllMocks(); - process.exit = jest.fn() as any; - }); -function main(configFilePath?: string): void { - const buildConfigPath = configFilePath; +function testBuildTypeBranch() { + const BuildMode = require('../../../src/build/build_mode').BuildMode; + const BuildFrameworkMode = require('../../../src/build/build_framework_mode').BuildFrameworkMode; + const mockBuildModeRun = jest.fn().mockResolvedValue(undefined); + BuildMode.mockImplementation(() => { + return { + run: mockBuildModeRun, + generateDeclaration: jest.fn() + }; + }); - const projectConfig: BuildConfig = JSON.parse(fs.readFileSync(buildConfigPath!, 'utf-8')); + const mockLoggerHasErrors = jest.fn().mockReturnValue(false); + Logger.getInstance = jest.fn().mockReturnValue({ + hasErrors: mockLoggerHasErrors, + printInfo: jest.fn(), + printError: jest.fn() + }); - entryModule.build(projectConfig); + const mockConfig = { + buildType: BUILD_TYPE.BUILD, + packageName: 'test', + compileFiles: ['test.ets'], + enableDeclgenEts2Ts: false, + frameworkMode: false, + loaderOutPath: './dist', + cachePath: './dist/cache', + moduleType: OHOS_MODULE_TYPE.HAR, + sourceRoots: ['./'], + moduleRootPath: '/test/path', + buildMode: BUILD_MODE.DEBUG + } as BuildConfig; + return { + buildMode: BuildMode, + frameworkMode: BuildFrameworkMode, + mockBuildModeRun, + mockConfig, + execute: async () => { + await entryModule.build(mockConfig); + expect(BuildMode).toHaveBeenCalledWith(expect.objectContaining({ + buildType: BUILD_TYPE.BUILD, + entryFiles: mockConfig.compileFiles + })); + expect(mockBuildModeRun).toHaveBeenCalled(); + expect(BuildFrameworkMode).not.toHaveBeenCalled(); + } + }; } -describe('test entry.ts file api', () => { - test('test build001', () => { - test_build001(); +function testFrameworkModeBranch() { + const BuildMode = require('../../../src/build/build_mode').BuildMode; + const BuildFrameworkMode = require('../../../src/build/build_framework_mode').BuildFrameworkMode; + + const mockFrameworkModeRun = jest.fn().mockResolvedValue(undefined); + BuildFrameworkMode.mockImplementation(() => { + return { + run: mockFrameworkModeRun + }; }); - test('test build002', () => { - test_build002(); + + const mockLoggerHasErrors = jest.fn().mockReturnValue(false); + Logger.getInstance = jest.fn().mockReturnValue({ + hasErrors: mockLoggerHasErrors, + printInfo: jest.fn(), + printError: jest.fn() }); - test('test build003', () => { - test_build003(); + + const mockConfig = { + buildType: BUILD_TYPE.BUILD, + packageName: 'test', + compileFiles: ['test.ets'], + enableDeclgenEts2Ts: false, + frameworkMode: true, + loaderOutPath: './dist', + cachePath: './dist/cache', + moduleType: OHOS_MODULE_TYPE.HAR, + sourceRoots: ['./'], + moduleRootPath: '/test/path', + buildMode: BUILD_MODE.DEBUG + } as BuildConfig; + + return { + buildMode: BuildMode, + frameworkMode: BuildFrameworkMode, + mockFrameworkModeRun, + mockConfig, + mockLoggerHasErrors, + execute: async () => { + await entryModule.build(mockConfig); + expect(BuildFrameworkMode).toHaveBeenCalledWith(expect.objectContaining({ + frameworkMode: true, + entryFiles: mockConfig.compileFiles + })); + expect(mockFrameworkModeRun).toHaveBeenCalled(); + expect(BuildMode).not.toHaveBeenCalled(); + expect(process.exit).not.toHaveBeenCalled(); + } + }; +} + +function testEnableDeclgenEts2TsBranch() { + const BuildMode = require('../../../src/build/build_mode').BuildMode; + const BuildFrameworkMode = require('../../../src/build/build_framework_mode').BuildFrameworkMode; + const mockGenerateDeclaration = jest.fn().mockResolvedValue(undefined); + BuildMode.mockImplementation(() => { + return { + run: jest.fn(), + generateDeclaration: mockGenerateDeclaration + }; }); - test('test build004', () => { - test_build004(); + + Logger.getInstance = jest.fn().mockReturnValue({ + hasErrors: jest.fn().mockReturnValue(false), + printInfo: jest.fn(), + printError: jest.fn() }); -}); -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' - })); -} + const mockConfig = { + buildType: BUILD_TYPE.BUILD, + packageName: 'test', + compileFiles: ['test.ets'], + enableDeclgenEts2Ts: true, + frameworkMode: false, + loaderOutPath: './dist', + cachePath: './dist/cache', + moduleType: OHOS_MODULE_TYPE.HAR, + sourceRoots: ['./'], + moduleRootPath: '/test/path', + buildMode: BUILD_MODE.DEBUG + } as BuildConfig; -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' - })); + return { + buildMode: BuildMode, + frameworkMode: BuildFrameworkMode, + mockGenerateDeclaration, + mockConfig, + execute: async () => { + await entryModule.build(mockConfig); + expect(BuildMode).toHaveBeenCalledWith(expect.objectContaining({ + enableDeclgenEts2Ts: true, + entryFiles: mockConfig.compileFiles + })); + expect(mockGenerateDeclaration).toHaveBeenCalled(); + expect(BuildFrameworkMode).not.toHaveBeenCalled(); + } + }; } -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 testNoMatchingBranch() { + const BuildMode = require('../../../src/build/build_mode').BuildMode; + const BuildFrameworkMode = require('../../../src/build/build_framework_mode').BuildFrameworkMode; + BuildMode.mockReset(); + BuildFrameworkMode.mockReset(); + const mockBuildModeRun = jest.fn(); + const mockGenerateDeclaration = jest.fn(); + const mockLoggerHasErrors = jest.fn() + .mockReturnValueOnce(true) + .mockReturnValueOnce(false); + Logger.getInstance = jest.fn().mockReturnValue({ + hasErrors: mockLoggerHasErrors, + printInfo: jest.fn(), + printError: jest.fn() + }); + const clean = jest.fn(); + jest.mock('../../../src/entry.ts', () => ({ + clean: clean })); + const mockConfig = { + buildType: BUILD_TYPE.PREVIEW, + packageName: 'test', + compileFiles: ['test.ets'], + enableDeclgenEts2Ts: false, + frameworkMode: true, + loaderOutPath: './dist', + cachePath: './dist/cache', + moduleType: OHOS_MODULE_TYPE.HAR, + sourceRoots: ['./'], + moduleRootPath: '/test/path', + buildMode: BUILD_MODE.DEBUG + } as BuildConfig; + return { + buildMode: BuildMode, + frameworkMode: BuildFrameworkMode, + mockBuildModeRun, + mockGenerateDeclaration, + mockConfig, + mockLoggerHasErrors, + clean, + execute: async () => { + await entryModule.build(mockConfig); + expect(mockLoggerHasErrors).toHaveBeenCalled(); + jest.clearAllMocks(); + await entryModule.build(mockConfig); + expect(BuildMode).not.toHaveBeenCalled(); + } + }; } -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, - })); -} +// Test the functions of the entry.ts file +describe('test entry.ts file api', () => { + test('test build function BUILD_TYPE.BUILD branch', async () => { + const testCase = testBuildTypeBranch(); + await testCase.execute(); + }); + + test('test build function frameworkMode branch', async () => { + const testCase = testFrameworkModeBranch(); + await testCase.execute(); + }); + + test('test build function enableDeclgenEts2Ts branch', async () => { + const testCase = testEnableDeclgenEts2TsBranch(); + await testCase.execute(); + }); + + test('test build function no matching branch', async () => { + const testCase = testNoMatchingBranch(); + await testCase.execute(); + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts b/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts index 4beba39f104d6748739bc37952cd8281f5bad762..809fb3d50fdda97f5858046662faba98897ef9f8 100755 --- a/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts +++ b/ets2panda/driver/build_system/test/ut/fileManagerTest/filemanager.test.ts @@ -17,6 +17,7 @@ import { FileManager } from '../../../src/plugins/FileManager'; import { LANGUAGE_VERSION } from '../../../src/pre_define'; import * as utils from '../../../src/utils'; +// This test suite is for the FileManager class, which manages file paths and language versions in the build system. describe('class FileManager', () => { const mockBuildConfig = { dependentModuleList: [ @@ -36,53 +37,49 @@ describe('class FileManager', () => { FileManager.arkTSModuleMap.clear(); FileManager.staticApiPath.clear(); FileManager.dynamicApiPath.clear(); - // Make tsc ignore the access of private member + // Make tsc ignore the access of private member or type error // @ts-ignore FileManager.buildConfig = undefined; }); - it('should initialize singleton and static properties', () => { + test('initialize singleton && 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(); + const instance = FileManager.getInstance(); + FileManager.init({ + ...mockBuildConfig, + buildSdkPath: '/another/path' + } as any); + expect(FileManager.getInstance()).toBe(instance); }); - it('should add staticApiPath and dynamicApiPath in initSDK', () => { + test('add staticApiPath && 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', () => { + test('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', () => { + test('get language version', () => { FileManager.init(mockBuildConfig as any); - const fm = FileManager.getInstance(); + let 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: [ @@ -93,28 +90,11 @@ describe('class FileManager', () => { } ] } as any); - const fm = FileManager.getInstance(); - // Make tsc ignore the access of private member + fm = FileManager.getInstance(); + // Make tsc ignore the access of private member or type error // @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', () => { + expect(fm.getLanguageVersionByFilePath('/mock/hybrid/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_1); FileManager.init({ dependentModuleList: [ { @@ -127,41 +107,21 @@ describe('class FileManager', () => { buildSdkPath: '/mock/sdk', compileFiles: [] } as any); - const fm = FileManager.getInstance(); - // Make tsc ignore the access of private member + fm = FileManager.getInstance(); + // Make tsc ignore the access of private member or type error // @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 + // Make tsc ignore the access of private member or type error // @ts-ignore FileManager.buildConfig = { compileFiles: [] }; - const fm = FileManager.getInstance(); + 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: [ { @@ -174,43 +134,60 @@ describe('class FileManager', () => { buildSdkPath: '/mock/sdk', compileFiles: [] } as any); - const fm = FileManager.getInstance(); - expect(fm.getLanguageVersionByFilePath('/mock/path/modB/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_2); + fm = FileManager.getInstance(); + expect(fm.getLanguageVersionByFilePath('/mock/path/modB/file.ets')).toBe(LANGUAGE_VERSION.ARKTS_1_1); }); - it('should isFirstLineUseStatic returns false when first line is not use static', () => { - jest.spyOn(utils, 'readFirstLineSync').mockReturnValue('not static'); - + test('empty dependentModuleList', () => { + // Make tsc ignore the access of private member or type error // @ts-ignore - expect(FileManager['isFirstLineUseStatic']('anyfile.ets')).toBe(false); + FileManager['initLanguageVersionFromDependentModuleMap']([]); + expect(FileManager.arkTSModuleMap.size).toBe(0); }); - it('should init does nothing if already initialized', () => { + test('clean singleton instance', () => { FileManager.init(mockBuildConfig as any); - const instance = FileManager.getInstance(); + FileManager.cleanFileManagerObject(); + // Make tsc ignore the access of private member or type error + // @ts-ignore + expect(FileManager.instance).toBeUndefined(); FileManager.init({ - ...mockBuildConfig, - buildSdkPath: '/another/path' + dependentModuleList: [], + externalApiPaths: [], + buildSdkPath: '/mock/sdk', + compileFiles: [] } as any); - expect(FileManager.getInstance()).toBe(instance); - }); - - it('should cleanFileManagerObject does nothing if instance is undefined', () => { + FileManager.cleanFileManagerObject(); + expect(FileManager.getInstance()).toBeInstanceOf(FileManager); + FileManager.cleanFileManagerObject(); + // Make tsc ignore the access of private member or type error + // @ts-ignore + expect(FileManager.instance).toBeUndefined(); + // Make tsc ignore the access of private member or type error // @ts-ignore FileManager.instance = undefined; expect(() => FileManager.cleanFileManagerObject()).not.toThrow(); }); - it('should getLanguageVersionByFilePath handles undefined compileFiles', () => { + test('isFirstLineUseStatic', () => { + jest.spyOn(utils, 'readFirstLineSync').mockReturnValue('not static'); + // Make tsc ignore the access of private member or type error + // @ts-ignore + expect(FileManager['isFirstLineUseStatic']('anyfile.ets')).toBe(false); + }); + + test('getLanguageVersionByFilePath handles undefined compileFiles', () => { FileManager.cleanFileManagerObject(); + // Make tsc ignore the access of private member or type error // @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', () => { + test('initSDK', () => { + // Make tsc ignore the access of private member or type error // @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 index aa044c0a81a49358b4424a2dc0dd034eadff6557..b0841ae6622a922c07910e1fcc49a8d350ae6693 100755 --- 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 @@ -13,102 +13,912 @@ * 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, - }; -}); +import { ModuleInfo, OHOS_MODULE_TYPE } from '../../../src/types'; +import { mockLogger, moduleInfoWithNullSourceRoots, moduleInfoWithFalseEts2Ts } from '../mock/mockData'; +import * as fs from 'fs'; +// Define the AliasConfig interface for testing +interface AliasConfig { + isStatic: boolean; + originalAPIName: string; + [key: string]: any; +} + +// generate_arktsconfig test suite. describe('test generate_arktsconfig.ts file api', () => { - test('test getInstance', () => { + + afterEach(() => { + ArkTSConfigGenerator.destroyInstance(); + }); + + test('should throw error if buildConfig or moduleInfos is not provided on first instantiation', () => { expect(() => { ArkTSConfigGenerator.getInstance(); }).toThrow('buildConfig and moduleInfos is required for the first instantiation of ArkTSConfigGenerator.'); }); - test('test getOhmurl', () => { - test_getOhmurl(); +}); + +describe('test writeArkTSConfigFile in normal and abnormal scenarios', () => { + let generator: ArkTSConfigGenerator; + beforeEach(() => { + // Mock ArkTSConfigGenerator instance + generator = Object.create(ArkTSConfigGenerator.prototype); + (generator as any).getPathSection = jest.fn().mockReturnValue({ + std: ['/path/to/stdlib/std'], + escompat: ['/path/to/stdlib/escompat'], + }); + (generator as any).getDependenciesSection = jest.fn(); + (generator as any).getDynamicPathSection = jest.fn(); + (generator as any).logger = mockLogger; + }); + + afterEach(() => { + jest.clearAllMocks(); }); - test('test writeArkTSConfigFile', () => { - test_writeArkTSConfigFile(); + test('should throw error if sourceRoots is empty', () => { + expect(() => { + generator.writeArkTSConfigFile(moduleInfoWithNullSourceRoots, false); + }).toThrow('Exit with error.'); }); + + test('should generate correct arktsConfig when enableDeclgenEts2Ts is false', () => { + generator.writeArkTSConfigFile(moduleInfoWithFalseEts2Ts, false); + expect((generator as any).getDynamicPathSection).toHaveBeenCalled(); + }) }); +// Test suite for handleEntryFile method in ArkTSConfigGenerator. +describe('handleEntryFile', () => { + test('should add path to pathSection for ARKTS_1_2 language module', () => { + const fs = require('fs'); + const path = require('path'); -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'); -} + jest.spyOn(fs, 'statSync').mockReturnValue({ + isFile: () => true + } as fs.Stats); -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); -} + jest.spyOn(fs, 'readFileSync').mockReturnValue('any content'); + + jest.spyOn(path, 'resolve').mockImplementation((modulePath, sourceRoot) => + `${modulePath}/${sourceRoot}`); + + (global as any).LogDataFactory = { + newInstance: jest.fn().mockReturnValue({ + toString: () => 'Mock Error', + code: '123', + message: 'Test Error' + }) + }; + (global as any).ErrorCode = { + BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' + }; + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.pathSection = {}; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + + const LANGUAGE_VERSION = { + ARKTS_1_2: "11.0", + ARKTS_HYBRID: "hybrid" + }; + + const moduleInfo: ModuleInfo = { + packageName: "testModule", + moduleRootPath: "/modules/testModule", + sourceRoots: ["src"], + entryFile: "/modules/testModule/src/index.ets", + language: LANGUAGE_VERSION.ARKTS_1_2, + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + }); + + test('should add path to pathSection for ARKTS_HYBRID language module with "use static"', () => { + const fs = require('fs'); + const path = require('path'); + + jest.spyOn(fs, 'statSync').mockReturnValue({ + isFile: () => true + } as fs.Stats); + + jest.spyOn(fs, 'readFileSync').mockReturnValue('use static\nother content'); + + jest.spyOn(path, 'resolve').mockImplementation((modulePath, sourceRoot) => + `${modulePath}/${sourceRoot}`); + + (global as any).LogDataFactory = { + newInstance: jest.fn().mockReturnValue({ + toString: () => 'Mock Error', + code: '123', + message: 'Test Error' + }) + }; + (global as any).ErrorCode = { + BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' + }; + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.pathSection = {}; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + + const LANGUAGE_VERSION = { + ARKTS_1_2: "11.0", + ARKTS_HYBRID: "hybrid" + }; + + const moduleInfo: ModuleInfo = { + packageName: "hybridModule", + moduleRootPath: "/modules/hybridModule", + sourceRoots: ["src"], + entryFile: "/modules/hybridModule/src/index.ets", + language: LANGUAGE_VERSION.ARKTS_HYBRID, + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); + + expect(generator.pathSection).toEqual({ + "hybridModule": ["/modules/hybridModule/src"] + }); + + expect(fs.statSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets"); + expect(fs.readFileSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets", "utf-8"); + expect(path.resolve).toHaveBeenCalledWith("/modules/hybridModule", "src"); + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + }); + + test('should NOT add path to pathSection for ARKTS_HYBRID language module without "use static"', () => { + const fs = require('fs'); + const path = require('path'); + + jest.spyOn(fs, 'statSync').mockReturnValue({ + isFile: () => true + } as fs.Stats); + + jest.spyOn(fs, 'readFileSync').mockReturnValue('import something\nother content'); + + jest.spyOn(path, 'resolve').mockImplementation((modulePath, sourceRoot) => + `${modulePath}/${sourceRoot}`); + + (global as any).LogDataFactory = { + newInstance: jest.fn().mockReturnValue({ + toString: () => 'Mock Error', + code: '123', + message: 'Test Error' + }) + }; + (global as any).ErrorCode = { + BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' + }; + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.pathSection = {}; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + + const LANGUAGE_VERSION = { + ARKTS_1_2: "11.0", + ARKTS_HYBRID: "hybrid" + }; + + const moduleInfo: ModuleInfo = { + packageName: "hybridModule", + moduleRootPath: "/modules/hybridModule", + sourceRoots: ["src"], + entryFile: "/modules/hybridModule/src/index.ets", + language: LANGUAGE_VERSION.ARKTS_HYBRID, + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); + + expect(generator.pathSection).toEqual({}); + + expect(fs.statSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets"); + expect(fs.readFileSync).toHaveBeenCalledWith("/modules/hybridModule/src/index.ets", "utf-8"); + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + }); + + test('should handle error when entry file does not exist', () => { + const fs = require('fs'); + const path = require('path'); + + jest.spyOn(fs, 'statSync').mockImplementation(() => { + throw new Error('ENOENT: no such file or directory'); + }); + + (global as any).LogDataFactory = { + newInstance: jest.fn().mockReturnValue({ + toString: () => 'Mock Error', + code: '123', + message: 'Test Error' + }) + }; + (global as any).ErrorCode = { + BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' + }; + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.pathSection = {}; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + + const LANGUAGE_VERSION = { + ARKTS_1_2: "11.0", + ARKTS_HYBRID: "hybrid" + }; + + const moduleInfo: ModuleInfo = { + packageName: "errorModule", + moduleRootPath: "/modules/errorModule", + sourceRoots: ["src"], + entryFile: "/modules/errorModule/src/nonexistent.ets", + language: LANGUAGE_VERSION.ARKTS_1_2, + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); + + expect(generator.pathSection).toEqual({}); + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + }); + + test('should return early when entry file is not a regular file', () => { + const fs = require('fs'); + + jest.spyOn(fs, 'statSync').mockReturnValue({ + isFile: () => false, + isDirectory: () => true + } as fs.Stats); + + (global as any).LogDataFactory = { + newInstance: jest.fn() + }; + (global as any).ErrorCode = { + BUILDSYSTEM_HANDLE_ENTRY_FILE: '11410200' + }; + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.pathSection = {}; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + + const LANGUAGE_VERSION = { + ARKTS_1_2: "11.0", + ARKTS_HYBRID: "hybrid" + }; + + const moduleInfo: ModuleInfo = { + packageName: "directoryModule", + moduleRootPath: "/modules/directoryModule", + sourceRoots: ["src"], + entryFile: "/modules/directoryModule/src", + language: LANGUAGE_VERSION.ARKTS_1_2, + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + (ArkTSConfigGenerator.prototype as any).handleEntryFile.call(generator, moduleInfo); + + expect(generator.pathSection).toEqual({}); + + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + }); +}); + +describe('test if the getDynamicPathSection is working correctly', () => { + test('should properly process dynamic dependencies and their declaration files', () => { + const fs = require('fs'); + const path = require('path'); + + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + jest.spyOn(fs, 'readFileSync').mockReturnValue(JSON.stringify({ + files: { + 'src/file1.ets': { + declPath: '/modules/dep1/dist/file1.d.ts', + ohmUrl: 'dep1/file1' + }, + 'src/index.ets': { + declPath: '/modules/dep1/dist/index.d.ts', + ohmUrl: 'dep1/index' + }, + 'src/file2.ets': { + declPath: '/modules/dep1/dist/file2.d.ts', + ohmUrl: 'dep1/file2' + } + } + })); + + jest.spyOn(path, 'resolve').mockImplementation((root, file) => `${root}/${file}`); + + (global as any).changeFileExtension = jest.fn((filePath, newExt) => { + return filePath.replace(/\.[^/.]+$/, newExt); + }); + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.getOhmurl = jest.fn((file, depModuleInfo) => { + return `${depModuleInfo.packageName}/${file.replace(/^src\//, '').replace(/\.ets$/, '')}`; + }); + + const depModuleInfo: ModuleInfo = { + packageName: "dep1", + moduleRootPath: "/modules/dep1", + sourceRoots: ["src"], + entryFile: "/modules/dep1/src/index.ets", + declFilesPath: "/modules/dep1/dist/decls.json", + language: "11.0", + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: false, + byteCodeHar: false + } as any; + + const moduleInfo: ModuleInfo = { + packageName: "mainModule", + moduleRootPath: "/modules/main", + sourceRoots: ["src"], + entryFile: "/modules/main/src/index.ets", + language: "11.0", + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map([ + ["dep1", depModuleInfo] + ]), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + const dynamicPathSection: Record = {}; + (ArkTSConfigGenerator.prototype as any).getDynamicPathSection.call(generator, moduleInfo, dynamicPathSection); + + expect(fs.existsSync).toHaveBeenCalledWith("/modules/dep1/dist/decls.json"); + expect(fs.readFileSync).toHaveBeenCalledWith("/modules/dep1/dist/decls.json", "utf-8"); + expect(generator.getOhmurl).toHaveBeenCalledWith("src/file1.ets", depModuleInfo); + expect(generator.getOhmurl).toHaveBeenCalledWith("src/index.ets", depModuleInfo); + expect(generator.getOhmurl).toHaveBeenCalledWith("src/file2.ets", depModuleInfo); + expect(path.resolve).toHaveBeenCalledWith("/modules/dep1", "src/file1.ets"); + expect(path.resolve).toHaveBeenCalledWith("/modules/dep1", "src/index.ets"); + expect(path.resolve).toHaveBeenCalledWith("/modules/dep1", "src/file2.ets"); + + delete (global as any).changeFileExtension; + }); + + test('should skip dependency if declaration file does not exist', () => { + const fs = require('fs'); + const path = require('path'); + + const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => { }); + + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + const depModuleInfo: ModuleInfo = { + packageName: "dep1", + moduleRootPath: "/modules/dep1", + sourceRoots: ["src"], + entryFile: "/modules/dep1/src/index.ets", + declFilesPath: "/modules/dep1/dist/decls.json", + language: "11.0", + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: false, + byteCodeHar: false + } as any; + + const moduleInfo: ModuleInfo = { + packageName: "mainModule", + moduleRootPath: "/modules/main", + sourceRoots: ["src"], + entryFile: "/modules/main/src/index.ets", + language: "11.0", + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map([ + ["dep1", depModuleInfo] + ]), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + const dynamicPathSection: Record = {}; + (ArkTSConfigGenerator.prototype as any).getDynamicPathSection.call(generator, moduleInfo, dynamicPathSection); + + expect(dynamicPathSection).toEqual({}); + + expect(consoleErrorMock).toHaveBeenCalled(); + expect(consoleErrorMock.mock.calls[0][0]).toContain("mainModule depends on dynamic module dep1"); + + expect(fs.existsSync).toHaveBeenCalledWith("/modules/dep1/dist/decls.json"); + + consoleErrorMock.mockRestore(); + }); + + test('should handle missing declFilesPath property', () => { + const fs = require('fs'); + + const consoleErrorMock = jest.spyOn(console, 'error').mockImplementation(() => { }); + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + const depModuleInfo: ModuleInfo = { + packageName: "dep1", + moduleRootPath: "/modules/dep1", + sourceRoots: ["src"], + entryFile: "/modules/dep1/src/index.ets", + language: "11.0", + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: false, + byteCodeHar: false + } as any; + + const moduleInfo: ModuleInfo = { + packageName: "mainModule", + moduleRootPath: "/modules/main", + sourceRoots: ["src"], + entryFile: "/modules/main/src/index.ets", + language: "11.0", + arktsConfigFile: "/config/arktsconfig.json", + compileFileInfos: [], + dynamicDepModuleInfos: new Map([ + ["dep1", depModuleInfo] + ]), + staticDepModuleInfos: new Map(), + moduleType: OHOS_MODULE_TYPE.HAR, + isMainModule: true, + byteCodeHar: false + } as any; + + const dynamicPathSection: Record = {}; + (ArkTSConfigGenerator.prototype as any).getDynamicPathSection.call(generator, moduleInfo, dynamicPathSection); + + expect(dynamicPathSection).toEqual({}); + + expect(consoleErrorMock).toHaveBeenCalled(); + expect(consoleErrorMock.mock.calls[0][0]).toContain("mainModule depends on dynamic module dep1"); + + consoleErrorMock.mockRestore(); + }); +}); + +describe('test if the generateSystemSdkPathSection is working correctly', () => { + test('should traverse directories and add correct paths to pathSection', () => { + const fs = require('fs'); + const path = require('path'); + + jest.spyOn(fs, 'existsSync').mockImplementation(function (path) { + return ['/sdk/api', '/sdk/arkts'].includes(path as string); + }); + + jest.spyOn(fs, 'readdirSync').mockImplementation(function (dir) { + if (dir === '/sdk/api') { + return ['web.d.ets', 'component', 'arkui']; + } + if (dir === '/sdk/api/component') { + return ['button.d.ets', 'text.d.ets']; + } + if (dir === '/sdk/api/arkui') { + return ['runtime-api']; + } + if (dir === '/sdk/api/arkui/runtime-api') { + return ['special.d.ets']; + } + if (dir === '/sdk/arkts') { + return ['common.d.ets', 'utils']; + } + if (dir === '/sdk/arkts/utils') { + return ['helper.d.ets']; + } + return []; + }); + + jest.spyOn(fs, 'statSync').mockImplementation(function (itemPath) { + const isFile = (itemPath as string).endsWith('.d.ets'); + return { + isFile: () => isFile, + isDirectory: () => !isFile + } as fs.Stats; + }); + + jest.spyOn(path, 'basename').mockImplementation(function (p, ext) { + const base = (p as string).split('/').pop() || ''; + return ext && base.endsWith(ext as string) ? base.slice(0, -(ext as string).length) : base; + }); + + jest.spyOn(path, 'join').mockImplementation(function () { + return Array.from(arguments).join('/'); + }); + + jest.spyOn(path, 'resolve').mockImplementation(function (a, b) { + return `${a as string}/${b as string}`; + }); + + (global as any).changeFileExtension = jest.fn(function (filePath, newExt, oldExt) { + return (filePath as string).replace(oldExt as string, newExt as string); + }); + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.systemSdkPath = '/sdk'; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn(), + printWarn: jest.fn() + }; + + const pathSection: Record = {}; + (ArkTSConfigGenerator.prototype as any).generateSystemSdkPathSection.call(generator, pathSection); + + expect(generator.logger.printWarn).toHaveBeenCalledWith('sdk path /sdk/kits not exist.'); + + delete (global as any).changeFileExtension; + }); + + test('should use externalApiPaths when provided', () => { + const fs = require('fs'); + const path = require('path'); + + jest.spyOn(fs, 'existsSync').mockImplementation(function (path) { + return path === '/external/api'; + }); + + jest.spyOn(fs, 'readdirSync').mockImplementation(function (dir) { + if (dir === '/external/api') { + return ['external.d.ets', 'widgets']; + } + if (dir === '/external/api/widgets') { + return ['widget.d.ets']; + } + return []; + }); + + jest.spyOn(fs, 'statSync').mockImplementation(function (itemPath) { + const isFile = (itemPath as string).endsWith('.d.ets'); + return { + isFile: () => isFile, + isDirectory: () => !isFile + } as fs.Stats; + }); + + jest.spyOn(path, 'basename').mockImplementation(function (p, ext) { + const base = (p as string).split('/').pop() || ''; + return ext && base.endsWith(ext as string) ? base.slice(0, -(ext as string).length) : base; + }); + + jest.spyOn(path, 'join').mockImplementation(function () { + return Array.from(arguments).join('/'); + }); + + jest.spyOn(path, 'resolve').mockImplementation(function (a, b) { + return `${a as string}/${b as string}`; + }); + + (global as any).changeFileExtension = jest.fn(function (filePath, newExt, oldExt) { + return (filePath as string).replace(oldExt as string, newExt as string); + }); + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.systemSdkPath = '/sdk'; + generator.externalApiPaths = ['/external/api', '/nonexistent/path']; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn(), + printWarn: jest.fn() + }; + + const pathSection: Record = {}; + (ArkTSConfigGenerator.prototype as any).generateSystemSdkPathSection.call(generator, pathSection); + + expect(pathSection).toEqual({ + 'external': ['/external/api/external'], + 'widgets.widget': ['/external/api/widgets/widget'] + }); + + expect(generator.logger.printWarn).toHaveBeenCalledWith('sdk path /nonexistent/path not exist.'); + + delete (global as any).changeFileExtension; + }); + + test('should skip non-allowed file extensions', () => { + const fs = require('fs'); + const path = require('path'); + + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + + jest.spyOn(fs, 'readdirSync').mockImplementation(function (dir) { + if (dir === '/sdk/api') { + return [ + 'valid.d.ets', + 'invalid.js', + 'another.ts', + 'weird.d.ets.bak' + ]; + } + return []; + }); + + jest.spyOn(fs, 'statSync').mockImplementation(function (itemPath) { + return { + isFile: () => true, + isDirectory: () => false + } as fs.Stats; + }); + + jest.spyOn(path, 'basename').mockImplementation(function (p, ext) { + const base = (p as string).split('/').pop() || ''; + return ext && base.endsWith(ext as string) ? base.slice(0, -(ext as string).length) : base; + }); + + jest.spyOn(path, 'join').mockImplementation(function () { + return Array.from(arguments).join('/'); + }); + + jest.spyOn(path, 'resolve').mockImplementation(function (a, b) { + return `${a}/${b}`; + }); + + (global as any).changeFileExtension = jest.fn(function (filePath, newExt, oldExt) { + return filePath.replace(oldExt, newExt); + }); + + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.systemSdkPath = '/sdk'; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn(), + printWarn: jest.fn() + }; + + const pathSection: Record = {}; + (ArkTSConfigGenerator.prototype as any).generateSystemSdkPathSection.call(generator, pathSection); + + delete (global as any).changeFileExtension; + }); +}); + +describe('test if the processAlias is working correctly', () => { + test('should handle both static and dynamic aliases correctly', () => { + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.processStaticAlias = jest.fn(); + generator.processDynamicAlias = jest.fn(); + + const aliasConfig = new Map>(); + + const moduleAliasConfig = new Map(); + moduleAliasConfig.set("static1", { + isStatic: true, + originalAPIName: "@ohos.test1" + } as any); + + moduleAliasConfig.set("kit1", { + isStatic: false, + originalAPIName: "@kit.test2" + } as any); + + moduleAliasConfig.set("dynamic1", { + isStatic: false, + originalAPIName: "@ohos.test3" + } as any); + + aliasConfig.set("testModule", moduleAliasConfig); + generator.aliasConfig = aliasConfig; + + const moduleInfo: ModuleInfo = { + packageName: "testModule", + moduleRootPath: "/test/path", + sourceRoots: ["src"], + arktsConfigFile: "/test/path/config.json", + compileFileInfos: [] + } as any; + + const dynamicPathSection: Record = {}; + + (ArkTSConfigGenerator.prototype as any).processAlias.call( + generator, + moduleInfo, + dynamicPathSection + ); + + expect(generator.processStaticAlias).toHaveBeenCalledWith( + "static1", + { isStatic: true, originalAPIName: "@ohos.test1" } + ); + + expect(generator.processStaticAlias).toHaveBeenCalledWith( + "kit1", + { isStatic: false, originalAPIName: "@kit.test2" } + ); + + expect(generator.processDynamicAlias).toHaveBeenCalledWith( + "dynamic1", + { isStatic: false, originalAPIName: "@ohos.test3" }, + dynamicPathSection + ); + + expect(generator.processStaticAlias).toHaveBeenCalledTimes(2); + expect(generator.processDynamicAlias).toHaveBeenCalledTimes(1); + }); + + test('should handle undefined aliasConfig gracefully', () => { + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.processStaticAlias = jest.fn(); + generator.processDynamicAlias = jest.fn(); + + generator.aliasConfig = new Map>(); + + const moduleInfo: ModuleInfo = { + packageName: "testModule", + moduleRootPath: "/test/path", + sourceRoots: ["src"], + arktsConfigFile: "/test/path/config.json", + compileFileInfos: [] + } as any; + + const dynamicPathSection: Record = {}; + + (ArkTSConfigGenerator.prototype as any).processAlias.call( + generator, + moduleInfo, + dynamicPathSection + ); + + expect(generator.processStaticAlias).not.toHaveBeenCalled(); + expect(generator.processDynamicAlias).not.toHaveBeenCalled(); + }); + + test('should handle null aliasConfig gracefully', () => { + const generator = Object.create(ArkTSConfigGenerator.prototype); + + generator.processStaticAlias = jest.fn(); + generator.processDynamicAlias = jest.fn(); + + generator.aliasConfig = null; + + const moduleInfo: ModuleInfo = { + packageName: "testModule", + moduleRootPath: "/test/path", + sourceRoots: ["src"], + arktsConfigFile: "/test/path/config.json", + compileFileInfos: [] + } as any; + + const dynamicPathSection: Record = {}; + + (ArkTSConfigGenerator.prototype as any).processAlias.call( + generator, + moduleInfo, + dynamicPathSection + ); + + expect(generator.processStaticAlias).not.toHaveBeenCalled(); + expect(generator.processDynamicAlias).not.toHaveBeenCalled(); + }); +}); + +describe('test if the processDynamicAlias is working correctly', () => { + test('should handle API with non-existent declaration file', () => { + const fs = require('fs'); + jest.spyOn(fs, 'existsSync').mockReturnValue(false); + + (global as any).getInteropFilePathByApi = jest.fn().mockReturnValue('/sdk/apis/interop/ohos.missing.d.ts'); + (global as any).getOhmurlByApi = jest.fn().mockReturnValue('@ohos.missing'); + + (global as any).LogDataFactory = { + newInstance: jest.fn().mockReturnValue({ + toString: () => 'Mock Error: Interop SDK File Not Exist', + code: '11410500', + message: 'Interop SDK File Not Exist: /sdk/apis/interop/ohos.missing.d.ts' + }) + }; + + (global as any).ErrorCode = { + BUILDSYSTEM_INTEROP_SDK_NOT_FIND: '11410500' + }; + + const generator = Object.create(ArkTSConfigGenerator.prototype); + generator.dynamicSDKPaths = ['/sdk/apis/interop']; + generator.logger = { + printError: jest.fn(), + printInfo: jest.fn() + }; + + const aliasName = 'missingAlias'; + const aliasConfig = { + isStatic: false, + originalAPIName: '@ohos.missing' + } as AliasConfig; + const dynamicPathSection: Record = {}; + + (ArkTSConfigGenerator.prototype as any).processDynamicAlias.call( + generator, + aliasName, + aliasConfig, + dynamicPathSection + ); + + expect(fs.existsSync).toHaveBeenCalledWith('/modules/dep1/dist/decls.json'); + + delete (global as any).getInteropFilePathByApi; + delete (global as any).getOhmurlByApi; + delete (global as any).LogDataFactory; + delete (global as any).ErrorCode; + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts b/ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts index 90c5bfd82d3b72d0502eebccb6939e3e1fd65244..1d79e806f572fac1200aa9accdcbb3548d8f7b9c 100755 --- a/ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts +++ b/ets2panda/driver/build_system/test/ut/loggerTest/logger.test.ts @@ -47,10 +47,12 @@ function createMockBuildConfig(): BuildConfig { cachePath: '', externalApiPaths: [], enableDeclgenEts2Ts: false, + //`as unknown` to satisfy TypeScript type checking } as unknown as BuildConfig; } -describe('Logger class', () => { +// This test suite is for the Logger class, which handles logging for different subsystems in the build system. +describe('test Logger class', () => { let logger: Logger; beforeEach(() => { @@ -63,65 +65,47 @@ describe('Logger class', () => { jest.restoreAllMocks(); }); - test('getInstance throws if not initialized', () => { + test('singleton', () => { 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()); + const logger3 = Logger.getInstance(createMockBuildConfig()); Logger.destroyInstance(); - const logger2 = Logger.getInstance(createMockBuildConfig()); - expect(logger1).not.toBe(logger2); + const logger4 = Logger.getInstance(createMockBuildConfig()); + expect(logger3).not.toBe(logger4); }); - test('printInfo calls underlying logger', () => { + test('printInfo', () => { 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.printInfo('info'); + logger.printWarn('warn'); logger.printDebug('debug'); + expect(spy).toHaveBeenCalledWith('info'); + expect(spy).toHaveBeenCalledWith('warn'); expect(spy).toHaveBeenCalledWith('debug'); }); - test('printError sets hasErrorOccurred and calls printError', () => { + test('printError && printErrorAndExit', () => { const spy = jest.fn(); - // insert '001' into the map, for test + // insert persudo code '001' && '002' into the map, for testing. (logger as any).loggerMap['001'] = { printError: spy }; - const logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc'); + (logger as any).loggerMap['002'] = { printErrorAndExit: spy }; + let 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'); + logData = LogDataFactory.newInstance('00200001' as ErrorCode, 'desc'); logger.printErrorAndExit(logData); expect((logger as any).hasErrorOccurred).toBe(true); expect(spy).toHaveBeenCalledWith(logData); }); - test('hasErrors and resetErrorFlag', () => { + test('hasErrors && resetErrorFlag', () => { expect(logger.hasErrors()).toBe(false); (logger as any).hasErrorOccurred = true; expect(logger.hasErrors()).toBe(true); @@ -129,33 +113,22 @@ describe('Logger class', () => { expect(logger.hasErrors()).toBe(false); }); - test('isValidErrorCode returns true for 8 digits', () => { + test('ValidErrorCode', () => { 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() - }; + test('getLoggerFromSubsystemCode', () => { + 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', () => { + test('getLoggerFromErrorCode', () => { expect(() => (logger as any).getLoggerFromErrorCode('badcode')).toThrow('Invalid errorCode.'); - }); - - test('getLoggerFromErrorCode returns logger for valid code', () => { const fakeLogger = { printInfo: jest.fn(), printWarn: jest.fn(), @@ -168,9 +141,11 @@ describe('Logger class', () => { }); }); -describe('LogDataFactory and LogData', () => { +// This test suite is for the LogDataFactory and LogData classes, which are used to create log data instances. +describe('test LogDataFactory && LogData', () => { test('LogDataFactory.newInstance creates LogData', () => { - const logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc', 'cause', 'pos', ['sol1', 'sol2'], { foo: 'bar' }); + let 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'); @@ -178,21 +153,15 @@ describe('LogDataFactory and LogData', () => { 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(); + logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc', 'cause', 'pos', ['sol1'], { foo: 'bar' }); + let 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(); + logData = LogDataFactory.newInstance('00100001' as ErrorCode, 'desc', '', '', [''], undefined); + 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/entry/a.ets b/ets2panda/driver/build_system/test/ut/mock/a.ets similarity index 100% rename from ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/entry/a.ets rename to ets2panda/driver/build_system/test/ut/mock/a.ets 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 deleted file mode 100755 index 000e0a1d00021ad62d75d32cafcd60e82b854546..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index 0f4b843bced861e6408ad2746ec582558f465425..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config1.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index 5db55059d6735fa16935237dc0072b4d5d64467c..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config2.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "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 deleted file mode 100755 index 18f3232dbd350d9b8d5390bf22609e1365727d07..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config3.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index 9b4fa009f5f36f598e17e49bdb99a36a71d2c54e..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config4.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index 9b4fa009f5f36f598e17e49bdb99a36a71d2c54e..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config5.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index b696a99c42e58567e26cfb76427dff9a7a08e7c9..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config6.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index 12a2810108783492b45a88663c9d477a7329b532..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config7.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index d4ca4121f898d2164d07c770cc8d337a2b57c1e3..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config8.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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 deleted file mode 100755 index 7c21410ce6cfa1a0255d57973302b929dc41e7e1..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/build_config9.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "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/harA/index.ets b/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets deleted file mode 100755 index 46ec690949a54a32be627a9c5347eb0fcdcfe536..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/index.ets +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 deleted file mode 100755 index de553adf3a44c0218446f235b50b426a6816f44f..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/harA/sub.ets +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 { add } from 'hspA' - -export function sub(a: number, b: number) { - let sum = add(a, b) - console.log(sum) - return a - b -} 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 deleted file mode 100755 index db03b551fc823831d910ed131f5852fe1ff3a61b..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/Calc.ets +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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 function add(a: number, b: number) { - return a + b -} 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 deleted file mode 100755 index bb4ae32be3cc9f16c70ccf117e89e792a04c8989..0000000000000000000000000000000000000000 --- a/ets2panda/driver/build_system/test/ut/mock/demo_1.2_dep_hsp1.2/hspA/index.ets +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 { add } from './Calc' diff --git a/ets2panda/driver/build_system/test/ut/mock/mockData.ts b/ets2panda/driver/build_system/test/ut/mock/mockData.ts new file mode 100755 index 0000000000000000000000000000000000000000..7323ef4691be06d53dfcad3c7bc9831fc9543588 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/mock/mockData.ts @@ -0,0 +1,89 @@ +/* + * 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 { ModuleInfo } from '../../../src/types'; + +export const moduleInfoWithNullSourceRoots: 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() +}; + +export const moduleInfoWithFalseEts2Ts: ModuleInfo = { + isMainModule: true, + packageName: 'test-pkg', + moduleRootPath: '/tmp/test-module', + moduleType: 'type', + sourceRoots: ['/src/moduleA'], + entryFile: 'index.ets', + arktsConfigFile: 'arktsconfig.json', + compileFileInfos: [], + declgenV1OutPath: undefined, + declgenV2OutPath: undefined, + declgenBridgeCodePath: undefined, + byteCodeHar: false, + staticDepModuleInfos: new Map(), + dynamicDepModuleInfos: new Map(), +}; + +export const moduleInfo: ModuleInfo = { + isMainModule: false, + packageName: 'moduleA', + moduleRootPath: '/src/moduleA', + moduleType: 'feature', + sourceRoots: ['/src/moduleA'], + entryFile: '/src/moduleA/index.ts', + arktsConfigFile: '/path/to/moduleA/arktsConfig.json', + compileFileInfos: [], + dynamicDepModuleInfos: new Map(), + staticDepModuleInfos: new Map(), + declgenV1OutPath: '/path/to/moduleA/declgen/v1', + declgenV2OutPath: '/path/to/moduleA/declgen/v2', + declgenBridgeCodePath: '/path/to/moduleA/bridge/code', + language: "1.2", + declFilesPath: '/path/to/moduleA/declFiles', + dependencies: [], + byteCodeHar: true, + abcPath: '/path/to/moduleA/abc/file.abc' +}; + +export const mockModuleInfos: Map = new Map([ + [ + 'moduleA', + { + ...moduleInfoWithFalseEts2Ts + }, + ], +]); + +export let mockLogger: mockLogger = { + printErrorAndExit: jest.fn(() => { throw new Error('Exit with error.'); }), + printWarn: jest.fn(() => { throw new Error('Warn'); }), +}; +interface mockLogger { + printErrorAndExit: jest.Mock; + printWarn: jest.Mock; +} 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 index b5ade7c42b995fe09989624110366f344f821a86..b9fa0450e6d934728df4f55bee2eb57970b36ea1 100755 --- 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 @@ -13,150 +13,265 @@ * 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]; - } +import { PluginDriver, PluginHook } from '../../../src/plugins/plugins_driver'; +import { + BuildConfig, + BUILD_MODE, + BUILD_TYPE, + OHOS_MODULE_TYPE, + PluginsConfig +} from '../../../src/types'; + +jest.mock('../../../src/logger'); + +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: [ + ] +} as any; + +// The PluginDriver class is responsible for managing and executing plugins in the build system. +describe('test PluginDriver', () => { + beforeEach(() => { + PluginDriver.destroyInstance(); + jest.clearAllMocks(); + }); + test('constructor', () => { + test_construcotr(); }); - 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, -} + test('getInstance', () => { + test_getInstance(); + }); -jest.mock('path/to/valid/plugin', () => { - return { - validPlugin: () => { }, - }; -}, { virtual: true }); + test('destroyInstance', () => { + test_destroyInstance(); + }); -jest.mock('path/to/invalid/plugin', () => { - return { - invalidPlugin: {}, - }; -}, { virtual: true }); + test('getPluginContext', () => { + test_getPluginContext(); + }); + + test('setArkTSAst and getArkTSAst', () => { + test_setArkTSAstAndGetArkTSAst(); + }); -describe('test plugins_driver.ts file api', () => { - test('test initPlugins001', () => { + test('setArkTSProgram and getArkTSProgram', () => { + test_setArkTSProgramAndGetArkTSProgram(); + }); + + test('setProjectConfig and getProjectConfig', () => { + test_setProjectConfigAndGetProjectConfig(); + }); + + test('setFileManager and getFileManager', () => { + test_setFileManagerAndGetFileManager(); + }); + + test('setContextPtr and getContextPtr', () => { + test_setContextPtrAndGetContextPtr(); + }); + + test('runPluginHook001', () => { + test_runPluginHook001(); + }); + + test('runPluginHook002', () => { + test_runPluginHook002(); + }); + + test('getPlugins', () => { + test_getPlugins(); + }); + + test('initPlugins001', () => { test_initPlugins001(); }); - test('test initPlugins002', () => { + test('initPlugins002', () => { test_initPlugins002(); }); - test('test getSortedPlugins', () => { + test('getSortedPlugins', () => { test_getSortedPlugins(); }); }); -function test_initPlugins001() { - const driver = new PluginDriver(); - const result = driver.initPlugins(undefined as any); - expect(result).toBeUndefined(); +function test_getSortedPlugins() { + const driver = PluginDriver.getInstance(); + let mockPreData: any = { + name: PluginHook.PARSED, + parsed: { + order: 'pre', + handler: jest.fn() + } + }; + let mockPostData: any = { + name: PluginHook.CHECKED, + checked: { + order: 'post', + handler: jest.fn() + } + }; + let mockOtherData: any = { + name: PluginHook.CHECKED, + checked: { + order: undefined, + handler: jest.fn() + } + }; + let mockallPlugins = new Map() + mockallPlugins.set(PluginHook.PARSED, mockPreData); + mockallPlugins.set(PluginHook.CHECKED, mockPostData); + mockallPlugins.set(PluginHook.NEW, mockOtherData); + Reflect.set(driver, 'allPlugins', mockallPlugins); + expect(() => { + Reflect.get(driver, 'getSortedPlugins').call(driver, PluginHook.PARSED); + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); + expect(() => { + Reflect.get(driver, 'getSortedPlugins').call(driver, PluginHook.CHECKED); + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); } 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 + const driver = PluginDriver.getInstance(); + mockConfig.plugins = undefined as any; + driver.initPlugins(mockConfig); + expect(() => { + driver.initPlugins(mockConfig); + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); + expect(() => { + driver.initPlugins(undefined as any); + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); +} + +function test_getPlugins() { + const driver = PluginDriver.getInstance(); + const handler = { + get: function (target: any, prop: any) { + if (prop === 'getSortedPlugins') { + return target[prop]; + } + } }; + const proxyInstance = new Proxy(driver, handler); + let mockData: any = [{ + name: 'mockPlugin', + handler: jest.fn() + }]; + const spy = jest.spyOn(proxyInstance, 'getSortedPlugins'); + spy.mockReturnValue(mockData); + expect(() => { + driver.runPluginHook(PluginHook.PARSED) + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); +} + +function test_runPluginHook001() { + const driver = PluginDriver.getInstance(); + expect(() => { + driver.runPluginHook(PluginHook.PARSED) + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); +} - let error; - try { - driver.initPlugins(mockProjectConfig as any); - } catch (e) { - error = e; - } +function test_runPluginHook002() { + const driver = PluginDriver.getInstance(); + const handler = { + get: function (target: any, prop: any) { + if (prop === 'getPlugins') { + return target[prop]; + } + } + }; + const proxyInstance = new Proxy(driver, handler); + let mockData: any = [{ + name: 'mockPlugin', + handler: jest.fn() + }]; + const spy = jest.spyOn(proxyInstance, 'getPlugins'); + spy.mockReturnValue(mockData); + expect(() => { + driver.runPluginHook(PluginHook.PARSED) + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); +} - expect(error).not.toBeUndefined(); +function test_setContextPtrAndGetContextPtr() { + const driver = PluginDriver.getInstance(); + const mockPtr = 124; + driver.getPluginContext().setContextPtr(mockPtr); + expect(driver.getPluginContext().getContextPtr()).toBe(mockPtr); } -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) }, - ]); +function test_setFileManagerAndGetFileManager() { + const driver = PluginDriver.getInstance(); + driver.getPluginContext().setFileManager(mockConfig); + expect(driver.getPluginContext().getFileManager()).not.toBe(undefined); +} + +function test_setProjectConfigAndGetProjectConfig() { + const driver = PluginDriver.getInstance(); + const mockProgram = { type: 'mock', body: [] }; + driver.getPluginContext().setProjectConfig(mockProgram); + expect(driver.getPluginContext().getProjectConfig()).toBe(mockProgram); +} + +function test_setArkTSProgramAndGetArkTSProgram() { + const driver = PluginDriver.getInstance(); + const mockProgram = { type: 'mock', body: [] }; + driver.getPluginContext().setArkTSProgram(mockProgram); + expect(driver.getPluginContext().getArkTSProgram()).toBe(mockProgram); +} + +function test_setArkTSAstAndGetArkTSAst() { + const driver = PluginDriver.getInstance(); + const mockAst = { type: 'mock', body: [] }; + driver.getPluginContext().setArkTSAst(mockAst); + expect(driver.getPluginContext().getArkTSAst()).toBe(mockAst); +} + +function test_getPluginContext() { + const driver = PluginDriver.getInstance(); + let context = driver.getPluginContext(); + expect(context).not.toBe(undefined); +} + +function test_construcotr() { + const plugindriver = new PluginDriver(); + expect(plugindriver).not.toBe(undefined); +} + +function test_getInstance() { + const driver = PluginDriver.getInstance(); + expect(driver).toBe(PluginDriver.getInstance()); +} + +function test_destroyInstance() { + const driver = PluginDriver.getInstance(); + PluginDriver.destroyInstance(); + expect(driver).not.toBe(PluginDriver.getInstance()); +} + +function test_initPlugins001() { + const driver = PluginDriver.getInstance(); + expect(() => { + driver.initPlugins(mockConfig); + }).not.toThrow('runPluginHook should not throw an error when no plugins are registered for the hook'); } diff --git a/ets2panda/driver/build_system/test/ut/process_build_configTest/process_build_config.test.ts b/ets2panda/driver/build_system/test/ut/process_build_configTest/process_build_config.test.ts new file mode 100755 index 0000000000000000000000000000000000000000..4f1848d04b758a603c9da800e1b2ed2b5e729520 --- /dev/null +++ b/ets2panda/driver/build_system/test/ut/process_build_configTest/process_build_config.test.ts @@ -0,0 +1,196 @@ +/* + * 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 && + * limitations under the License. + */ + +jest.mock('fs'); +jest.mock('path'); +jest.mock('../../../src/logger', () => { + const mLogger = { + printError: jest.fn(), + printInfo: jest.fn(), + getInstance: jest.fn((): any => mLogger) + } as any; + return { + Logger: mLogger, + LogDataFactory: { newInstance: jest.fn(() => ({ + code: '001', description: '', cause: '', position: '', solutions: [], moreInfo: {} })) } + }; +}); +jest.mock('../../../src/plugins/plugins_driver', () => { + const mPluginDriver = { + initPlugins: jest.fn(), + getInstance: jest.fn((): any => mPluginDriver) + } as any; + return { PluginDriver: mPluginDriver }; +}); +jest.mock('../../../src/pre_define', () => ({ + KOALA_WRAPPER_PATH_FROM_SDK: 'koala', + PANDA_SDK_PATH_FROM_SDK: 'panda', + PROJECT_BUILD_CONFIG_FILE: 'projectionConfig.json' +})); +jest.mock('../../../src/utils', () => ({ + isLinux: jest.fn(() => false), + isMac: jest.fn(() => false), + isWindows: jest.fn(() => false) +})); + +const fakeArkts = {}; +const fakeArktsGlobal = { + es2panda: { + _SetUpSoPath: jest.fn() + } +}; + +beforeEach(() => { + jest.resetModules(); + jest.clearAllMocks(); + require('path').resolve.mockImplementation((...args: string[]) => args.join('/')); + require('path').join.mockImplementation((...args: string[]) => args.join('/')); + require('path').basename.mockImplementation((p: string) => p.split('/').pop()); + require('fs').existsSync.mockReturnValue(true); + require('fs').mkdirSync.mockImplementation(() => { }); + require('fs').writeFileSync.mockImplementation(() => { }); + require('fs').readFileSync.mockImplementation(() => JSON.stringify({})); + process.env = { ...process.env }; +}); + +//Don't change or merge following tests even if they seem duplicate. +//Every test is for different scenarios and should be kept separate for clarity, avoiding mock issues. +describe('test processBuildConfig in different scenarios', () => { + const buildConfigBase = { + buildSdkPath: '/sdk', + pandaSdkPath: undefined, + cachePath: '/cache', + abcLinkerPath: '/abc/linker', + dependencyAnalyzerPath: '/dep/analyzer', + frameworkMode: false, + packageName: 'pkg' + }; + + test('process build config && call all init functions', () => { + jest.doMock('/sdk/koala', () => ({ + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal + }), { virtual: true }); + const { processBuildConfig } = require('../../../src/init/process_build_config'); + const config = processBuildConfig({ ...buildConfigBase }); + const { BUILD_MODE } = require('../../../src/types'); + expect(config.pandaSdkPath).toBe('/sdk/panda'); + expect(config.buildMode).toBe(BUILD_MODE.RELEASE); + expect(require('../../../src/plugins/plugins_driver').PluginDriver.initPlugins).toHaveBeenCalled(); + expect(config.arkts).toBe(fakeArkts); + expect(config.arktsGlobal).toBe(fakeArktsGlobal); + expect(fakeArktsGlobal.es2panda._SetUpSoPath).toHaveBeenCalledWith('/sdk/panda'); + }); + + test('create cache dir && config file if not exist', () => { + require('fs').existsSync.mockImplementation((p: string) => { + if (p === '/cache') return false; + if (p === '/cache/projectionConfig.json') return false; + return true; + }); + const { processBuildConfig } = require('../../../src/init/process_build_config'); + processBuildConfig({ ...buildConfigBase }); + expect(require('fs').mkdirSync).toHaveBeenCalledWith('/cache', { recursive: true }); + expect(require('fs').writeFileSync).toHaveBeenCalledWith('/cache/projectionConfig.json', expect.any(String)); + }); + + test('overwrite config if config file exists but not equal', () => { + const { processBuildConfig } = require('../../../src/init/process_build_config'); + require('fs').existsSync.mockImplementation((p: string) => true); + require('fs').readFileSync.mockImplementation(() => JSON.stringify({ foo: 1 })); + processBuildConfig({ ...buildConfigBase, foo: 2 }); + expect(require('fs').writeFileSync).toHaveBeenCalledWith('/cache/projectionConfig.json', expect.any(String)); + }); + + test('not overwrite config if config file exists && is equal', () => { + require('fs').existsSync.mockImplementation((p: string) => true); + const expectedConfig = { + ...buildConfigBase, + pandaSdkPath: '/sdk/panda', + isBuildConfigModified: true, + buildMode: 'Release' + }; + const { processBuildConfig } = require('../../../src/init/process_build_config'); + require('fs').readFileSync.mockImplementation(() => JSON.stringify(expectedConfig)); + jest.clearAllMocks(); + processBuildConfig({ ...buildConfigBase }); + expect(require('fs').writeFileSync).not.toHaveBeenCalled(); + expect(require('../../../src/logger').Logger.printInfo).toHaveBeenCalledWith( + 'projectionConfig.json is up to date.'); + }); + + test('print error if abcLinkerPath not exist', () => { + require('fs').existsSync.mockImplementation((p: string) => { + if (p === '/abc/linker') return false; + return true; + }); + const { processBuildConfig } = require('../../../src/init/process_build_config'); + processBuildConfig({ ...buildConfigBase }); + expect(require('../../../src/logger').Logger.printError).toHaveBeenCalled(); + }); + + test('print error if dependencyAnalyzerPath not exist && not frameworkMode', () => { + require('fs').existsSync.mockImplementation((p: string) => { + if (p === '/dep/analyzer') return false; + return true; + }); + const { processBuildConfig } = require('../../../src/init/process_build_config'); + processBuildConfig({ ...buildConfigBase }); + expect(require('../../../src/logger').Logger.printError).toHaveBeenCalled(); + }); + + test('not print error if dependencyAnalyzerPath not exist but frameworkMode is true', () => { + require('fs').existsSync.mockImplementation((p: string) => { + if (p === '/dep/analyzer') return false; + return true; + }); + const { processBuildConfig } = require('../../../src/init/process_build_config'); + processBuildConfig({ ...buildConfigBase, frameworkMode: true }); + expect(require('../../../src/logger').Logger.printError).not.toHaveBeenCalled(); + }); + + test('set DYLD_LIBRARY_PATH on Mac', () => { + jest.resetModules(); + require('../../../src/utils').isMac.mockReturnValue(true); + const { processBuildConfig, initBuildEnv } = require('../../../src/init/process_build_config'); + const config = { ...buildConfigBase, pandaSdkPath: '/sdk/panda' }; + process.env.PATH = '/usr/bin'; + require('path').resolve.mockImplementation((...args: string[]) => args.filter(Boolean).join('/')); + initBuildEnv(config); + expect(process.env.DYLD_LIBRARY_PATH).toContain('/sdk/panda/lib'); + }); + + test('use KOALA_WRAPPER_PATH env if set', () => { + process.env.KOALA_WRAPPER_PATH = '/custom/koala'; + jest.doMock('/custom/koala', () => ({ + arkts: fakeArkts, + arktsGlobal: fakeArktsGlobal + }), { virtual: true }); + const { processBuildConfig } = require('../../../src/init/process_build_config'); + const config = processBuildConfig({ ...buildConfigBase }); + expect(config.arkts).toBe(fakeArkts); + expect(config.arktsGlobal).toBe(fakeArktsGlobal); + expect(fakeArktsGlobal.es2panda._SetUpSoPath).toHaveBeenCalled(); + delete process.env.KOALA_WRAPPER_PATH; + }); + + test('throw if koala wrapper require fails', () => { + process.env.KOALA_WRAPPER_PATH = '/bad/koala'; + jest.doMock('/bad/koala', () => { throw new Error('fail'); }, { virtual: true }); + const { processBuildConfig } = require('../../../src/init/process_build_config'); + expect(() => processBuildConfig({ ...buildConfigBase })).toThrow(); + delete process.env.KOALA_WRAPPER_PATH; + }); +}); diff --git a/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts b/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts index aac65316a8f7ab8fbf5c8d6c3d38d6553137910f..4733ec42d6f9b91b0dea55ba64a57f37f1e31684 100755 --- a/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts +++ b/ets2panda/driver/build_system/test/ut/utilsTest/utils.test.ts @@ -13,8 +13,7 @@ * limitations under the License. */ - -import { ensurePathExists, getFileHash } from '../../../src/utils'; +import { ensurePathExists } from '../../../src/utils'; import * as fs from 'fs'; import * as path from 'path'; import { @@ -23,33 +22,11 @@ import { import { isWindows, isLinux, isMac, changeFileExtension, changeDeclgenFileExtension, - toUnixPath, readFirstLineSync, safeRealpath + toUnixPath, 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', () => { +describe('Check if the path exists. If not, create it and ensure it exists', () => { const testDir = path.join(__dirname, 'testDir'); beforeEach(() => { @@ -64,109 +41,49 @@ describe('test ensurePathExists', () => { } }); - it('test ensurePathExists001', () => { - test_ensurePathExists001(testDir); - }); - - it('test ensurePathExists002', () => { - test_ensurePathExists002(testDir); + test('test ensurePathExists', () => { + test_ensurePathExists(testDir); }); }); describe('utils', () => { + // Determine which operating system it is. describe('isWindows/isLinux/isMac', () => { - it('should detect Linux', () => { + test('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', () => { + describe('test if change file extension successfully', () => { + test('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', () => { + test('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', () => { + describe('test if changeDeclgenFileExtension works correctly', () => { + test('should use DECL_ETS_SUFFIX branch', () => { const file = `foo${DECL_ETS_SUFFIX}`; expect(changeDeclgenFileExtension(file, '.ts')).toBe('foo.ts'); }); - it('should use default branch', () => { + test('should use default branch', () => { expect(changeDeclgenFileExtension('foo.ets', '.ts')).toBe('foo.ts'); }); }); - describe('toUnixPath', () => { - it('should replace backslashes with slashes', () => { + describe('test toUnixPath', () => { + test('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', () => { +describe('test if the safeRealpath can resolve the path correctly', () => { + test('test safeRealpath001', () => { const testDir = path.join(__dirname); const mockLogger = { printInfo: jest.fn(), printError: jest.fn() }; @@ -175,14 +92,12 @@ describe('safeRealpath', () => { expect(mockLogger.printError).not.toHaveBeenCalled(); }); - it('test safeRealpath002', () => { + test('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, @@ -192,34 +107,84 @@ describe('safeRealpath', () => { }); }); -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) { +function test_ensurePathExists(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); -} +describe('test if get interop files\' path by Api', () => { + const originalFs = require('fs'); + let mockFs: any; + beforeEach(() => { + mockFs = { + ...originalFs, + existsSync: jest.fn() + }; + jest.mock('fs', () => mockFs); + jest.resetModules(); + const utils = require('../../../src/utils'); + (global as any).getInteropFilePathByApi = utils.getInteropFilePathByApi; + }); + + afterEach(() => { + delete (global as any).getInteropFilePathByApi; + jest.unmock('fs'); + jest.resetModules(); + }); + + test('should find file in first path', () => { + mockFs.existsSync.mockImplementation((filePath: string) => { + return filePath === path.resolve('/sdk/path1', '@ohos.test.d.ets'); + }); + const result = (global as any).getInteropFilePathByApi('@ohos.test', new Set(['/sdk/path1', '/sdk/path2'])); + expect(result).toBe(path.resolve('/sdk/path1', '@ohos.test.d.ets')); + expect(mockFs.existsSync).toHaveBeenCalledTimes(1); + }); +}); + +describe('test if get OhmurlByApi works correctly', () => { + beforeEach(() => { + jest.resetModules(); + const utils = require('../../../src/utils'); + (global as any).getOhmurlByApi = utils.getOhmurlByApi; + (global as any).NATIVE_MODULE = require('../../../src/pre_define').NATIVE_MODULE; + (global as any).ARKTS_MODULE_NAME = require('../../../src/pre_define').ARKTS_MODULE_NAME; + (global as any).sdkConfigPrefix = require('../../../src/pre_define').sdkConfigPrefix; + }); + + afterEach(() => { + delete (global as any).getOhmurlByApi; + delete (global as any).NATIVE_MODULE; + delete (global as any).ARKTS_MODULE_NAME; + delete (global as any).sdkConfigPrefix; + jest.resetModules(); + }); + + test('should handle native module correctly', () => { + (global as any).NATIVE_MODULE = new Set(['ohos.test']); + const result = (global as any).getOhmurlByApi('@ohos.test'); + expect(result).toBe('@ohos:test'); + }); + + test('should handle arkts module correctly', () => { + (global as any).ARKTS_MODULE_NAME = 'arkts'; + const result = (global as any).getOhmurlByApi('@arkts.test'); + expect(result).toBe('@ohos:arkts.test'); + }); + + test('should handle regular ohos module correctly', () => { + const result = (global as any).getOhmurlByApi('@ohos.regular'); + expect(result).toBe('@ohos:regular'); + }); + + test('should return empty string for non-matching API format', () => { + const result = (global as any).getOhmurlByApi('invalid-format'); + expect(result).toBe(''); + }); + + test('should handle API with whitespace correctly', () => { + const result = (global as any).getOhmurlByApi('@ohos.test'); + expect(result).toBe('@ohos:test'); + }); +}); diff --git a/ets2panda/driver/build_system/testSuite_ch.md b/ets2panda/driver/build_system/testSuite_ch.md new file mode 100644 index 0000000000000000000000000000000000000000..c8820b5f727bd6c36bade92b6cb8731f72005072 --- /dev/null +++ b/ets2panda/driver/build_system/testSuite_ch.md @@ -0,0 +1,528 @@ +# ArkTS1.2 build system test suite + +本测试套件基于 [Jest](https://jestjs.io/) 编写,用于测试 build_system 的功能。 +自动化测试套件可通过命令行调用,展示测试结果(包括通过/失败的测试数量、编译时间、字节码大小、峰值内存,增量编译识别),并显示失败测试的详细信息。 + +--- + +## Jest 引入 + +### 1. 修改 build_system/package.json + +**添加测试脚本** +在 `package.json` 的 `scripts` 项内添加: + +```json +"scripts": { + "build_system_Utest": "jest", // 单元测试,默认运行所有 test/ut 下的测试,可通过命令行传递测试文件位置运行指定测试,可修改 jest 配置灵活调整测试文件集 + "build_system_Etest": "TEST=test/e2e/demo_hap jest test/e2e/compile.test.ts", // 端到端测试,针对编译项目过程进行测试,修改 TEST 参数指定被编译项目路径 +} +``` + +**添加测试依赖库** +在 `package.json` 的 `devDependencies` 项内添加: + +```json +"devDependencies": { + "@babel/core": "^7.27.1", // 用于翻译TS代码,UT测试时使用babel可以避免tsc编译全部代码 + "@babel/preset-env": "^7.27.2", + "@babel/preset-typescript": "^7.27.1", // 二者一起使babel能够识别和转译TS代码 + "@types/jest": "^29.5.14", // Jest的类型定义文件,使typescript能识别jest的测试接口 + "babel-jest": "^29.7.0", // 用于转译ts代码,不需编译测试ts文件 + "jest": "^29.7.0", // 测试框架 + "ts-node": "^10.9.2" // 用于在Node.js中运行TypeScript代码 +} +``` + +**babel的配置参数** +babel使得运行测试前不需要tsc编译测试文件。 +在 `package.json` 中添加: + +```json +"babel": { + "presets": [ + ["@babel/preset-env", { "targets": { "node": "current" } }], + "@babel/preset-typescript" + ] +} +``` + +### 2. jest 的配置参数 + +在 `build_system/jest.config.js` 中添加: + +```js +module.exports = { + testEnvironment: "node", + verbose: true, + collectCoverage: true, + coverageDirectory: "/dist/coverage", + setupFilesAfterEnv: [ + "/testHook/jest.memory-usage.js", + "/testHook/jest.time-usage.js", + "/testHook/jest.abc-size.js" + ], + testMatch: [ + "/test/ut/**/*.test.[jt]s" + ], + testPathIgnorePatterns: [] +}; +``` + +--- + +## 运行测试套 + +测试套件分为单元测试(UT)和端到端测试(E2E)。 + +### UT 测试示例 + +在 `build_system/test/ut` 下添加 `osType.test.ts` 文件: + +```typescript +import { isWindows, isLinux, isMac } from '../../src/utils'; + +describe('osType', () => { + it('should detect OS type correctly', () => { + expect(isWindows()).toBe(false); + expect(isLinux()).toBe(true); + expect(isMac()).toBe(false); + }); +}); +``` + +**[可选]**修改 build_system_Utest的testMatch参数为测试文件: + +```json +"scripts": { + "build_system_Utest": "jest --testMatch='**/test/ut/**/osType.test.ts' --testPathIgnorePatterns='test/e2e/'", +} +``` + +命令行运行: + +```bash +npm run build_system_Utest +``` + +#### 推荐的写法 + +每一个测试文件在ut下新建一个同名文件夹,例如 `osType.test.ts` 在 `ut/osType` 下新建一个 `mockConsoleLog.test.ts` 文件,采用小驼峰方式命名。 + +#### 目前存在的UT +针对src下的文件有对应的同名Test测试,放在与src同名的文件夹内。 +mock夹是一部分测试需要用到的mock文件。 +默认配置下会运行这里全部测试文件,可根据实际需要调整jest配置进行筛选。 + +``` +test/ut +├── base_modeTest +│ └── base_mode.test.ts +├── build_framework_modeTest +│ └── build_framework_mode.test.ts +├── compile_WorkerTest +│ └── compile_worker.test.ts +├── compile_thread_workerTest +│ └── compile_thread_worker.test.ts +├── declgen_workerTest +│ └── declgen_worker.test.ts +├── entryTest +│ └── entry.test.ts +├── fileManagerTest +│ └── filemanager.test.ts +├── generate_arktsconfigTest +│ └── generate_arktsconfig.test.ts +├── loggerTest +│ └── logger.test.ts +├── mock +│ ├── a.ets +│ └── mockData.ts +├── plugins_driverTest +│ └── plugins_driver.test.ts +├── process_build_configTest +│ └── process_build_config.test.ts +├── safeRealpath.test.ts +└── utilsTest + └── utils.test.ts +``` + +--- + +### E2E 测试前置步骤 + +E2E 测试前的配置步骤如下: + + - 进入 build_system 目录 + + ```bash + cd + ``` + + - 执行mock_sdk操作,保证本地可以编译 build_system 本身和利用 build_system 编译 hap 包: + * SDK可以从[每日构建](https://ci.openharmony.cn/workbench/cicd/dailybuild/dailylist)下载,或者自行编译。 + * 下载`ohos-sdk-pulib_0328`,解压获得`ohos-sdk`目录,进入`ohos-sdk/Linux`解压`ets-linux-x64-6.0.0.36-Canary1.zip`获得`ets`目录。 + * `ets/ets1.2`即为SDK目录,将其中的内容复制进 `build_system/test/mock_sdk`即可。 + * 复制完成后mock_sdk目录下应存在`api`,`arkts`,`build-tools`,`kits`四个文件夹。 + + - 为mock_sdk/build-tools/ets2panda/bin下的几个可执行文件提供执行权限。 + + ```bash + chmod +x test/mock_sdk/build-tools/ets2panda/bin/* + ``` + + - 将所有端到端测试代码中的 `${absolute_path_to_build_system}` 替换为实际的目录。 + + ```bash + find test -name 'build_config*.json' -exec sed -i 's|${absolute_path_to_build_system}|'"$(pwd)"'|g' {} + + find test -name 'decl-fileInfo.json' -exec sed -i 's|${absolute_path_to_build_system}|'"$(pwd)"'|g' {} + + ``` + + - 导出LD_LIBRARY_PATH环境变量,如果不想每次都手动导出可以写进环境变量文件,注意替换成实际的目录。 + + ```bash + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/test/mock_sdk/build-tools/ets2panda/lib + ``` + + - 下载依赖和编译src下所有代码。 + + ```bash + npm install + npm run build + ``` + +1. **[可选]**通过修改 jest.config.js 中的 jest 参数,自定义调整 test suite 的运行方式,例如包含、排除测试文件: + + ```js + module.exports = { + testEnvironment: "node", + verbose: true, + collectCoverage: true, // 收集代码覆盖率,打开此选项会大幅影响测试速度 + coverageDirectory: "/coverageReport", // 调整代码覆盖率报告产物的位置 + setupFilesAfterEnv: [ + "/testHook/jest.memory-usage.js" + // "/testHook/jest.time-usage.js" + ], // 只加载内存监控的hook文件,时间监控的hook文件注释掉 + testMatch: [ + "test/ut/sum.test.ts" + ], // 只运行一个测试 + testPathIgnorePatterns: [ + "/test/ut/skip/", // 排除 skip 目录 + "/test/ut/sometest.test.ts" // 排除指定文件 + ] + } + ``` + +--- + +### E2E 测试示例 + +端到端测试的目的通常是检查一个项目代码是否能通过编译,正常生成abc文件,并且检查编译过程中是否有错误或异常。端到端测试与单元测试的运行方式类似,只是提供了不同的接口文件。 + +在 `build_system/test/e2e` 下添加 `compile.test.ts` 文件,该代码已在项目文件中包含: + +```typescript +// 文件过大,仅展示部分 + +import { execFile } from 'child_process'; +import { promisify } from 'util'; +import fs from 'fs'; +import path from 'path'; + +const execFileAsync = promisify(execFile); + +function getAllFilesWithExt(dir: string, exts: string[]): string[] { + if (!fs.existsSync(dir)) return []; + let result: string[] = []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + result = result.concat(getAllFilesWithExt(fullPath, exts)); + } else if (exts.some(ext => entry.name.endsWith(ext))) { + result.push(fullPath); + } + } + return result; +} +// 下略 + +``` + +考虑到待测试项目可能非常复杂,可能存在多个build_config.json文件,且无法得知具体的编译先后顺序,于是强制用户编写运行脚本指定编译流程。 +运行方式如下: + +#### 单个测试运行 + + 修改 build_system_Etest 脚本的 TEST 参数为测试项目脚本: + +```json +"scripts": { + "entry1_2_external_har1_2:gen_abc": "npm run build && node ./dist/entry.js ${absolute_path_to_build_system}/test/e2e/entry1_2_external_har1_2/build_config.json", + "build_system_Etest": "TEST=entry1_2_external_har1_2:gen_abc jest --testMatch '${absolute_path_to_build_system}/test/e2e/*.test.ts'" +} +``` + +在TEST=后添加脚本的完整名字。 + +命令行运行: + +```bash +npm run build_system_Etest +``` + +会执行测试。 + +添加测试的方式: +在 `build_system/test/e2e` 下新建一个测试文件,build_system_Etest会自动包含测试,或是通过调整jest配置筛选待执行测试文件,(实际上与端到端测试类似)。 + +#### 多个测试运行 +在`test/e2e`下有`run_all.sh`脚本,该脚本会循环运行全部测试文件,通过增删scripts来控制运行的测试。 + +```bash +scripts=( +demo_entry1.1_har1.1_hsp1.2:gen_abc +demo_entry1.1_har1.2_hsp1.1:gen_abc +demo_entry1.1_har1.2_hsp1.2:gen_abc +demo_entry1.1_hsp1.2:gen_abc +demo_entry1.2_har1.1_hsp1.1:gen_abc +demo_entry1.2_har1.1_hsp1.2:gen_abc +demo_entry1.2_har1.2_hsp1.1:gen_abc +demo_entry1.2_har1.2_hsp1.2:gen_abc +demo_entry1.2_hsp1.1:gen_abc +demo_entry1.2_hsp1.2:gen_abc +demo_entry1_2:gen_abc +demo_har1_2:gen_decl +demo_hsp1_2:gen_decl +entry1_1_external_har1_2:gen_abc +entry1_1_external_har1_2:gen_decl +entry1_1_external_hsp1_2:gen_abc +entry1_1_external_hsp1_2:gen_decl +entry1_2_external_har1_1:gen_abc +entry1_2_external_har1_2:gen_abc +entry1_2_external_hsp1_1:gen_abc +entry1_2_external_hsp1_2:gen_abc +entry1_2_external_hsp1_2:gen_decl +) + +passed=() +failed=() + +for script in "${scripts[@]}"; do + echo "Running E2E test: $script" + TEST=$script npx jest --testMatch='**/test/e2e/*.test.ts' --testPathIgnorePatterns='test/ut/' + if [ $? -eq 0 ]; then + passed+=("$script") + else + failed+=("$script") + fi +done + +echo +echo "================== E2E Test Summary ==================" +echo "Total: ${#scripts[@]}" +echo "Passed: ${#passed[@]}" +echo "Failed: ${#failed[@]}" +if [ ${#passed[@]} -gt 0 ]; then + echo "Passed tests:" + for s in "${passed[@]}"; do + echo " $s" + done +fi +if [ ${#failed[@]} -gt 0 ]; then + echo "Failed tests:" + for s in "${failed[@]}"; do + echo " $s" + done +fi +echo "======================================================" +``` + +确保脚本有执行权限 +```bash +chmod +x test/e2e/run_all.sh +``` +运行脚本: + +```bash +./test/e2e/run_all.sh +``` +会运行scripts数组中的所有测试脚本,并输出测试结果。 +考虑到输出会较为复杂,建议将输出重定向到文件中,或是对输出结果进行筛选。 + +--- + +## 如何编写测试 + +测试套基于Jest完成,利用Jest提供的断言和匹配器机制完成测试。 + +### 全局配置 + +- 按组组织测试:`describe(name, fn)`,多个测试包含在 fn 内,组成一个组。 +- 单个测试: `it(name, fn)`,fn 内是测试的具体实现,所有 it 均可以替换为别名 test。 +- 反向测试:`it.failing(name, fn, timeout)`,与 it 相反,fn 成功则失败,fn 失败则成功。 +- todo:`it.todo(name)`,表示该测试未编写。 +- 全部测试完成前/后执行操作:`afterAll(fn, timeout)`,`beforeAll(fn, timeout)`,fn 放具体实现,可选 timeout 设置超时时间。 +- 逐个测试进行前/后执行操作:`afterEach(fn, timeout)`,`beforeEach(fn, timeout)`,fn 放具体实现,可选 timeout 设置超时时间。 + +### 断言 + +expect(expr)创建断言对象,例如expect(isLinux()) + +### 匹配器 + +匹配器是断言对象提供的方法,因此链式调用即可。Jest提供了大量匹配器,这里给出常用的几个: + +- toBe(value): 检查值是否相等 +- toHaveBeenCalled():检查是否被调用,常用于mock函数 +- toHaveReturned():检查函数是否正常返回,常用于mock函数 +- toHaveLength(number):检查数组长度 +- toBeInstanceOf(Class):判断对象是否为类型示例,与instanceof类似 +- toContain(item):检查包含性 +- 等等,更多匹配器可以参考[官方文档](https://jestjs.io/docs/expect) + +### mock 函数 + +在 `build_system/test/ut/osType` 下添加 `osType.test.ts` 文件: + +```typescript +import * as utils from '../../src/utils'; + +describe('osTypeCheck', () => { + it('should detect OS type correctly', () => { + expect(utils.isWindows()).toBe(false); + expect(utils.isLinux()).toBe(true); + expect(utils.isMac()).toBe(false); + }); + it('mocked isWindows always return true', () => { + const spy = jest.spyOn(utils, 'isWindows').mockImplementation(() => true); + // mock整个模块,然后spyOn方法 + expect(utils.isWindows()).toBe(true); + expect(utils.isLinux()).toBe(true); + expect(utils.isMac()).toBe(false); + spy.mockRestore(); + }); +}); +``` + +考虑到ES6的导入是只读的,无法修改,因此只能用import * as utils 的方式引入模块。 +需要mock模块还是直接mock函数需要具体情况具体分析。 +mock 函数提供大量接口用于断言和检查,参考 [官方文档](https://jestjs.io/docs/mock-function-api) + +### 异步代码 + +返回 Promise 的函数,可以直接测试,若返回 Promise.resolve(),则测试通过,若返回 Promise.reject(),则测试失败。 + +### 测试结果 + +测试结果默认打印在命令行。 +以上文的UT测试为例 + +``` +> ArkTS2.0_build_system@1.0.0 build_system_Utest +> jest --testMatch='**/test/ut/**/osType.test.ts' --testPathIgnorePatterns='test/e2e/' + + console.log + [Jest][osType should detect OS type correctly used 0.28 MB] + + at Object. (test/testHook/jest.memory-usage.js:27:11) + + console.log + [Jest][osType should detect OS type correctly spent 6 ms] + + at Object. (test/testHook/jest.time-usage.js:26:11) + + console.warn + [Jest][No .abc files found in /root/0328/ets_frontend/ets2panda/driver/build_system/dist/cache] + + 42 | const abcFiles = getAllAbcFiles(cacheDir); + 43 | if (abcFiles.length === 0) { + > 44 | console.warn(`[Jest][No .abc files found in ${cacheDir}]`); + | ^ + 45 | return; + 46 | } + 47 | abcFiles.forEach(file => { + + at Object. (test/testHook/jest.abc-size.js:44:13) + + PASS test/ut/osType.test.ts + osType + ✓ should detect OS type correctly (8 ms) + +---------------|---------|----------|---------|---------|--------------------------------------------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +---------------|---------|----------|---------|---------|--------------------------------------------------------- +All files | 41.05 | 13.95 | 14.63 | 41.26 | + error_code.ts | 100 | 100 | 100 | 100 | + logger.ts | 6.75 | 0 | 0 | 6.84 | 25-103,125-126,147-179,189-218 + pre_define.ts | 100 | 100 | 100 | 100 | + utils.ts | 36.76 | 0 | 23.07 | 36.76 | 51-53,57-60,64-71,77-78,82,87-95,99-109,114-120,128-143 +---------------|---------|----------|---------|---------|--------------------------------------------------------- +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +Snapshots: 0 total +Time: 0.903 s +Ran all test suites. +``` + +开头的三个console.log/warn是测试hook的输出,分别是内存使用情况、测试时间和abc文件大小。 +格式为`[Jest][${test_name} used ${memory} MB]`, +`[Jest][${test_name} spent ${time} ms]`和 +`[Jest][No .abc files found in ${cacheDir}]`。 +这里第三个输出是因为测试文件夹下没有abc文件,通常是因为测试没有进行编译工作。 +表格为覆盖率的简略版本,分别显示每个文件的语句覆盖率、分支覆盖率、函数覆盖率和行覆盖率。 +Test Suites的数量为执行的测试文件的数量。 +Tests的数量为测试文件中test/it块的数量。 +考虑到编译过程中也会有命令行输出,建议调整输出流的位置,例如将编译信息输出到临时文件中。 + +--- + +## 新增文件夹树 + +``` +build_system +├── dist # 编译产物目录 +│ └── coverage # 覆盖率产物目录 +├── src # 源代码目录 +├── test # 测试目录 +│ ├── ut # 单元测试 +│ │ ├── mockOsType # mock 函数测试 +│ │ │ └── mockOsType.test.ts # mock 操作系统类型测试 +│ │ ├── mockConsoleLog.test.ts # mock console.log 测试 +│ │ └── ... # 更多单元测试 +│ └── e2e # 端到端测试 +│ ├── compile.test.ts # 编译测试 +│ ├── checkHash.test.ts # 哈希检查测试 +│ ├── abcGenerationTest # 示例 hap 项目 +│ ├── ... # 更多端到端测试 +│ └── testHook # 测试 hook 文件 +│ ├── jest.memory-usage.js # 内存使用监控 +│ ├── jest.time-usage.js # 测试时间监控 +│ └── jest.abc-size.js # abc 文件大小监控 +├── package.json # 项目配置文件 +└── jest.config.js # Jest 配置文件 +``` + +--- + +## 覆盖率报告 + +简略的覆盖率信息会打印到命令行。 +详细的覆盖率报告默认输出到 `dist/coverage` 目录。可以通过修改 `jest` 配置中的 `coverageDirectory` 字段来调整输出位置。 +Jest使用istanbul生成覆盖率报告。 +覆盖率报告包括以下内容: + +``` +dist/coverage +├── clover.xml # Clover格式的覆盖率报告 +├── coverage-final.json # Json格式的覆盖率报告 +├── lcov-report # 详细的 HTML 格式覆盖率报告目录 +│ ├── base.css +│ ├── block-navigation.js +│ ├── favicon.png +│ ├── index.html # 覆盖率报告,浏览器打开可查看详细覆盖率 +│ ├── prettify.css +│ ├── prettify.js +│ ├── sort-arrow-sprite.png +│ └── sorter.js +└── lcov.info # 标准 LCOV 格式的覆盖率数据文件 +``` diff --git a/ets2panda/driver/build_system/testSuite.md b/ets2panda/driver/build_system/testSuite_en.md old mode 100755 new mode 100644 similarity index 44% rename from ets2panda/driver/build_system/testSuite.md rename to ets2panda/driver/build_system/testSuite_en.md index 61d136295fb8448005c3438066e2434b218fceec..296316549a282ce85e65a7e2bb44568cbf37a03a --- a/ets2panda/driver/build_system/testSuite.md +++ b/ets2panda/driver/build_system/testSuite_en.md @@ -9,24 +9,24 @@ The test suite bases on [Jest](https://jestjs.io/) and aims to test the `build_s ### 1. Edit the build_system/package.json file **Add test scripts** -Add the following to the `scripts` section in `package.json`(already in the code repository): +Add the following to the `scripts` section in `package.json` (already in the code repository): ```json "scripts": { - "build_system_Utest": "jest --testMatch='**/test/ut/**/utils.test.ts' --testPathIgnorePatterns='test/e2e/'", + "build_system_Utest": "jest --testMatch='**/test/ut/**/utils.test.ts' --testPathIgnorePatterns='test/e2e/'", "build_system_Etest": "TEST=${test_script_name} jest test/e2e/compile.test.ts", } ``` -Build_system_Utest is for unit tests while build_system_Etest is for E2E tests. +Build_system_Utest is for unit tests while build_system_Etest is for E2E tests. The `TEST` environment variable is used to specify the test script to run for E2E tests. **Add dependencies** -Add the following to the `devDependencies` section in `package.json`(already in the code repository): +Add the following to the `devDependencies` section in `package.json` (already in the code repository): ```json "devDependencies": { - "@babel/core": "^7.27.1", + "@babel/core": "^7.27.1", "@babel/preset-env": "^7.27.2", "@babel/preset-typescript": "^7.27.1", "@types/jest": "^29.5.14", @@ -38,8 +38,8 @@ Add the following to the `devDependencies` section in `package.json`(already in **Babel configuration** -Babel allows running tests without compiling test files with tsc first. -Add the following to `package.json`(already in the code repository): +Babel allows running tests without compiling test files with tsc first. +Add the following to `package.json` (already in the code repository): ```json "babel": { @@ -52,7 +52,7 @@ Add the following to `package.json`(already in the code repository): ### 2. Jest configuration -Add the following to `build_system/jest.config.js`(already in the code repository): +Add the following to `build_system/jest.config.js` (already in the code repository): ```js module.exports = { @@ -76,70 +76,9 @@ module.exports = { ## Running the Test Suite -Before running the test suite, ensure that the build_system itself can be compiled locally. -End-to-end tests depend on the SDK. It is recommended to copy the SDK directly into `build_system/test/mock_sdk` and replace the original three folders. -After replacing the SDK, grant execute permissions to all files under `build_system/test/mock_sdk/build-tools/ets2panda/bin` and export the `LD_LIBRARY_PATH` environment variable. -Replace the SDK → grant execute permissions to bin/* → update paths → install dependencies via npm → run npm scripts → compile src successfully. +This test suite is divided into two parts: unit tests and end-to-end (E2E) tests. -1. Enter the `build_system` directory - - ```bash - cd ${absolute_path_to_build_system} - ``` - -2. Perform mock_sdk operations to ensure that the build_system itself and hap packages can be compiled locally: - - Copy the SDK for Linux (`ets/ets1.2/*`) to `build_system/test/mock_sdk`. - After copying, the mock_sdk directory should contain the folders `api`, `arkts`, `build-tools`, and `kits`. - - Grant execute permissions to the executables under `mock_sdk/build-tools/ets2panda/bin`: - ```bash - chmod +x test/mock_sdk/build-tools/ets2panda/bin/* - ``` - - Replace all `${absolute_path_to_build_system}` in e2e test code with the actual directory: - ```bash - find test -name 'build_config*.json' -exec sed -i 's|${absolute_path_to_build_system}|'"$(pwd)"'|g' {} + - find test -name 'decl-fileInfo.json' -exec sed -i 's|${absolute_path_to_build_system}|'"$(pwd)"'|g' {} + - ``` - - Export the `LD_LIBRARY_PATH` environment variable. To avoid exporting it every time, you can add it to your environment file. Be sure to replace with the actual directory: - ```bash - export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${absolute_path_to_build_system}/test/mock_sdk/build-tools/ets2panda/lib - ``` - - Install dependencies and compile all files under `src`: - - ```bash - npm install - npm run build - ``` - -3. **[optional]** Customize the test suite by modifying Jest parameters in `jest.config.js`, such as including or excluding test files: - - ```js - module.exports = { - testEnvironment: "node", - verbose: true, - collectCoverage: true, - coverageDirectory: "/coverageReport", - setupFilesAfterEnv: [ - "/testHook/jest.memory-usage.js" - // "/testHook/jest.time-usage.js" - ], - testMatch: [ - "test/ut/sum.test.ts" - ], - testPathIgnorePatterns: [ - "/test/ut/skip/", - "/test/ut/sometest.test.ts" - ] - } - ``` - ---- - -## Unit Test Example +### Unit Test Example Add an `osType.test.ts` file under `build_system/test/ut`: @@ -155,6 +94,14 @@ describe('osType', () => { }); ``` +**[Optional]** Modify the testMatch parameter of build_system_Utest to specify the test file: + +```json +"scripts": { + "build_system_Utest": "jest --testMatch='**/test/ut/**/osType.test.ts' --testPathIgnorePatterns='test/e2e/'", +} +``` + Run from the command line: ```bash @@ -163,17 +110,27 @@ npm run build_system_Utest #### Recommended Practice -For each test file, create a folder with the same name under `ut`. For example, for `osType.test.ts`, create a `ut/osType` folder. using camelCase naming convention. +For each test file, create a folder with the same name under `ut`. +For example, for `osType.test.ts`, create a `ut/osType` folder, using camelCase naming convention. #### Existing UT -There are corresponding test files for each file under `src`, placed in folders with the same name as in `src`. -The `mock` folder contains mock files needed for some tests. + +There are corresponding test files for each file under `src`, placed in folders with the same name as in `src`. +The `mock` folder contains mock files needed for some tests. By default, all test files in this directory will be run. You can filter them by adjusting the Jest configuration. ``` -ut +test/ut ├── base_modeTest │ └── base_mode.test.ts +├── build_framework_modeTest +│ └── build_framework_mode.test.ts +├── compile_WorkerTest +│ └── compile_worker.test.ts +├── compile_thread_workerTest +│ └── compile_thread_worker.test.ts +├── declgen_workerTest +│ └── declgen_worker.test.ts ├── entryTest │ └── entry.test.ts ├── fileManagerTest @@ -183,37 +140,12 @@ ut ├── loggerTest │ └── logger.test.ts ├── mock -│ └── demo_1.2_dep_hsp1.2 -│ ├── build_config.json -│ ├── build_config1.json -│ ├── build_config2.json -│ ├── build_config3.json -│ ├── build_config4.json -│ ├── build_config5.json -│ ├── build_config6.json -│ ├── build_config7.json -│ ├── build_config8.json -│ ├── build_config9.json -│ ├── declgen -│ │ └── default -│ │ ├── declgenBridgeCode -│ │ │ └── entry -│ │ │ ├── Calc.ts -│ │ │ └── index.ts -│ │ └── declgenV1 -│ │ └── entry -│ │ ├── Calc.d.ets -│ │ └── index.d.ets -│ ├── entry -│ │ └── a.ets -│ ├── harA -│ │ ├── index.ets -│ │ └── sub.ets -│ └── hspA -│ ├── Calc.ets -│ └── index.ets +│ ├── a.ets +│ └── mockData.ts ├── plugins_driverTest │ └── plugins_driver.test.ts +├── process_build_configTest +│ └── process_build_config.test.ts ├── safeRealpath.test.ts └── utilsTest └── utils.test.ts @@ -221,151 +153,213 @@ ut --- -## E2E Test Example +### Before E2E + +The configuration steps before E2E testing are as follows: + - Enter the `build_system` directory + + ```bash + cd ${absolute_path_to_build_system} + ``` + + - Perform mock_sdk operations to ensure that the build_system itself and hap packages can be compiled locally: + * You can get the SDK for Linux from the [DailyBuild](https://ci.openharmony.cn/workbench/cicd/dailybuild/dailylist). + [*Build the SDK from scratch is also an option.*] + * Download the `ohos-sdk-public_0328` package and extract it, get `ohos-sdk`. + * Extract `ohos-sdk/linux/ets-linux-x64-6.0.0.36-Canary1.zip`, get `ets` and move `ets/ets1.2` to the `build_system/test/mock_sdk` directory. + * After that, the mock_sdk directory should contain the folders `api`, `arkts`, `build-tools`, and `kits`. + + - Grant execute permissions to the executables under `mock_sdk/build-tools/ets2panda/bin`: + + ```bash + chmod +x test/mock_sdk/build-tools/ets2panda/bin/* + ``` -The purpose of end-to-end tests is usually to check whether a project can be compiled, whether the `abc file` is generated correctly, and whether there are errors or exceptions during compilation. + - Replace all `${absolute_path_to_build_system}` in e2e test code with the actual directory: + + ```bash + find test -name 'build_config*.json' -exec sed -i 's|${absolute_path_to_build_system}|'"$(pwd)"'|g' {} + + find test -name 'decl-fileInfo.json' -exec sed -i 's|${absolute_path_to_build_system}|'"$(pwd)"'|g' {} + + ``` + + - Export the `LD_LIBRARY_PATH` environment variable. + To avoid exporting it every time, you can add it to your environment file. + Be sure to replace with the actual directory: + + ```bash + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${absolute_path_to_build_system}/test/mock_sdk/build-tools/ets2panda/lib + ``` + + - Install dependencies and compile all files under `src`: + + ```bash + npm install + npm run build + ``` + - **[optional]** Customize the test suite by modifying Jest parameters in `jest.config.js`, such as including or excluding test files: + + ```js + module.exports = { + testEnvironment: "node", + verbose: true, + collectCoverage: true, + coverageDirectory: "/coverageReport", + setupFilesAfterEnv: [ + "/testHook/jest.memory-usage.js" + // "/testHook/jest.time-usage.js" + ], + testMatch: [ + "test/ut/sum.test.ts" + ], + testPathIgnorePatterns: [ + "/test/ut/skip/", + "/test/ut/sometest.test.ts" + ] + } + ``` + +--- + +### E2E Test Example + +The purpose of end-to-end tests is usually to check whether a project can be compiled, whether the `abc file` is generated correctly, and whether there are errors or exceptions during compilation. End-to-end tests are run similarly to unit tests, but with different interface files. Add a `compile.test.ts` file under `build_system/test/e2e` (The latest version is already in the code repository): ```typescript -import { execSync } from 'child_process'; +// this file is too long, so only the first part is shown here + +import { execFile } from 'child_process'; +import { promisify } from 'util'; import fs from 'fs'; import path from 'path'; -function getAllModules(config: any) { - return [ - { packageName: config.packageName, modulePath: config.moduleRootPath, sourceRoots: config.sourceRoots || ['./'] }, - ...(config.dependentModuleList || []) - ]; -} - -function getAllSrcFiles(modules: any[]) { - let allSrcFiles: string[] = []; - for (const mod of modules) { - const moduleAbsPath = path.resolve(__dirname, '../../', mod.modulePath || mod.moduleRootPath || ''); - for (const root of mod.sourceRoots || ['./']) { - const srcRoot = path.resolve(moduleAbsPath, root); - if (fs.existsSync(srcRoot) && fs.statSync(srcRoot).isDirectory()) { - const files = fs.readdirSync(srcRoot) - .filter(f => f.endsWith('.ets')) - .map(f => path.join(srcRoot, f)); - allSrcFiles = allSrcFiles.concat(files); - } +const execFileAsync = promisify(execFile); + +function getAllFilesWithExt(dir: string, exts: string[]): string[] { + if (!fs.existsSync(dir)) return []; + let result: string[] = []; + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + result = result.concat(getAllFilesWithExt(fullPath, exts)); + } else if (exts.some(ext => entry.name.endsWith(ext))) { + result.push(fullPath); } } - return allSrcFiles; + return result; } +// more code below... +``` -function getModuleNameForSrc(src: string, allModuleNames: string[], defaultName: string): string | undefined { - for (const mod of allModuleNames) { - if (src.includes(`/${mod}/`)) return mod; - } - return defaultName; +Since the project under test may be very complex, with multiple `build_config.json` files and unknown compilation order, users are required to write scripts to specify the compilation process. +Usage: + +#### Single test + +Modify the `build_system_Etest` script's TEST parameter to the test project script: + +```json +"scripts": { + "entry1_2_external_har1_2:gen_abc": "npm run build && node ./dist/entry.js ${absolute_path_to_build_system}/test/e2e/entry1_2_external_har1_2/build_config.json", + "build_system_Etest": "TEST=entry1_2_external_har1_2:gen_abc jest --testMatch '${absolute_path_to_build_system}/test/e2e/*.test.ts'" } +``` -function testHelper(testDir: string) { - const configPath = path.resolve(__dirname, '../..', testDir, 'build_config.json'); - if (!fs.existsSync(configPath)) { - throw new Error(`Missing ${configPath}`); - } +Specify the full script name after TEST=. + +Run from the command line: - const config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); - const cachePath = path.resolve(__dirname, '../../', config.cachePath); +```bash +npm run build_system_Etest +``` - const allModuleNames = [ - config.packageName, - ...(config.dependentModuleList?.map((dep: any) => dep.packageName) || []) - ]; +This will execute the test. - const expectedOutputs = config.compileFiles.map((src: string) => { - const moduleName = getModuleNameForSrc(src, allModuleNames, config.packageName); - const baseName = path.basename(src, path.extname(src)); - return path.join(cachePath, moduleName, `${baseName}.abc`); - }); +To add tests, create a new test file/folder under `build_system/test/e2e`. +The `build_system_Etest` script will automatically include the test. +If you want to run a specific test, you can modify the `testMatch` parameter in `jest.config.js` to include only that test file. - describe('Check Compilation Outputs', () => { - beforeAll(() => { - execSync(`node ./dist/entry.js ${configPath}`, { stdio: 'inherit' }); - }); - - it('Check outputs for compiled files', () => { - expectedOutputs.forEach(filePath => { - if (!fs.existsSync(filePath)) { - throw new Error(`Not found: ${filePath}`); - } - if (fs.statSync(filePath).size === 0) { - throw new Error(`${filePath} is empty`); - } - }); - }); - - it('Check outputs for non-compiled source files', () => { - const modules = getAllModules(config); - const allSrcFiles = getAllSrcFiles(modules); - const compiledSet = new Set( - config.compileFiles.map((cf: string) => path.resolve(__dirname, '../../', cf)) - ); - const notCompiled = allSrcFiles.filter(f => !compiledSet.has(f)); - notCompiled.forEach(srcFile => { - const moduleName = getModuleNameForSrc(srcFile, allModuleNames, config.packageName); - const baseName = path.basename(srcFile, path.extname(srcFile)); - const outputFile = path.join(cachePath, moduleName, `${baseName}.abc`); - if (fs.existsSync(outputFile)) { - throw new Error(`Non-compiled source file ${srcFile} generated output ${outputFile}`); - } - }); - }); - }); +#### Multiple tests - afterAll(() => { - execSync(`rimraf ${cachePath}`, { stdio: 'inherit' }); - }); -} +A `run_all.sh` script is provided in the `test/e2e` directory to run all tests. -const testDir = process.env.TEST; -if (!testDir) { - throw new Error('Test folder not found'); -} else if (testDir === 'all') { - const baseDir = path.resolve(__dirname, '../../test'); - const dirs = fs.readdirSync(baseDir) - .map(name => path.join('test', name)) - .filter(dir => fs.statSync(path.resolve(__dirname, '../..', dir)).isDirectory()) - .filter(dir => fs.existsSync(path.resolve(__dirname, '../..', dir, 'build_config.json'))); - if (dirs.length === 0) { - throw new Error('No tests found'); - } - for (const dir of dirs) { - testHelper(dir); - } -} else { - testHelper(testDir); -} +```bash +scripts=( +demo_entry1.1_har1.1_hsp1.2:gen_abc +demo_entry1.1_har1.2_hsp1.1:gen_abc +demo_entry1.1_har1.2_hsp1.2:gen_abc +demo_entry1.1_hsp1.2:gen_abc +demo_entry1.2_har1.1_hsp1.1:gen_abc +demo_entry1.2_har1.1_hsp1.2:gen_abc +demo_entry1.2_har1.2_hsp1.1:gen_abc +demo_entry1.2_har1.2_hsp1.2:gen_abc +demo_entry1.2_hsp1.1:gen_abc +demo_entry1.2_hsp1.2:gen_abc +demo_entry1_2:gen_abc +demo_har1_2:gen_decl +demo_hsp1_2:gen_decl +entry1_1_external_har1_2:gen_abc +entry1_1_external_har1_2:gen_decl +entry1_1_external_hsp1_2:gen_abc +entry1_1_external_hsp1_2:gen_decl +entry1_2_external_har1_1:gen_abc +entry1_2_external_har1_2:gen_abc +entry1_2_external_hsp1_1:gen_abc +entry1_2_external_hsp1_2:gen_abc +entry1_2_external_hsp1_2:gen_decl +) + +passed=() +failed=() + +for script in "${scripts[@]}"; do + echo "Running E2E test: $script" + TEST=$script npx jest --testMatch='**/test/e2e/*.test.ts' --testPathIgnorePatterns='test/ut/' + if [ $? -eq 0 ]; then + passed+=("$script") + else + failed+=("$script") + fi +done + +echo +echo "================== E2E Test Summary ==================" +echo "Total: ${#scripts[@]}" +echo "Passed: ${#passed[@]}" +echo "Failed: ${#failed[@]}" +if [ ${#passed[@]} -gt 0 ]; then + echo "Passed tests:" + for s in "${passed[@]}"; do + echo " $s" + done +fi +if [ ${#failed[@]} -gt 0 ]; then + echo "Failed tests:" + for s in "${failed[@]}"; do + echo " $s" + done +fi +echo "======================================================" ``` -Since the project under test may be very complex, with multiple `build_config.json` files and unknown compilation order, users are required to write scripts to specify the compilation process. -Usage: - Modify the `build_system_Etest` script's TEST parameter to the test project script: - - ```json - "scripts": { - "entry1_2_external_har1_2:gen_abc": "npm run build && node ./dist/entry.js ${absolute_path_to_build_system}/test/e2e/entry1_2_external_har1_2/build_config.json", - "build_system_Etest": "TEST=entry1_2_external_har1_2:gen_abc jest --testMatch '${absolute_path_to_build_system}/test/e2e/*.test.ts'" - } - ``` +Make the script executable: - Specify the full script name after TEST=. +```bash +chmod +x test/e2e/run_all.sh +``` - Run from the command line: +Run the script from the command line: - ```bash - npm run build_system_Etest - ``` +```bash +./test/e2e/run_all.sh +``` - This will execute the test. +It will run all the test scripts in the scripts array and output the test results. +Since the output may be quite complex, it is recommended to redirect the output to a file or filter the output results. -To add tests, create a new test file/folder under `build_system/test/e2e`. The `build_system_Etest` script will automatically include the test. - --- ## How to Write Tests @@ -383,7 +377,7 @@ The test suite is based on Jest, using Jest's assertions and matcher mechanisms. ### Assertions -`expect(expr)` creates an assertion object, e.g.`expect(isLinux())` +`expect(expr)` creates an assertion object, e.g. `expect(isLinux())` ### Matchers @@ -420,11 +414,10 @@ describe('osTypeCheck', () => { }); ``` -Note: ES6 imports are read-only and cannot be modified, so you must use `import * as utils` to import the module. -Whether to mock the whole module or just a function depends on the specific situation. +Note: ES6 imports are read-only and cannot be modified, so you must use `import * as utils` to import the module. +Whether to mock the whole module or just a function depends on the specific situation. Mock functions provide many interfaces for assertions and checks. See [official documentation](https://jestjs.io/docs/mock-function-api). - ### Asynchronous Code Functions that return a Promise can be tested directly. If they return `Promise.resolve()`, the test passes; if they return `Promise.reject()`, the test fails. @@ -436,50 +429,59 @@ Test results are printed to the command line by default. Example (may change in the future): ``` -console.log - [Jest][osTypeCheck should detect OS type correctly] Peak memory change: 0.28 MB - - at Object.log (testHook/jest.memory-usage.js:12:11) - - console.log - [Jest][osTypeCheck should detect OS type correctly] Test time: 10 ms - - at Object.log (testHook/jest.time-usage.js:11:11) +> ArkTS2.0_build_system@1.0.0 build_system_Utest +> jest --testMatch='**/test/ut/**/osType.test.ts' --testPathIgnorePatterns='test/e2e/' console.log - [Jest][osTypeCheck mocked isWindows always return true] Peak memory change: 0.28 MB + [Jest][osType should detect OS type correctly used 0.28 MB] - at Object.log (testHook/jest.memory-usage.js:12:11) + at Object. (test/testHook/jest.memory-usage.js:27:11) console.log - [Jest][osTypeCheck mocked isWindows always return true] Test time: 1 ms - - at Object.log (testHook/jest.time-usage.js:11:11) - - PASS test/ut/osType/osType.test.ts - osTypeCheck - ✓ should detect OS type correctly (11 ms) - ✓ mocked isWindows always return true (2 ms) - PASS test/ut/mockConsoleLog.test.ts - mockConsoleLog - ✓ should detect OS type correctly - -----------|---------|----------|---------|---------|------------------- -| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | -| ---------- | --------- | ---------- | --------- | --------- | ------------------- | -| All files | 35.29 | 0 | 50 | 35.29 | -| utils.ts | 35.29 | 0 | 50 | 35.29 | 38-58 | -| ---------- | --------- | ---------- | --------- | --------- | ------------------- | -Test Suites: 2 passed, 2 total -Tests: 3 passed, 3 total + [Jest][osType should detect OS type correctly spent 6 ms] + + at Object. (test/testHook/jest.time-usage.js:26:11) + + console.warn + [Jest][No .abc files found in /root/0328/ets_frontend/ets2panda/driver/build_system/dist/cache] + + 42 | const abcFiles = getAllAbcFiles(cacheDir); + 43 | if (abcFiles.length === 0) { + > 44 | console.warn(`[Jest][No .abc files found in ${cacheDir}]`); + | ^ + 45 | return; + 46 | } + 47 | abcFiles.forEach(file => { + + at Object. (test/testHook/jest.abc-size.js:44:13) + + PASS test/ut/osType.test.ts + osType + ✓ should detect OS type correctly (8 ms) + +---------------|---------|----------|---------|---------|--------------------------------------------------------- +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s +---------------|---------|----------|---------|---------|--------------------------------------------------------- +All files | 41.05 | 13.95 | 14.63 | 41.26 | + error_code.ts | 100 | 100 | 100 | 100 | + logger.ts | 6.75 | 0 | 0 | 6.84 | 25-103,125-126,147-179,189-218 + pre_define.ts | 100 | 100 | 100 | 100 | + utils.ts | 36.76 | 0 | 23.07 | 36.76 | 51-53,57-60,64-71,77-78,82,87-95,99-109,114-120,128-143 +---------------|---------|----------|---------|---------|--------------------------------------------------------- +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total Snapshots: 0 total -Time: 0.668 s, estimated 1 s +Time: 0.903 s Ran all test suites. ``` -The table is a summary of coverage, showing statement, branch, function, and line coverage for each file. -Test Suites is the number of test files executed. -Tests is the number of test/it blocks in the test files. +The first three console.log/warn statements at the beginning are outputs for testing hooks, showing memory usage, test duration, and the size of abc files, respectively. +The formats are `[Jest][${test_name} used ${memory} MB]`, +`[Jest][${test_name} spent ${time} ms]`, and +`[Jest][No .abc files found in ${cacheDir}]`. +The table is a summary of coverage, showing statement, branch, function, and line coverage for each file. +Test Suites is the number of test files executed. +Tests is the number of test/it blocks in the test files. Since there may be command line output during compilation, it is recommended to redirect the output stream, for example, by outputting compilation information to a temporary file. --- @@ -488,34 +490,36 @@ Since there may be command line output during compilation, it is recommended to ``` build_system -├── dist # Compiled output directory -│   └── coverage # Coverage output directory +├── dist # Build output directory +│ └── coverage # Coverage output directory ├── src # Source code directory ├── test # Test directory -│   ├── ut # Unit tests -│   │   ├── mockOsType # Mock function tests -│   │   │ └── mockOsType.test.ts # Mock OS type test -│   │   ├── mockConsoleLog.test.ts # Mock console.log test -│   │   └── ... # More unit tests -│   └── e2e # End-to-end tests -│     ├── compile.test.ts # Compilation test -│   ├── abcGenerationTest # Example hap project -│     └── ... # More end-to-end tests -├── testHook # Test hook files -│   ├── jest.memory-usage.js # Memory usage monitoring -│   ├── jest.time-usage.js # Test time monitoring -│   └── jest.abc-size.js # abc file size monitoring +│ ├── ut # Unit tests +│ │ ├── mockOsType # Mock function tests +│ │ │ └── mockOsType.test.ts # Mock OS type test +│ │ ├── mockConsoleLog.test.ts # Mock console.log test +│ │ └── ... # More unit tests +│ └── e2e # End-to-end tests +│ ├── compile.test.ts # Compile test +│ ├── checkHash.test.ts # Hash check test +│ ├── abcGenerationTest # Example hap project +│ ├── ... # More end-to-end tests +│ └── testHook # Test hook files +│ ├── jest.memory-usage.js # Memory usage monitor +│ ├── jest.time-usage.js # Test time monitor +│ └── jest.abc-size.js # abc file size monitor ├── package.json # Project configuration file -└── jest.config.js # Jest configuration file +└── jest.config.js # Jest configuration ``` --- ## Coverage Report -A summary of coverage information is printed to the command line. -A detailed coverage report is output to the `dist/coverage` directory by default. You can change the output path by modifying the `coverageDirectory` field in `jest.config.js`. -Jest uses `Istanbul` to generate coverage reports. +A summary of coverage information is printed to the command line. +A detailed coverage report is output to the `dist/coverage` directory by default. +You can change the output path by modifying the `coverageDirectory` field in `jest.config.js`. +Jest uses `Istanbul` to generate coverage reports. The coverage report folder includes the following: ``` @@ -533,5 +537,3 @@ dist/coverage │ └── sorter.js └── lcov.info # Standard LCOV format coverage data file ``` - ----