• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { describe, it } from 'mocha';
17import { expect } from 'chai';
18import {
19  ArkObfuscator,
20  clearGlobalCaches,
21  collectReservedNameForObf,
22  collectResevedFileNameInIDEConfig,
23  deleteLineInfoForNameString,
24  enableObfuscatedFilePathConfig,
25  enableObfuscateFileName,
26  generateConsumerObConfigFile,
27  getRelativeSourcePath,
28  handleObfuscatedFilePath,
29  handleUniversalPathInObf,
30  initObfuscationConfig,
31  mangleFilePath,
32  MemoryUtils,
33  MergedConfig,
34  nameCacheMap,
35  ObConfigResolver,
36  ObfuscationResultType,
37  orignalFilePathForSearching,
38  PropCollections,
39  readNameCache,
40  readProjectPropertiesByCollectedPaths,
41  renameFileNameModule,
42  ReseverdSetForArkguard,
43  TimeSumPrinter,
44  UnobfuscationCollections,
45  writeObfuscationNameCache,
46  writeUnobfuscationContent,
47  unobfuscationNamesObj,
48  FileUtils,
49} from '../../../src/ArkObfuscator';
50
51import {
52  createSourceFile,
53  createObfTextSingleLineWriter,
54  RawSourceMap,
55  SourceFile,
56  ScriptTarget,
57} from 'typescript';
58
59import { IOptions } from '../../../src/configs/IOptions';
60import { getSourceMapGenerator } from '../../../src/utils/SourceMapUtil';
61import {
62  globalFileNameMangledTable,
63  historyFileNameMangledTable,
64} from '../../../src/transformers/rename/RenameFileNameTransformer';
65import { LocalVariableCollections } from '../../../src/utils/CommonCollections';
66import { SOURCE_FILE_PATHS, projectWhiteListManager } from '../../../src/utils/ProjectCollections';
67import { FilePathObj } from '../../../src/common/type';
68import { historyAllUnobfuscatedNamesMap } from '../../../src/initialization/Initializer';
69import path from 'path';
70
71describe('Tester Cases for <ArkObfuscator>', function () {
72  let obfuscator: ArkObfuscator;
73  let defaultConfig: IOptions;
74
75  const sourceFilePathObj: FilePathObj = {
76    buildFilePath: 'demo.ts',
77    relativeFilePath: 'demo',
78  };
79  let sourceFile: SourceFile;
80  let sourceFileContent: string = `class Person {
81  constructor(public name: string, public age: number) {
82      this.name = name;
83      this.age = age;
84  }
85}`;
86
87  const jsSourceFilePathObj: FilePathObj = {
88    buildFilePath: 'demo.js',
89    relativeFilePath: '',
90  };
91  let jsSourceFile: SourceFile;
92  let jsSourceFileContent: string = `//This is a comment
93//This is a comment
94function subtract(a, b) {
95    return a - b;
96}`;
97
98  const declSourceFilePathObj: FilePathObj = {
99    buildFilePath: 'demo.d.ts',
100    relativeFilePath: '',
101  };
102  let declSourceFile: SourceFile;
103  let declSourceFileContent: string = `//This is a comment
104//This is a comment
105export declare function add(num1: number, num2: number): number;
106export declare function findElement<T>(arr: T[], callback: (item: T) => boolean): T | undefined;`;
107
108  beforeEach(() => {
109    obfuscator = new ArkObfuscator();
110
111    // Clear the collection to ensure test isolation
112    PropCollections.clearPropsCollections();
113    UnobfuscationCollections.clear();
114    LocalVariableCollections.clear();
115
116    defaultConfig = {
117      mRemoveComments: true,
118      mNameObfuscation: {
119        mEnable: true,
120        mNameGeneratorType: 1,
121        mReservedNames: [],
122        mRenameProperties: true,
123        mReservedProperties: [],
124        mTopLevel: true,
125        mReservedToplevelNames: [],
126      },
127      mEnableSourceMap: true,
128      mEnableNameCache: true,
129    };
130
131    sourceFile = createSourceFile(sourceFilePathObj.buildFilePath, sourceFileContent, ScriptTarget.ES2015, true);
132    jsSourceFile = createSourceFile(jsSourceFilePathObj.buildFilePath, jsSourceFileContent, ScriptTarget.ES2015, true);
133    declSourceFile = createSourceFile(declSourceFilePathObj.buildFilePath, declSourceFileContent, ScriptTarget.ES2015, true);
134  });
135
136  describe('test for ArkObfuscator.init', () => {
137    it('should return false if config is undefined', () => {
138      const result = obfuscator.init(undefined);
139      expect(result).to.be.false;
140    });
141
142    it('should return true if config is valid', () => {
143      const result = obfuscator.init(defaultConfig);
144      expect(result).to.be.true;
145    });
146
147    it('source code should be compacted into one line', () => {
148      const config: IOptions = {
149        mCompact: true,
150        mEnableSourceMap: true,
151        mRemoveComments: true
152      };
153      obfuscator.init(config);
154
155      let sourceMapGenerator = getSourceMapGenerator(jsSourceFilePathObj.buildFilePath);
156      const textWriter = createObfTextSingleLineWriter();
157      obfuscator.createObfsPrinter(jsSourceFile.isDeclarationFile).writeFile(jsSourceFile, textWriter, sourceMapGenerator);
158      const actualContent = textWriter.getText();
159      const expectContent = `function subtract(a, b) {return a - b;}`;
160      expect(actualContent === expectContent).to.be.true;
161    });
162
163    it('should not init incremental cache when cachePath is not passed', () => {
164      const config: IOptions = {
165        mNameObfuscation: {
166          mEnable: true,
167          mRenameProperties: true,
168          mReservedProperties: [],
169        },
170        mCompact: true,
171        mEnableSourceMap: true,
172        mRemoveComments: true
173      };
174      obfuscator.init(config);
175      expect(obfuscator.fileContentManager).to.be.undefined;
176      expect(obfuscator.filePathManager).to.be.undefined;
177      expect(projectWhiteListManager).to.be.undefined;
178    });
179
180    it('should init incremental cache when cachePath is passed', () => {
181      const config: IOptions = {
182        mNameObfuscation: {
183          mEnable: true,
184          mRenameProperties: true,
185          mReservedProperties: [],
186        },
187        mCompact: true,
188        mEnableSourceMap: true,
189        mRemoveComments: true
190      };
191      const cachePath = 'test/ut/utils/obfuscation';
192      obfuscator.init(config, cachePath);
193      expect(obfuscator.fileContentManager).to.not.be.undefined;
194      expect(obfuscator.filePathManager).to.not.be.undefined;
195      expect(projectWhiteListManager).to.not.be.undefined;
196    });
197
198    it('should set is incremental flag if is incremental', () => {
199      const config: IOptions = {
200        mNameObfuscation: {
201          mEnable: true,
202          mRenameProperties: true,
203          mReservedProperties: [],
204        },
205        mCompact: true,
206        mEnableSourceMap: true,
207        mRemoveComments: true
208      };
209      const cachePath = 'test/ut/utils/obfuscation';
210      const filePathsCache = path.join(cachePath, SOURCE_FILE_PATHS);
211      let content = 'hello';
212      FileUtils.writeFile(filePathsCache, content);
213      obfuscator.init(config, cachePath);
214      expect(obfuscator.isIncremental).to.be.true;
215      FileUtils.deleteFile(filePathsCache);
216    });
217  });
218
219  describe('test for ArkObfuscator.obfuscate', () => {
220    it('should return empty result for ignored files', async () => {
221      const sourceFilePathObj: FilePathObj = {
222        buildFilePath: 'ignoredFile.cpp',
223        relativeFilePath: '',
224      };
225      const result: ObfuscationResultType = await obfuscator.obfuscate(
226        'hello world',
227        sourceFilePathObj,
228      );
229      expect(result).to.deep.equal({ content: undefined });
230    });
231
232    it('should return empty result for empty AST', async () => {
233      const sourceFilePathObj: FilePathObj = {
234        buildFilePath: 'emptyFile.js',
235        relativeFilePath: '',
236      };
237      const result: ObfuscationResultType = await obfuscator.obfuscate('', sourceFilePathObj);
238      expect(result).to.deep.equal({ content: undefined });
239    });
240
241    it('should be correctly obfuscated for valid AST', async () => {
242      obfuscator.init(defaultConfig);
243      const result: ObfuscationResultType = await obfuscator.obfuscate(
244        sourceFile,
245        sourceFilePathObj,
246      );
247      const expectResult = `class g {
248    constructor(public name: string, public h: number) {
249        this.name = name;
250        this.h = h;
251    }
252}
253`;
254      expect(result.content === expectResult).to.be.true;
255    });
256
257    it('comments in declaration file should be removed when enable -remove-comments', async () => {
258      const config: IOptions = {
259        mRemoveDeclarationComments: {
260          mEnable: true,
261          mReservedComments: [],
262          mUniversalReservedComments: [],
263        },
264      };
265
266      obfuscator.init(config);
267      const result: ObfuscationResultType = await obfuscator.obfuscate(declSourceFile, declSourceFilePathObj);
268      const expectResult = `export declare function add(num1: number, num2: number): number;
269export declare function findElement<T>(arr: T[], callback: (item: T) => boolean): T | undefined;
270`;
271      expect(result.content === expectResult).to.be.true;
272    });
273
274    it('comments in declaration file should not be removed when enable -remove-comments', async () => {
275      const config: IOptions = {
276        mRemoveDeclarationComments: {
277          mEnable: false,
278          mReservedComments: [],
279          mUniversalReservedComments: [],
280        },
281      };
282      obfuscator.init(config);
283      const result: ObfuscationResultType = await obfuscator.obfuscate(declSourceFile, declSourceFilePathObj);
284      const expectResult = `//This is a comment
285//This is a comment
286export declare function add(num1: number, num2: number): number;
287export declare function findElement<T>(arr: T[], callback: (item: T) => boolean): T | undefined;
288`;
289      expect(result.content === expectResult).to.be.true;
290    });
291
292    it('unobfuscationNameMap should be Map type when enable -print-kept-names', async () => {
293      const config = {
294        mUnobfuscationOption: {
295          mPrintKeptNames: true,
296          mPrintPath: 'local.json',
297        },
298      };
299      historyAllUnobfuscatedNamesMap.set('demo', {'key': ['value']});
300      obfuscator.init(config);
301      const result: ObfuscationResultType = await obfuscator.obfuscate(
302        sourceFile,
303        sourceFilePathObj,
304      );
305      expect(result.unobfuscationNameMap instanceof Map).to.be.true;
306    });
307
308    it('unobfuscationNameMap should be undefined when disable -print-kept-names, ', async () => {
309      obfuscator.init(defaultConfig);
310      const result: ObfuscationResultType = await obfuscator.obfuscate(
311        sourceFile,
312        sourceFilePathObj,
313      );
314      expect(result.unobfuscationNameMap === undefined).to.be.true;
315    });
316
317    it('historyNameCache should be used when provide historyNameCache', async () => {
318      obfuscator.init(defaultConfig);
319      const historyNameCache = new Map([['#Person', 'm'], ['Person:2:5', 'm']]);
320      const result: ObfuscationResultType = await obfuscator.obfuscate(
321        sourceFile,
322        sourceFilePathObj,
323        undefined,
324        historyNameCache,
325      );
326      const expectResult = `
327        class m {
328          constructor(public name: string, public g: number) {
329            this.name = name;
330            this.g = g;
331          }
332        }
333      `;
334      expect(compareStringsIgnoreNewlines(result.content, expectResult)).to.be.true;
335    });
336
337    it('fileName should be obfuscated when enable -enable-filename-obfuscation', async () => {
338      const config: IOptions = {
339        mRenameFileName: {
340          mEnable: true,
341          mNameGeneratorType: 1,
342          mReservedFileNames: [],
343        },
344      };
345      obfuscator.init(config);
346      const result: ObfuscationResultType = await obfuscator.obfuscate(
347        sourceFile,
348        sourceFilePathObj,
349      );
350
351      expect(orignalFilePathForSearching === 'demo.ts').to.be.true;
352      expect(result.filePath === 'a.ts').to.be.true;
353    });
354
355    it('PropCollections shoule be cleared when only enable toplevel option', async () => {
356      PropCollections.globalMangledTable.set('test', 'obfuscated');
357      const config: IOptions = {
358        mNameObfuscation: {
359          mEnable: true,
360          mNameGeneratorType: 1,
361          mRenameProperties: false,
362          mReservedProperties: [],
363          mTopLevel: true,
364        },
365        mExportObfuscation: false,
366      };
367      obfuscator.init(config);
368      await obfuscator.obfuscate(sourceFile, sourceFilePathObj);
369      expect(PropCollections.globalMangledTable.size).to.equal(0);
370    });
371
372    it('PropCollections shoule not be cleared when enable toplevel、property and export option', async () => {
373      PropCollections.globalMangledTable.set('test', 'obfuscated');
374      const config: IOptions = {
375        mNameObfuscation: {
376          mEnable: true,
377          mNameGeneratorType: 1,
378          mRenameProperties: true,
379          mReservedProperties: [],
380          mTopLevel: true,
381        },
382        mExportObfuscation: true,
383      };
384      obfuscator.init(config);
385      await obfuscator.obfuscate(sourceFile, sourceFilePathObj);
386      expect(PropCollections.globalMangledTable.get('test') === 'obfuscated').to.be.true;
387    });
388
389    it('test for use sourcemap mapping', async () => {
390      obfuscator.init(defaultConfig);
391      const previousStageSourceMap = {
392        "version":3,
393        "file":"demo.js",
394        "sourceRoot":"",
395        "sources":["demo.js"],
396        "names":[],
397        "mappings":"AAAA,mBAAmB;AACnB,mBAAmB;AACnB,SAAS,QAAQ,CAAC,CAAC,EAAE,CAAC;IAClB,OAAO,CAAC,GAAG,CAAC,CAAC;AACjB,CAAC"
398      } as RawSourceMap;
399      const result: ObfuscationResultType = await obfuscator.obfuscate(
400        jsSourceFile,
401        jsSourceFilePathObj,
402        previousStageSourceMap,
403      );
404      const actualSourceMap = JSON.stringify(result.sourceMap);
405      const expectSourceMap = `{"version":3,"file":"demo.js","sources":["demo.js"],"names":[],
406        "mappings":"AAEA,WAAkB,CAAC,EAAE,CAAC;IAClB,OAAO,KAAK,CAAC;AACjB,CAAC","sourceRoot":""}`;
407      expect(compareStringsIgnoreNewlines(actualSourceMap, expectSourceMap)).to.be.true;
408    });
409
410    it('test for not use sourcemap mapping', async () => {
411      obfuscator.init(defaultConfig);
412      const result: ObfuscationResultType = await obfuscator.obfuscate(
413        jsSourceFile,
414        jsSourceFilePathObj,
415      );
416      const actualSourceMap = JSON.stringify(result.sourceMap);
417      const expectSourceMap = `{"version":3,"file":"demo.js","sourceRoot":"","sources":["demo.js"],
418        "names":[],"mappings":"AAEA,WAAkB,CAAC,EAAE,CAAC;IAClB,OAAO,KAAK,CAAC;AACjB,CAAC"}`;
419      expect(compareStringsIgnoreNewlines(actualSourceMap, expectSourceMap)).to.be.true;
420    });
421  });
422
423  describe('test for ArkObfuscator.setWriteOriginalFile', () => {
424    it('should set writeOriginalFile to true', () => {
425      obfuscator.setWriteOriginalFile(true);
426      expect(obfuscator.getWriteOriginalFileForTest()).to.be.true;
427    });
428
429    it('should set writeOriginalFile to false', () => {
430      obfuscator.setWriteOriginalFile(false);
431      expect(obfuscator.getWriteOriginalFileForTest()).to.be.false;
432    });
433  });
434
435  describe('test for ArkObfuscator.addReservedSetForPropertyObf', () => {
436    it('should add reserved sets correctly', () => {
437      const properties: ReseverdSetForArkguard = {
438        structPropertySet: new Set(['struct1']),
439        stringPropertySet: new Set(['string1']),
440        exportNameAndPropSet: new Set(['export1']),
441        exportNameSet: undefined,
442        enumPropertySet: new Set(['enum1']),
443      };
444
445      obfuscator.addReservedSetForPropertyObf(properties);
446
447      expect(UnobfuscationCollections.reservedStruct.has('struct1')).to.be.true;
448      expect(UnobfuscationCollections.reservedStrProp.has('string1')).to.be.true;
449      expect(UnobfuscationCollections.reservedExportNameAndProp.has('export1')).to.be.true;
450      expect(UnobfuscationCollections.reservedEnum.has('enum1')).to.be.true;
451    });
452
453    it('should not add empty sets', () => {
454      const properties: ReseverdSetForArkguard = {
455        structPropertySet: new Set(),
456        stringPropertySet: new Set(),
457        exportNameAndPropSet: new Set(),
458        exportNameSet: undefined,
459        enumPropertySet: new Set(),
460      };
461
462      obfuscator.addReservedSetForPropertyObf(properties);
463
464      expect(UnobfuscationCollections.reservedStruct.size).to.equal(0);
465      expect(UnobfuscationCollections.reservedStrProp.size).to.equal(0);
466      expect(UnobfuscationCollections.reservedExportNameAndProp.size).to.equal(0);
467      expect(UnobfuscationCollections.reservedEnum.size).to.equal(0);
468    });
469  });
470
471  describe('test for ArkObfuscator.addReservedSetForDefaultObf', () => {
472    it('should add reserved export name set correctly', () => {
473      const properties: ReseverdSetForArkguard = {
474        structPropertySet: undefined,
475        stringPropertySet: undefined,
476        exportNameAndPropSet: undefined,
477        exportNameSet: new Set(['exportName1']),
478        enumPropertySet: undefined,
479      };
480
481      obfuscator.addReservedSetForDefaultObf(properties);
482      expect(UnobfuscationCollections.reservedExportName.has('exportName1')).to.be.true;
483    });
484  });
485
486  describe('test for ArkObfuscator.setKeepSourceOfPaths', () => {
487    it('should set the keep source of paths correctly', () => {
488      const config: IOptions = {
489        mKeepFileSourceCode: {
490          mKeepSourceOfPaths: new Set(),
491          mkeepFilesAndDependencies: new Set(),
492        },
493      };
494      obfuscator.init(config);
495
496      const paths = new Set(['path1', 'path2']);
497      obfuscator.setKeepSourceOfPaths(paths);
498      expect(obfuscator.customProfiles.mKeepFileSourceCode?.mKeepSourceOfPaths).to.equal(paths);
499    });
500  });
501
502  describe('test for ArkObfuscator.isCurrentFileInKeepPaths', () => {
503    it('should return false if mKeepSourceOfPaths is empty', () => {
504      const customProfiles: IOptions = {
505        mKeepFileSourceCode: {
506          mKeepSourceOfPaths: new Set(),
507          mkeepFilesAndDependencies: new Set(),
508        },
509      };
510      const result = obfuscator.isCurrentFileInKeepPathsForTest(customProfiles, 'some/file/path.js');
511      expect(result).to.be.false;
512    });
513
514    it('should return true if originalFilePath is in mKeepSourceOfPaths', () => {
515      const keepPaths = new Set(['some/file/path.js']);
516      const customProfiles: IOptions = {
517        mKeepFileSourceCode: {
518          mKeepSourceOfPaths: keepPaths,
519          mkeepFilesAndDependencies: new Set(),
520        }
521      };
522      const result = obfuscator.isCurrentFileInKeepPathsForTest(customProfiles, 'some/file/path.js');
523      expect(result).to.be.true;
524    });
525  });
526
527  describe('test for clearGlobalCaches', () => {
528    beforeEach(() => {
529      PropCollections.globalMangledTable.set('test1', 'obfuscated1');
530      PropCollections.historyMangledTable = new Map([['test2', 'obfuscated2']]);
531      PropCollections.reservedProperties.add('reserved1');
532      PropCollections.universalReservedProperties.push(/universal\d+/);
533      globalFileNameMangledTable.set('key1', 'value1');
534      renameFileNameModule.historyFileNameMangledTable = new Map([['keyA', 'valueA']]);
535      UnobfuscationCollections.reservedSdkApiForProp.add('api1');
536      UnobfuscationCollections.reservedSdkApiForGlobal.add('globalApi1');
537      UnobfuscationCollections.reservedSdkApiForLocal.add('localApi1');
538      UnobfuscationCollections.reservedStruct.add('struct1');
539      UnobfuscationCollections.reservedLangForProperty.add('lang1');
540      UnobfuscationCollections.reservedExportName.add('exportName1');
541      UnobfuscationCollections.reservedExportNameAndProp.add('exportNameAndProp1');
542      UnobfuscationCollections.reservedStrProp.add('stringProp1');
543      UnobfuscationCollections.reservedEnum.add('enum1');
544      UnobfuscationCollections.unobfuscatedPropMap.set('age', new Set(['key', 'value']));
545      UnobfuscationCollections.unobfuscatedNamesMap.set('name1', new Set(['key1', 'value2']));
546      LocalVariableCollections.reservedConfig.add('localConfig1');
547    });
548
549    it('should clear all global caches', () => {
550      clearGlobalCaches();
551
552      expect(PropCollections.globalMangledTable.size).to.equal(0);
553      expect(PropCollections.historyMangledTable.size).to.equal(0);
554      expect(PropCollections.reservedProperties.size).to.equal(0);
555      expect(PropCollections.universalReservedProperties.length).to.equal(0);
556      expect(globalFileNameMangledTable.size).to.equal(0);
557      expect(historyFileNameMangledTable.size).to.equal(0);
558      expect(UnobfuscationCollections.reservedSdkApiForProp.size).to.equal(0);
559      expect(UnobfuscationCollections.reservedStruct.size).to.equal(0);
560      expect(UnobfuscationCollections.reservedExportName.size).to.equal(0);
561      expect(LocalVariableCollections.reservedConfig.size).to.equal(0);
562    });
563  });
564
565  describe('test whether the methods exported from the ArkObfuscator file exist', () => {
566    it('The ArkObfuscator export the collectReservedNameForObf method', () => {
567      expect(collectReservedNameForObf).to.exist;
568    });
569
570    it('The ArkObfuscator export the collectResevedFileNameInIDEConfig method', () => {
571      expect(collectResevedFileNameInIDEConfig).to.exist;
572    });
573
574    it('The ArkObfuscator export the deleteLineInfoForNameString method', () => {
575      expect(deleteLineInfoForNameString).to.exist;
576    });
577
578    it('The ArkObfuscator export the enableObfuscatedFilePathConfig method', () => {
579      expect(enableObfuscatedFilePathConfig).to.exist;
580    });
581
582    it('The ArkObfuscator export the enableObfuscateFileName method', () => {
583      expect(enableObfuscateFileName).to.exist;
584    });
585
586    it('The ArkObfuscator export the generateConsumerObConfigFile method', () => {
587      expect(generateConsumerObConfigFile).to.exist;
588    });
589
590    it('The ArkObfuscator export the getRelativeSourcePath method', () => {
591      expect(getRelativeSourcePath).to.exist;
592    });
593
594    it('The ArkObfuscator export the handleObfuscatedFilePath method', () => {
595      expect(handleObfuscatedFilePath).to.exist;
596    });
597
598    it('The ArkObfuscator export the handleUniversalPathInObf method', () => {
599      expect(handleUniversalPathInObf).to.exist;
600    });
601
602    it('The ArkObfuscator export the initObfuscationConfig method', () => {
603      expect(initObfuscationConfig).to.exist;
604    });
605
606    it('The ArkObfuscator export the mangleFilePath method', () => {
607      expect(mangleFilePath).to.exist;
608    });
609
610    it('The ArkObfuscator export the MemoryUtils method', () => {
611      expect(MemoryUtils).to.exist;
612    });
613
614    it('The ArkObfuscator export the MergedConfig method', () => {
615      expect(MergedConfig).to.exist;
616    });
617
618    it('The ArkObfuscator export the nameCacheMap method', () => {
619      expect(nameCacheMap).to.exist;
620    });
621
622    it('The ArkObfuscator export the ObConfigResolver method', () => {
623      expect(ObConfigResolver).to.exist;
624    });
625
626    it('The ArkObfuscator export the readNameCache method', () => {
627      expect(readNameCache).to.exist;
628    });
629
630    it('The ArkObfuscator export the readProjectPropertiesByCollectedPaths method', () => {
631      expect(readProjectPropertiesByCollectedPaths).to.exist;
632    });
633
634    it('The ArkObfuscator export the TimeSumPrinter method', () => {
635      expect(TimeSumPrinter).to.exist;
636    });
637
638    it('The ArkObfuscator export the writeObfuscationNameCache method', () => {
639      expect(writeObfuscationNameCache).to.exist;
640    });
641
642    it('The ArkObfuscator export the writeUnobfuscationContent method', () => {
643      expect(writeUnobfuscationContent).to.exist;
644    });
645
646    it('The ArkObfuscator export the unobfuscationNamesObj method', () => {
647      expect(unobfuscationNamesObj).to.exist;
648    });
649  });
650});
651
652function compareStringsIgnoreNewlines(str1: string, str2: string): boolean {
653  const normalize = (str: string) => str.replace(/[\n\r\s]+/g, '');
654  return normalize(str1) === normalize(str2);
655}
656