1/*
2 * Copyright (c) 2023 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
16
17const ts = require('typescript');
18const path = require('path');
19const fs = require('fs')
20
21
22const ignoreCaseFilePath= path.join(__dirname, "ignorecase.json")
23const compResults = {"detail":{}, 'failNum':0, "passedNum":0}
24let consoleDetail = false;
25let ignoreList = [];
26let failTestCaseList = [];
27let genResultFile = false;
28let arktsVersion = '1.1';
29let enableBeta2 = false;
30// Traverse the directory to find all test cases
31function getAllETSFiles(filePath) {
32  let allFilePaths = [];
33  if (fs.existsSync(filePath)) {
34      const files = fs.readdirSync(filePath);
35      for (let i = 0; i < files.length; i++) {
36          let file = files[i]; // File name (excluding file path)
37          let currentFilePath = path.normalize(path.join(filePath, file));
38          let stats = fs.lstatSync(currentFilePath);
39          if(ignoreList.includes(currentFilePath)){
40            continue
41          }
42          if (stats.isDirectory()) {
43              allFilePaths = allFilePaths.concat(getAllETSFiles(currentFilePath));
44          } else {
45              var index= currentFilePath.lastIndexOf(".");
46              var ext = currentFilePath.substring(index+1);
47              if (ext === 'ets' || ext === 'ts') {
48                allFilePaths.push(currentFilePath);
49                runComp(currentFilePath, file)
50              }
51          }
52      }
53  } else {
54      console.warn(`The specified directory ${filePath} Non-existent!`);
55  }
56  return allFilePaths;
57}
58
59function runComp(currentFilePath, file){
60    const result = runLinter(currentFilePath)
61    let jsonFile = currentFilePath.replace('.ets', '.json');
62    jsonFile = jsonFile.replace('.ts', '.json');
63    const checkfile = fs.existsSync(jsonFile);
64    if(checkfile){
65      loadPares(jsonFile, result, currentFilePath, file)
66    }else{
67      if (!currentFilePath.includes('-dependencie.ets') || ignoreList.includes(currentFilePath)) {
68        console.log(`Test cases ${currentFilePath} expected results are not added`)
69      }
70    }
71}
72
73function forceUpdateExpected(expect, reality, jsonFile) {
74  let updateArray = [];
75  for (let i = 0; i < reality.length; i++) {
76    const realErrorItem = reality[i];
77    const { line, character } = realErrorItem.file.getLineAndCharacterOfPosition(realErrorItem.start);
78    const realLine = { 'line': line + 1, 'character': character + 1 };
79    const realMessageText = typeof (realErrorItem.messageText) === 'string' ? realErrorItem.messageText : realErrorItem.messageText.messageText;
80    let data = {
81      messageText: realMessageText,
82      expectLineAndCharacter: realLine
83    };
84    updateArray.push(data);
85  }
86  if (arktsVersion === '1.0') {
87    expect.arktsVersion_1_0 = updateArray;
88  } else {
89    expect.arktsVersion_1_1 = updateArray;
90  }
91  let s = JSON.stringify(expect, null, 2);
92  fs.writeFileSync(jsonFile, s);
93}
94
95// Compare the results with expectations and count the success and failure situations
96function loadPares(jsonFile, result, currentFilePath, file){
97  const dirName = path.dirname(currentFilePath)
98  let rules = ""
99  if (dirName.includes("\\")){
100    rules = currentFilePath.split("\\")[currentFilePath.split("\\").length - 2]
101  }else{
102    rules = currentFilePath.split("/")[currentFilePath.split("/").length - 2]
103  }
104  const testCaseFileName = file
105  dataStr = fs.readFileSync(jsonFile, "utf-8")
106  const expect = JSON.parse(dataStr)
107  // if need update expected files, insert forceUpdateExpected(expect, result, jsonFile) here.
108  let expectByVersion = arktsVersion === '1.0' ? expect.arktsVersion_1_0 : expect.arktsVersion_1_1;
109  if (expectByVersion === undefined) {
110    expectByVersion = [];
111  }
112
113  const compResult = compareResult(expectByVersion, result);
114  compResult["testCaseName"] = testCaseFileName
115  if (Object.prototype.hasOwnProperty.call(compResults.detail, rules)) {
116    compResults["detail"][rules]["detail"].push(compResult)
117    compResults["detail"][rules]["testCaseNum"] += 1
118  }else{
119    compResults["detail"][rules] = {"detail":[compResult], "testCaseNum": 1, "failNum": 0, "passedNum": 0}
120  }
121  if(compResult.status){
122    compResults["passedNum"] += 1
123    compResults["detail"][rules]["passedNum"] += 1
124  }else{
125    failTestCaseList.push(currentFilePath)
126    if(consoleDetail){
127      console.log(`Test cases ${currentFilePath} Failed!`)
128      for(let compDetail of compResult.detail){
129        if(!compDetail.compResult){
130          console.log(`==>  Expect the error in Line ${compDetail.expectLineAndCharacter.line} The ${compDetail.expectLineAndCharacter.character} character. Expect exception rules:${compDetail.expectMessageText}  Actual error line ${compDetail.realLineAndCharacter.line}  The ${compDetail.realLineAndCharacter.character} character. Actual exception rules:${compDetail.realMessageText} Comparison Result:Fail!`)
131        }
132      }
133    }
134    compResults["failNum"] += 1
135    compResults['detail'][rules]["failNum"] += 1
136  }
137}
138
139
140// initial configuration
141options = ts.readConfigFile('tsconfig.json', ts.sys.readFile).config.compilerOptions;
142const allPath = ['*'];
143Object.assign(options, {
144  'emitNodeModulesFiles': true,
145  'importsNotUsedAsValues': ts.ImportsNotUsedAsValues.Preserve,
146  'module': ts.ModuleKind.ES2020,
147  'moduleResolution': ts.ModuleResolutionKind.NodeJs,
148  'noEmit': true,
149  'target': ts.ScriptTarget.ES2021,
150  'baseUrl': "/",
151  'paths': {
152    '*': allPath
153  },
154  'lib': [
155    'lib.es2021.d.ts'
156  ],
157  'types': [],
158  'etsLoaderPath': 'null_sdkPath',
159  'compatibleSdkVersion': 12,
160  'compatibleSdkVersionStage': 'beta3'
161});
162
163// Calling the runlinter interface
164function runLinter(rootName) {
165  nonStrictCheckParam = {
166    allowJS: true,
167    checkJs: false
168  };
169  Object.assign(options, nonStrictCheckParam);
170  if (enableBeta2) {
171    beta2Param = { compatibleSdkVersionStage: 'beta2'};
172    Object.assign(options, beta2Param);
173  }
174  builderProgram = ts.createIncrementalProgramForArkTs({
175      rootNames: [path.join(process.cwd(), rootName)],
176      options: options,
177    });
178
179  let result = arktsVersion === '1.0' ? ts.ArkTSLinter_1_0.runArkTSLinter(builderProgram) : ts.ArkTSLinter_1_1.runArkTSLinter(builderProgram);
180  return result;
181}
182
183// Compare the difference between the expected value and the actual return value of the runlinter to determine if the test has passed
184function compareResult(expect, reality){
185  let isPass = true
186  const itemPassList = new Array()
187   if(reality.length == 0){
188      if(expect.length == 0){
189        // pass
190        isPass = true
191      }else{
192        isPass = false
193        for(let expectInfo of expect){
194          const compInfo = {
195            'compResult':false,
196            'realLineAndCharacter':{"line": null,"character": null},
197            'realMessageText':null,
198            'expectLineAndCharacter':{"line": expectInfo.expectLineAndCharacter.line,"character": expectInfo.expectLineAndCharacter.character},
199            'expectMessageText':expectInfo.messageText,
200          }
201          itemPassList.push(compInfo)
202        }
203      }
204   }else{
205      if(expect.length == 0){
206        isPass = false
207        for(let realityInfo of reality){
208            const { line, character } = realityInfo.file.getLineAndCharacterOfPosition(realityInfo.start)
209            const compInfo = {
210              'compResult':false,
211              'realLineAndCharacter':{"line": line + 1,"character": character + 1},
212              'realMessageText':realityInfo.messageText,
213              'expectLineAndCharacter':{"line": null,"character": null},
214              'expectMessageText':null,
215            }
216            itemPassList.push(compInfo)
217        }
218      }else{
219        if( reality.length > expect.length){
220          isPass = false
221          for(let i=0; i<reality.length; i++){
222            const realErrorItem = reality[i]
223            const { line, character } = realErrorItem.file.getLineAndCharacterOfPosition(realErrorItem.start)
224            const realLine = {"line": line + 1,"character": character + 1}
225            const realMessageText = typeof (realErrorItem.messageText) === 'string' ? realErrorItem.messageText : realErrorItem.messageText.messageText;
226            let expectMessageText = null
227            let compResult = false
228            let expectLineAndCharacter = {"line": null,"character": null}
229            if( expect.length < i+1){
230              compResult = false
231            }else{
232              expectErrorItem = expect[i]
233              expectLineAndCharacter = {"line": expectErrorItem.expectLineAndCharacter.line,"character": expectErrorItem.expectLineAndCharacter.character}
234              expectMessageText = expectErrorItem.messageText
235              if ((expectErrorItem.expectLineAndCharacter.line === realLine.line && expectErrorItem.expectLineAndCharacter.character === realLine.character) &&
236                realMessageText === expectMessageText) {
237                compResult = true
238              }
239            }
240            const compInfo = {
241              'compResult':compResult,
242              'realLineAndCharacter':realLine,
243              'realMessageText':realMessageText,
244              'expectLineAndCharacter':expectLineAndCharacter,
245              'expectMessageText':expectMessageText,
246            }
247            itemPassList.push(compInfo)
248          }
249        }else if(reality.length < expect.length){
250          isPass = false
251          for(let i=0; i<expect.length; i++){
252            const expectErrorItem = expect[i]
253            const expectMessageText = expectErrorItem.messageText
254            let expectLineAndCharacter = {"line": expectErrorItem.expectLineAndCharacter.line,"character": expectErrorItem.expectLineAndCharacter.character}
255            let realLine = {"line": null,"character": null}
256            let realMessageText = null
257            let compResult = false
258            if( reality.length < i+1){
259              compResult = false
260            }else{
261              const realErrorItem = reality[i]
262              const { line, character } = realErrorItem.file.getLineAndCharacterOfPosition(realErrorItem.start)
263              realLine = {"line": line + 1,"character": character + 1}
264              realMessageText = typeof (realErrorItem.messageText) === 'string' ? realErrorItem.messageText : realErrorItem.messageText.messageText;
265              if ((expectErrorItem.expectLineAndCharacter.line === realLine.line && expectErrorItem.expectLineAndCharacter.character === realLine.character) &&
266                realMessageText === expectMessageText) {
267                compResult = true
268              }
269            }
270            const compInfo = {
271              'compResult':compResult,
272              'realLineAndCharacter':realLine,
273              'realMessageText':realMessageText,
274              'expectLineAndCharacter':expectLineAndCharacter,
275              'expectMessageText':expectMessageText,
276            }
277            itemPassList.push(compInfo)
278          }
279        }else{
280            for(let i =0;i<reality.length;i++){
281              const realErrorItem = reality[i]
282              const expectErrorItem = expect[i]
283              const expectMessageText = expectErrorItem.messageText
284              let expectLineAndCharacter = {"line": expectErrorItem.expectLineAndCharacter.line,"character": expectErrorItem.expectLineAndCharacter.character}
285              const { line, character } = realErrorItem.file.getLineAndCharacterOfPosition(realErrorItem.start)
286              const realLine = {"line": line + 1,"character": character + 1}
287              const realMessageText = typeof (realErrorItem.messageText) === 'string' ? realErrorItem.messageText : realErrorItem.messageText.messageText;
288              let compInfo = null; compResult = false
289              if ((expectErrorItem.expectLineAndCharacter.line === realLine.line && expectErrorItem.expectLineAndCharacter.character === realLine.character) &&
290                realMessageText === expectMessageText) {
291                compResult = true
292              }else{
293                isPass = false
294              }
295              compInfo = {
296                'compResult':compResult,
297                'realLineAndCharacter':realLine,
298                'realMessageText':realMessageText,
299                'expectLineAndCharacter':expectLineAndCharacter,
300                'expectMessageText':expectMessageText,
301              }
302              itemPassList.push(compInfo)
303            }
304      }
305    }
306  }
307  return {"status":isPass, "detail":itemPassList}
308}
309
310// output result file
311function writeResult(result){
312  const dir = path.join(__dirname, "test_results")
313  if (!fs.existsSync(dir)) {
314    fs.mkdirSync(dir)
315  }
316  fs.writeFileSync(path.join(dir, "test_result.json"), JSON.stringify(result, null, 4))
317}
318
319
320function run(){
321   let interval = 0, startTime = process.uptime()*1000, endTime = startTime;
322   const pathParam = getParam()
323   let filePath = 'testcase'
324   if(pathParam){
325    filePath = pathParam
326   }
327   let ignoreCaseConfigList = []
328   if(fs.existsSync(ignoreCaseFilePath)){
329    ignoreCaseConfigList = JSON.parse(fs.readFileSync(ignoreCaseFilePath)).ignoreCase
330    if (enableBeta2) {
331      ignoreCaseConfigList.push(...JSON.parse(fs.readFileSync(ignoreCaseFilePath)).beta3);
332    } else {
333      ignoreCaseConfigList.push(...JSON.parse(fs.readFileSync(ignoreCaseFilePath)).beta2);
334    }
335   }
336
337   ignoreList = ignoreList.concat(ignoreCaseConfigList).map(x => path.normalize(x));
338
339   let filePathStats = fs.lstatSync(filePath)
340   if(!filePathStats.isDirectory()){
341      runComp(filePath, path.basename(filePath))
342   }else{
343      getAllETSFiles(filePath)
344   }
345   endTime = process.uptime()*1000
346   interval = (endTime - startTime);
347   const compReportDetail = {"compDetail": compResults, "compTime": interval, "failNum": compResults.failNum, "passedNum": compResults.passedNum}
348   const testCaseSum = compReportDetail.failNum + compReportDetail.passedNum
349   compReportDetail["testCaseSum"] = testCaseSum
350   console.log(`Total number of test cases:${testCaseSum} Number of use cases passed:${compResults.passedNum} The number of use cases that failed:${compResults.failNum} Total running time:${JSON.stringify(interval).split(".")[0]}ms. ArkTSVersion: ${arktsVersion}`)
351   if(genResultFile){
352     writeResult(compReportDetail)
353   }
354   if (failTestCaseList.length > 0){
355      console.log("Failed test cases:")
356      for(let testCase of failTestCaseList){
357        console.log(testCase)
358      }
359    }
360   if(ignoreList.length>0){
361    console.log("Ignored test cases:")
362    for(let ignoreCase of ignoreList){
363      console.log(ignoreCase)
364    }
365   }
366   if(compReportDetail.failNum){
367    process.exit(1)
368   }
369}
370
371// get parameters
372function getParam(){
373  let pathArg = null
374  for(let key of process.argv){
375    if(key.includes("-P:")){
376      pathArg = key.replace("-P:", "")
377    }
378    if(key === "--detail" || key === "-D"){
379      consoleDetail = true
380    }
381    if(key === "-e"){
382      genResultFile = true
383    }
384    if(key.includes("--ignore-list:")){
385      let ignoreStr = key.replace("--ignore-list:", "")
386      ignoreList = ignoreStr.split(",")
387    }
388    if (key === '-v1.0') {
389      arktsVersion = '1.0';
390    }
391    if (key === '-v1.1') {
392      arktsVersion = '1.1';
393    }
394    if (key === '-beta2') {
395      enableBeta2 = true;
396    }
397  }
398  return pathArg
399}
400
401// execute
402run()
403