/*
* Copyright (c) 2023 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use rollupObject 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.
*/
// Execute this file first to avoid circular dependency problems
import { expect } from 'chai';
import mocha from 'mocha';
import fs from "fs";
import path from "path";
import MagicString from 'magic-string';
import sinon from 'sinon';
import {
getBuildModeInLowerCase,
getPackageInfo,
genSourceMapFileName,
isOhModules,
isEs2Abc,
writeArkguardObfuscatedSourceCode,
writeMinimizedSourceCode,
tryMangleFileName
} from '../../lib/ark_utils';
import {
DEBUG,
RELEASE,
OH_MODULES,
EXTNAME_TS,
EXTNAME_JS,
EXTNAME_ETS,
OBFUSCATION_TOOL
} from '../../lib/fast_build/ark_compiler/common/ark_define';
import RollUpPluginMock from './mock/rollup_mock/rollup_plugin_mock';
import {
BUNDLE_NAME_DEFAULT,
ENTRY_MODULE_NAME_DEFAULT,
EXTNAME_MAP,
ENTRYABILITY_JS
} from './mock/rollup_mock/common';
import projectConfig from './utils/processProjectConfig';
import {
ES2ABC,
TS2ABC
} from '../../lib/pre_define';
import { changeFileExtension } from '../../lib/fast_build/ark_compiler/utils';
import ModuleSourceFileMock from './mock/class_mock/module_source_files_mock';
import {
genTemporaryPath,
toUnixPath
} from '../../lib/utils';
import {
ObConfigResolver,
MergedConfig
} from '../../lib/fast_build/ark_compiler/common/ob_config_resolver';
import {
utProcessArkConfig
} from '../../lib/fast_build/ark_compiler/common/process_ark_config';
import { ModuleSourceFile } from '../../lib/fast_build/ark_compiler/module/module_source_file';
import { PROJECT_ROOT, TERSER_PROCESSED_EXPECTED_CODE } from './mock/rollup_mock/path_config';
import { GEN_ABC_PLUGIN_NAME } from '../../lib/fast_build/ark_compiler/common/ark_define';
import { SourceMapGenerator } from '../../lib/fast_build/ark_compiler/generate_sourcemap';
import { ArkObfuscator } from 'arkguard';
mocha.describe('test ark_utils file api', function () {
mocha.before(function () {
this.rollup = new RollUpPluginMock();
});
mocha.after(() => {
delete this.rollup;
});
mocha.it('1-1: test getBuildModeInLowerCase under build debug', function () {
this.rollup.build();
const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
expect(buildMode === DEBUG).to.be.true;
});
mocha.it('1-2: test getBuildModeInLowerCase under build release', function () {
this.rollup.build(RELEASE);
const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
expect(buildMode === RELEASE).to.be.true;
});
mocha.it('1-3: test getBuildModeInLowerCase under preview debug', function () {
this.rollup.preview();
const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
expect(buildMode === DEBUG).to.be.true;
});
mocha.it('1-4: test getBuildModeInLowerCase under hot reload debug', function () {
this.rollup.hotReload();
const buildMode = getBuildModeInLowerCase(this.rollup.share.projectConfig);
expect(buildMode === DEBUG).to.be.true;
});
mocha.it('2-1: test getPackageInfo under build debug', function () {
this.rollup.build();
const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
});
mocha.it('2-2: test getPackageInfo under build release', function () {
this.rollup.build(RELEASE);
const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
});
mocha.it('2-3: test getPackageInfo under preview debug', function () {
this.rollup.preview();
const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
});
mocha.it('2-4: test getPackageInfo under hot reload debug', function () {
this.rollup.hotReload();
const returnInfo = getPackageInfo(this.rollup.share.projectConfig.aceModuleJsonPath);
expect(returnInfo[0] === BUNDLE_NAME_DEFAULT).to.be.true;
expect(returnInfo[1] === ENTRY_MODULE_NAME_DEFAULT).to.be.true;
});
mocha.it('3-1: test genSourceMapFileName under build debug', function () {
this.rollup.build();
for (const filePath of this.rollup.share.allFiles) {
if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
const originPath = genSourceMapFileName(filePath);
const expectedPath = `${filePath}${EXTNAME_MAP}`;
expect(originPath === expectedPath).to.be.true;
} else if (filePath.endsWith(EXTNAME_ETS)) {
const originPath = genSourceMapFileName(filePath);
expect(originPath === filePath).to.be.true;
}
}
});
mocha.it('3-2: test genSourceMapFileName under build release', function () {
this.rollup.build(RELEASE);
for (const filePath of this.rollup.share.allFiles) {
if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
const originPath = genSourceMapFileName(filePath);
const expectedPath = `${filePath}${EXTNAME_MAP}`;
expect(originPath === expectedPath).to.be.true;
}
}
});
mocha.it('3-3: test genSourceMapFileName under preview debug', function () {
this.rollup.preview();
for (const filePath of this.rollup.share.allFiles) {
if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
const originPath = genSourceMapFileName(filePath);
const expectedPath = `${filePath}${EXTNAME_MAP}`;
expect(originPath === expectedPath).to.be.true;
}
}
});
mocha.it('3-4: test genSourceMapFileName under hot reload debug', function () {
this.rollup.hotReload();
for (const filePath of this.rollup.share.allFiles) {
if (filePath.endsWith(EXTNAME_TS) || filePath.endsWith(EXTNAME_JS)) {
const originPath = genSourceMapFileName(filePath);
const expectedPath = `${filePath}${EXTNAME_MAP}`;
expect(originPath === expectedPath).to.be.true;
}
}
});
mocha.it('4-1: test isOhModules under build debug', function () {
this.rollup.build();
const returnInfo = isOhModules(this.rollup.share.projectConfig);
expect(returnInfo === false).to.be.true;
this.rollup.share.projectConfig.packageDir = OH_MODULES;
const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
expect(returnInfoOh).to.be.true;
});
mocha.it('4-2: test isOhModules under build release', function () {
this.rollup.build(RELEASE);
const returnInfo = isOhModules(this.rollup.share.projectConfig);
expect(returnInfo === false).to.be.true;
this.rollup.share.projectConfig.packageDir = OH_MODULES;
const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
expect(returnInfoOh).to.be.true;
});
mocha.it('4-3: test isOhModules under preview debug', function () {
this.rollup.preview();
const returnInfo = isOhModules(this.rollup.share.projectConfig);
expect(returnInfo === false).to.be.true;
this.rollup.share.projectConfig.packageDir = OH_MODULES;
const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
expect(returnInfoOh).to.be.true;
});
mocha.it('4-4: test isOhModules under hot reload debug', function () {
this.rollup.hotReload();
const returnInfo = isOhModules(this.rollup.share.projectConfig);
expect(returnInfo === false).to.be.true;
this.rollup.share.projectConfig.packageDir = OH_MODULES;
const returnInfoOh = isOhModules(this.rollup.share.projectConfig);
expect(returnInfoOh).to.be.true;
});
mocha.it('4-5: test isOhModules under hot fix debug', function () {
projectConfig.buildMode = DEBUG;
const returnInfo = isOhModules(projectConfig);
expect(returnInfo).to.be.true;
});
mocha.it('4-6: test isOhModules under hot fix release', function () {
projectConfig.buildMode = RELEASE;
const returnInfo = isOhModules(projectConfig);
expect(returnInfo).to.be.true;
});
mocha.it('5-1: test isEs2Abc under build debug', function () {
this.rollup.build();
this.rollup.share.projectConfig.pandaMode = ES2ABC;
const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfo).to.be.true;
this.rollup.share.projectConfig.pandaMode = TS2ABC;
const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfoTS2ABC === false).to.be.true;
this.rollup.share.projectConfig.pandaMode = "undefined";
const returnInfoUndef = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfoUndef).to.be.true;
this.rollup.share.projectConfig.pandaMode = undefined;
const returnInfoUndefined = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfoUndefined).to.be.true;
});
mocha.it('5-2: test isEs2Abc under build release', function () {
this.rollup.build(RELEASE);
this.rollup.share.projectConfig.pandaMode = ES2ABC;
const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfo).to.be.true;
this.rollup.share.projectConfig.pandaMode = TS2ABC;
const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfoTS2ABC).to.be.false;
});
mocha.it('5-3: test isEs2Abc under preview debug', function () {
this.rollup.preview();
this.rollup.share.projectConfig.pandaMode = ES2ABC;
const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfo).to.be.true;
this.rollup.share.projectConfig.pandaMode = TS2ABC;
const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfoTS2ABC).to.be.false;
});
mocha.it('5-4: test isEs2Abc under hot reload debug', function () {
this.rollup.hotReload();
this.rollup.share.projectConfig.pandaMode = ES2ABC;
const returnInfo = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfo).to.be.true;
this.rollup.share.projectConfig.pandaMode = TS2ABC;
const returnInfoTS2ABC = isEs2Abc(this.rollup.share.projectConfig);
expect(returnInfoTS2ABC).to.be.false;
});
mocha.it('5-5: test isEs2Abc under hot fix debug', function () {
projectConfig.buildMode = DEBUG;
projectConfig.pandaMode = ES2ABC;
const returnInfo = isEs2Abc(projectConfig);
expect(returnInfo).to.be.true;
projectConfig.pandaMode = TS2ABC;
const returnInfoTS2ABC = isEs2Abc(projectConfig);
expect(returnInfoTS2ABC).to.be.false;
});
mocha.it('5-6: test isEs2Abc under hot fix release', function () {
projectConfig.buildMode = RELEASE;
projectConfig.pandaMode = ES2ABC;
const returnInfo = isEs2Abc(projectConfig);
expect(returnInfo).to.be.true;
projectConfig.pandaMode = TS2ABC;
const returnInfoTS2ABC = isEs2Abc(projectConfig);
expect(returnInfoTS2ABC).to.be.false;
});
mocha.it('6-1: test the error message of writeArkguardObfuscatedSourceCode', async function () {
this.rollup.build(RELEASE);
SourceMapGenerator.initInstance(this.rollup);
const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME);
const stub = sinon.stub(logger, 'error');
const red: string = '\x1B[31m';
try {
await writeArkguardObfuscatedSourceCode(
{content: undefined, buildFilePath: '', relativeSourceFilePath: '', originSourceFilePath: ''},
logger, this.rollup.share.projectConfig, {});
} catch (e) {
}
expect(stub.calledWith(red,
`ArkTS:INTERNAL ERROR: Failed to obfuscate file '' with arkguard. TypeError: Cannot read properties of undefined (reading 'obfuscate')`
)).to.be.true;
stub.restore();
SourceMapGenerator.cleanSourceMapObject();
});
mocha.it('7-1: test the error message of writeMinimizedSourceCode', async function () {
this.rollup.build(RELEASE);
const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME);
const stub = sinon.stub(logger, 'error');
const red: string = '\x1B[31m';
const reset: string = '\x1B[39m';
try {
await writeMinimizedSourceCode(undefined, '', logger);
} catch (e) {
}
expect(stub.calledWith(red,
'ArkTS:INTERNAL ERROR: Failed to obfuscate source code for ', reset
)).to.be.true;
stub.restore();
});
mocha.it('8-1: test tryMangleFileName when obfuscation is disabled', async function () {
const filePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/entryability/EntryAbility.ts';
const originalFilePath = '/mnt/application/entry/src/main/ets/entryability/EntryAbility.ets';
const projectConfig = {
projectRootPath: '/mnt/application',
packageDir: 'oh_modules',
modulePathMap: {
entry: '/mnt/application/entry'
},
obfuscationMergedObConfig: {
options: {
enableFileNameObfuscation: false
}
}
}
const result = tryMangleFileName(filePath, projectConfig, originalFilePath);
expect(result === filePath).to.be.true;
});
mocha.it('8-2: test tryMangleFileName when obfuscation is enabled', async function () {
const filePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/entryability/EntryAbility.ts';
const originalFilePath = '/mnt/application/entry/src/main/ets/entryability/EntryAbility.ets';
const newFilePath = '/mnt/application/entry/build/default/cache/default/default@CompileArkTS/esmodule/release/src/main/ets/a/b.ts';
const projectConfig = {
projectRootPath: '/mnt/application',
packageDir: 'oh_modules',
modulePathMap: {
entry: '/mnt/application/entry'
},
obfuscationMergedObConfig: {
options: {
enableFileNameObfuscation: true
}
}
}
const printerConfig = {
//Print obfuscation time&memory usage of all files and obfuscation processes
mFilesPrinter: false,
//Print time&memory usage of a single file obfuscation in transform processes
mSingleFilePrinter: false,
//Print sum up time of transform processes during obfuscation
mSumPrinter: false,
//Output path of printer
mOutputPath: ""
}
const arkguardConfig = {
mRenameFileName: {
mEnable: true,
mNameGeneratorType: 1,
mReservedFileNames: [
'entry',
'mnt',
'application',
'entry',
'src',
'main',
'ets',
'build',
'default',
'cache',
'default',
'default@CompileArkTS',
'esmodule',
'release'
],
},
mPerformancePrinter: printerConfig
}
let arkObfuscator: ArkObfuscator = new ArkObfuscator();
arkObfuscator.init(arkguardConfig);
const content = `function foo() {}`
const obfuscateResult = arkObfuscator.obfuscate(content, filePath, undefined);
obfuscateResult.then(result => {
const afterObfuscateFilePath = result.filePath;
expect(afterObfuscateFilePath === newFilePath).to.be.true;
})
const result = tryMangleFileName(filePath, projectConfig, originalFilePath);
expect(result === newFilePath).to.be.true;
});
mocha.it('9-1: test writeArkguardObfuscatedSourceCode when obfuscation is enabled', async function () {
this.rollup.build(RELEASE);
const logger = this.rollup.share.getLogger(GEN_ABC_PLUGIN_NAME);
const arkguardConfig = {
mCompact: false,
mDisableConsole: false,
mSimplify: false,
mRemoveComments: true,
mNameObfuscation: {
mEnable: true,
mNameGeneratorType: 1,
mReservedNames: [],
mRenameProperties: false,
mReservedProperties: [],
mKeepStringProperty: true,
mTopLevel: false,
mReservedToplevelNames: [],
mUniversalReservedProperties: [],
mUniversalReservedToplevelNames: [],
},
mRemoveDeclarationComments: {
mEnable: true,
mReservedComments: []
},
mEnableSourceMap: true,
mEnableNameCache: true,
mRenameFileName: {
mEnable: false,
mNameGeneratorType: 1,
mReservedFileNames: [],
},
mExportObfuscation: false,
mPerformancePrinter: {
mFilesPrinter: false,
mSingleFilePrinter: false,
mSumPrinter: false,
mOutputPath: ""
},
mKeepFileSourceCode: {
mKeepSourceOfPaths: new Set(),
mkeepFilesAndDependencies: new Set(),
},
};
const sourceMapGenerator: SourceMapGenerator = SourceMapGenerator.initInstance(this.rollup);
sourceMapGenerator.setNewSoureMaps(false);
const arkObfuscator: ArkObfuscator = new ArkObfuscator();
arkObfuscator.init(arkguardConfig);
const projectConfig = {
projectRootPath: PROJECT_ROOT,
arkObfuscator: arkObfuscator,
packageDir: 'oh_modules',
localPackageSet: new Set(),
useTsHar: false,
useNormalized: false
};
const sourceFileName = 'sourceFile.ts';
const originalSourceFilePath = path.join(__dirname, '../../test/ark_compiler_ut/testdata/har_obfuscation', sourceFileName);
const moduleInfo = {
content: fs.readFileSync(originalSourceFilePath, 'utf-8'),
buildFilePath: `${PROJECT_ROOT}/har_obfuscation/build/default/cache/${sourceFileName}`,
relativeSourceFilePath: `har_obfuscation/${sourceFileName}`,
originSourceFilePath: originalSourceFilePath,
rollupModuleId: originalSourceFilePath
};
try {
await writeArkguardObfuscatedSourceCode(moduleInfo, logger, projectConfig, {});
} catch (e) {
}
const expectedResult = `export function add(d: number, e: number): number {
return d + e;
}
export function findElement(b: a[], c: (item: a) => boolean): a | undefined {
return b.find(c);
}
`;
const result = fs.readFileSync(moduleInfo.buildFilePath, 'utf-8');
expect(result === expectedResult).to.be.true;
const declFileName = 'sourceFile.d.ts';
const originalDeclFilePath = path.join(__dirname, '../../test/ark_compiler_ut/testdata/har_obfuscation/', declFileName);
const declModuleInfo = {
content: fs.readFileSync(originalDeclFilePath, 'utf-8'),
buildFilePath: `${PROJECT_ROOT}/har_obfuscation/build/default/cache/${declFileName}`,
relativeSourceFilePath: `har_obfuscation/build/default/cache/${declFileName}`,
originSourceFilePath: originalSourceFilePath,
rollupModuleId: originalSourceFilePath
};
try {
await writeArkguardObfuscatedSourceCode(declModuleInfo, logger, projectConfig, {});
} catch (e) {
}
const expectedDeclResult = `export declare function add(d: number, e: number): number;
export declare function findElement(b: a[], c: (item: a) => boolean): a | undefined;
`;
const declResult = fs.readFileSync(declModuleInfo.buildFilePath, 'utf-8');
expect(declResult === expectedDeclResult).to.be.true;
});
});