1/* 2* Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development 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 util = require('util'); 17import { replaceAll } from "../common/tool"; 18import { ClassObj, StructObj, FuncInfo, FuncObj, GenInfo, InterfaceList, ParamObj, TypeList } from "./datatype"; 19import { getInterfaceBody, getTypeBody, transTskey2Ckey } from './gendts'; 20import { testAbilityFuncTemplate } from "../template/func_template"; 21import { Logger } from '../common/log'; 22import { cpp2DtsKey, dts2TestValue } from '../template/dtscpp/dts2cpp_key'; 23import * as path from 'path'; 24import * as fs from 'fs'; 25import { testFirstGenTemplate } from '../template/dtscpp/dtscpp_testfirstgen_template'; 26const INTVALUE = 5; 27const FLOATVALUE = 2.5; 28 29export function generateFuncTestCase(funcInfo: FuncInfo, rawFileName: string, typeList: TypeList[], interfaceList: InterfaceList[]) { 30 let { funcParamUse, funcParamDefine, funcInfoParams } = genInitTestfunc(funcInfo, typeList, interfaceList); 31 // 去除调用参数的最后一个',' 32 let index = funcParamUse.lastIndexOf(', '); 33 funcParamUse = funcParamUse.substring(0, index); 34 let callFunc = ''; 35 let hilogContent = ''; 36 // 调用函数 37 Logger.getInstance().info("test funcInfo:" + JSON.stringify(funcInfo)); 38 if (getJsType(funcInfo.retType) !== 'void') { 39 callFunc = util.format('let result: %s = testNapi.%s(%s)\n ', getJsType(funcInfo.retType), funcInfo.name, funcParamUse); 40 // 加 hilog 打印 41 hilogContent = util.format('hilog.info(0x0000, "testTag", "Test NAPI %s: ", JSON.stringify(result));\n ', funcInfo.name); 42 hilogContent += util.format('Logger.getInstance().info("testTag", "Test NAPI %s: ", JSON.stringify(result));\n ', funcInfo.name); 43 } else { 44 callFunc = util.format('testNapi.%s(%s)\n ', funcInfo.name, funcParamUse); 45 } 46 let funcTestReplace = funcParamDefine + callFunc + hilogContent; 47 // 替换test_case_name 48 let funcTestContent = replaceAll(testAbilityFuncTemplate, '[func_direct_testCase]', funcTestReplace); 49 funcTestContent = replaceAll(funcTestContent, '[test_case_name]', funcInfo.name); 50 funcTestContent = replaceAll(funcTestContent, '[file_introduce_replace]', rawFileName); 51 funcTestContent = replaceAll(funcTestContent, '[func_introduce_replace]', funcInfo.name); 52 funcTestContent = replaceAll(funcTestContent, '[input_introduce_replace]', funcInfoParams === '' ? 'void' : funcInfoParams); 53 funcTestContent = replaceAll(funcTestContent, '[func_return_replace]', funcInfo.retType); 54 55 return funcTestContent; 56} 57 58export function genInitTestfunc(funcInfo: FuncInfo, typeList: TypeList[], interfaceList: InterfaceList[]) { 59 let funcParamDefine = ''; 60 let funcParamUse = ''; 61 let funcInfoParams = ''; 62 let funcInfoParamTemp = '[paramName]: [paramType]; '; 63 // 判断函数有几个参数,依次给参数赋值 64 for (let i = 0; i < funcInfo.params.length; i++) { 65 let funcInfoParamReplace = replaceAll(funcInfoParamTemp, '[paramName]', funcInfo.params[i].name); 66 funcInfoParamReplace = replaceAll(funcInfoParamReplace, '[paramType]', funcInfo.params[i].type); 67 funcInfoParams += funcInfoParamReplace; 68 let testType = getTestType(funcInfo.params[i].type); 69 if (testType === 'int') { 70 funcParamDefine += util.format('let %s = %s\n ', funcInfo.params[i].name, INTVALUE); 71 funcParamUse += funcInfo.params[i].name + ', '; 72 } else if (testType === 'float') { 73 funcParamDefine += util.format('let %s = %s\n ', funcInfo.params[i].name, FLOATVALUE); 74 funcParamUse += funcInfo.params[i].name + ', '; 75 } else if (testType === 'bool') { 76 funcParamDefine += util.format('let %s = %s\n ', funcInfo.params[i].name, true); 77 funcParamUse += funcInfo.params[i].name + ', '; 78 } else if (testType === 'string') { 79 funcParamDefine += util.format('let %s = "%s"\n ', funcInfo.params[i].name, 'hello'); 80 funcParamUse += funcInfo.params[i].name + ', '; 81 } else if (getTypeBody(testType, typeList)) { 82 let typeDefineRes = getTypeDefine(testType, funcParamDefine, funcInfo, i, funcParamUse, typeList); 83 funcParamDefine = typeDefineRes[0]; 84 funcParamUse = typeDefineRes[1]; 85 } else if (getInterfaceBody(testType, interfaceList)) { 86 let interfaceDefineRes = getInterfaceDefine(testType, funcParamDefine, funcInfo, i, funcParamUse, interfaceList); 87 funcParamDefine = interfaceDefineRes[0]; 88 funcParamUse = interfaceDefineRes[1]; 89 } 90 } 91 return { funcParamUse, funcParamDefine, funcInfoParams }; 92} 93 94export function getTypeDefine(testType: string, funcParamDefine: string, funcInfo: FuncInfo, i: number, funcParamUse: string, typeList: TypeList[]) { 95 let cTypeDefine = getTypeBody(testType, typeList); 96 let typeDefType = transTskey2Ckey(cTypeDefine as string); 97 // genType 98 if (typeDefType === 'number') { 99 funcParamDefine += util.format('let %s = %s\n ', funcInfo.params[i].name, INTVALUE); 100 funcParamUse += funcInfo.params[i].name + ', '; 101 } else if (typeDefType === 'string') { 102 funcParamDefine += util.format('let %s = "%s"\n ', funcInfo.params[i].name, 'hello'); 103 funcParamUse += funcInfo.params[i].name + ', '; 104 } else if (typeDefType === 'boolean') { 105 funcParamDefine += util.format('let %s = %s\n ', funcInfo.params[i].name, false); 106 funcParamUse += funcInfo.params[i].name + ', '; 107 } 108 return [funcParamDefine, funcParamUse]; 109} 110 111export function genInterFuncParamStr(param: ParamObj[]) { 112 let paramsStr = ''; 113 for(let i = 0; i < param.length; i++) { 114 let rawType = transTskey2Ckey(param[i].type); 115 paramsStr += param[i].name + ': ' + rawType; 116 if (i !== param.length - 1) { 117 paramsStr += ', '; 118 } 119 } 120 return paramsStr; 121} 122 123export function getInterfaceDefine(testType: string, funcParamDefine: string, funcInfo: FuncInfo, i: number, funcParamUse: string, interfaceList: InterfaceList[]) { 124 let objValue = getInterfaceBody(testType, interfaceList); 125 let objTestData = 'let %s:testNapi.%s = { '; 126 let interParams = objValue!.params; 127 let interFuncs = objValue!.funcs; 128 // 成员变量赋值 129 for (let j = 0; j < interParams.length; j++) { 130 let paramType = transTskey2Ckey(interParams[j].type); 131 if (paramType === 'number') { 132 objTestData += util.format('%s: %s, ', interParams[j].name, INTVALUE); 133 } else if (paramType === 'string') { 134 objTestData += util.format('%s: "%s", ', interParams[j].name, 'hello'); 135 } else if (paramType === 'boolean') { 136 objTestData += util.format('%s: %s, ', interParams[j].name, true); 137 } else if (getInterfaceBody(paramType, interfaceList)) { 138 objTestData += util.format('%s: null, ', interParams[j].name); 139 } 140 } 141 142 // 成员函数 143 for (let j = 0; j < interFuncs.length; j++) { 144 let paramStr = genInterFuncParamStr(interFuncs[j].parameters); 145 let initInterFunc = util.format('%s:(%s) => ',interFuncs[j].name, paramStr); 146 let interFuncRetType = transTskey2Ckey(interFuncs[j].returns); 147 if (interFuncRetType === 'void') { 148 let interfaceFuncRetDefine = initInterFunc + '{}'; 149 objTestData += util.format('%s, ', interfaceFuncRetDefine); 150 } else if (interFuncRetType === 'string') { 151 let interfaceFuncRetDefine = initInterFunc + '{return ""}'; 152 objTestData += util.format('%s, ', interfaceFuncRetDefine); 153 } else if (interFuncRetType === 'boolean') { 154 let interfaceFuncRetDefine = initInterFunc + '{ return true }'; 155 objTestData += util.format('%s, ', interfaceFuncRetDefine); 156 } else if (interFuncRetType === 'number') { 157 let interfaceFuncRetDefine = initInterFunc + '{ return 0 }'; 158 objTestData += util.format('%s, ', interfaceFuncRetDefine); 159 } 160 } 161 162 // 去除调用参数的最后一个',' 163 let index = objTestData.lastIndexOf(', '); 164 if (index !== -1) { 165 objTestData = objTestData.substring(0, index) + ' }\n '; 166 } else { 167 objTestData = 'let %s:testNapi.%s = null;\n '; 168 } 169 funcParamDefine += util.format(objTestData, funcInfo.params[i].name, testType); 170 funcParamUse += funcInfo.params[i].name + ', '; 171 return [funcParamDefine, funcParamUse]; 172} 173 174export function getTestType(type: string) { 175 // 去掉const 和 * 176 type = replaceAll(type,'const', ''); 177 type = replaceAll(type, '*', '').trim(); 178 if (type === 'uint32_t' || type === 'int32_t' || type === 'int16_t' || 179 type === 'int64_t' || type === 'int' || type === 'size_t') { 180 return 'int'; 181 } else if (type === 'double_t' || type === 'double' || type === 'float') { 182 return 'float'; 183 } else if (type === 'bool') { 184 return 'bool'; 185 } else if (type === 'std::string' || type.indexOf('char') >= 0) { 186 return 'string'; 187 } 188 return type; 189} 190 191export function getJsType(type: string) { 192 type = replaceAll(type,'const', ''); 193 type = replaceAll(type, '*', '').trim(); 194 for(const keyItem of cpp2DtsKey) { 195 for(const str of keyItem.keys) { 196 if (type.includes(str)) { 197 return transTskey2Ckey(type); 198 } 199 } 200 } 201 return 'testNapi.' + type.replace('*', '').trim(); 202} 203 204// ----------------------- gentest ------------------------ 205// 用H文件为源文件生成Ability.test.ets文件 206export function genAbilitytestFile(rootInfo: GenInfo, out: string) { 207 if (out === undefined || out === null || out.trim() === '') { 208 out = path.dirname(rootInfo.rawFilePath); 209 } 210 let testContent = '' 211 // 1.遍历所有class 212 testContent = genClassTestcase(rootInfo, testContent); 213 214 // 1.遍历所有struct 215 testContent = genStructTestcase(rootInfo, testContent); 216 217 // 2.遍历所有函数 218 testContent = genFunctionTestcase(rootInfo, testContent); 219 220 let fileContent = replaceAll(testFirstGenTemplate.content, 221 '[testAbilityFunctions]', testContent); 222 // 将文件写入out文件夹 223 if (!fs.existsSync(out)) { 224 fs.mkdirSync(out, { recursive: true }); 225 } 226 let lowerFileName = rootInfo.fileName.toLocaleLowerCase(); 227 let fileName = testFirstGenTemplate.name.replace('[fileName]', 228 lowerFileName); 229 fileContent = replaceAll(fileContent, '[fileName]', lowerFileName); 230 let filePath = path.join(out, fileName); 231 fs.writeFileSync(filePath, fileContent); 232} 233 234export function genFunctionTestcase(rootInfo: GenInfo, testContent: string) { 235 if (rootInfo.parseObj && rootInfo.parseObj.funcs) { 236 rootInfo.parseObj.funcs.forEach(funcInfo => { 237 let { funcTestReplace, funcInfoParams } = genMethodTestcase(funcInfo, false, ''); 238 let rawFileName = path.basename(rootInfo.rawFilePath); 239 // 替换test_case_name 240 let funcTestContent = replaceAll(testAbilityFuncTemplate, 241 '[func_direct_testCase]', funcTestReplace); 242 funcTestContent = replaceAll(funcTestContent, '[test_case_name]', 243 funcInfo.name); 244 funcTestContent = replaceAll(funcTestContent, '[file_introduce_replace]', 245 rawFileName); 246 funcTestContent = replaceAll(funcTestContent, 247 '[input_introduce_replace]', funcInfoParams === '' ? 'void' : funcInfoParams); 248 funcTestContent = replaceAll(funcTestContent, '[func_return_replace]', 249 funcInfo.returns); 250 testContent += funcTestContent; 251 }); 252 } 253 return testContent; 254} 255 256export function genStructTestcase(rootInfo: GenInfo, testContent: string) { 257 if (rootInfo.parseObj && rootInfo.parseObj.structs) { 258 // 只测试成员方法,不测试成员变量的get/set函数??? 还是得测试吧? 259 rootInfo.parseObj.structs.forEach(structItem => { 260 let structTestContent = util.format('let %sObj = new testNapi.%s();\n ', structItem.name.toLocaleLowerCase(), structItem.name); 261 // 1.1.遍历所有成员变量 测试set/get函数 262 structItem.members.forEach(variable => { 263 structTestContent = genClassGetSetTestcase(variable, structTestContent, structItem); 264 }); 265 266 // 1.2.遍历所有成员函数 267 structItem.functions.forEach(funcInfo => { 268 // 无法判断函数是否为静态函数/是否为constructor/析构函数,所以先全部测试一遍 269 let { funcTestReplace, funcInfoParams } = genMethodTestcase(funcInfo, true, structItem.name); 270 structTestContent += funcTestReplace; 271 }); 272 let clsTestContent = replaceAll(testAbilityFuncTemplate, '[func_direct_testCase]', structTestContent); 273 clsTestContent = replaceAll(clsTestContent, '[test_case_name]', structItem.name); 274 clsTestContent = replaceAll(clsTestContent, '[file_introduce_replace]', path.basename(rootInfo.rawFilePath)); 275 clsTestContent = replaceAll(clsTestContent, '[input_introduce_replace]', 'this is a test case for class.'); 276 clsTestContent = replaceAll(clsTestContent, '[func_return_replace]', 'this is a test case for class.'); 277 278 testContent += clsTestContent; 279 }); 280 } 281 return testContent; 282} 283 284export function genClassTestcase(rootInfo: GenInfo, testContent: string) { 285 if (rootInfo.parseObj && rootInfo.parseObj.classes) { 286 rootInfo.parseObj.classes.forEach(classItem => { 287 let classTestContent = util.format('let %sObj = new testNapi.%s();\n ', classItem.name.toLocaleLowerCase(), classItem.name); 288 // 1.1.遍历所有成员变量 测试set/get函数 289 classItem.variableList.forEach(variable => { 290 classTestContent = genClassGetSetTestcase(variable, classTestContent, classItem); 291 }); 292 293 // 1.2.遍历所有成员函数 294 classItem.functionList.forEach(funcInfo => { 295 // 无法判断函数是否为静态函数/是否为constructor/析构函数,所以先全部测试一遍 296 let { funcTestReplace, funcInfoParams } = genMethodTestcase(funcInfo, true, classItem.name); 297 classTestContent += funcTestReplace; 298 }); 299 let clsTestContent = replaceAll(testAbilityFuncTemplate, '[func_direct_testCase]', classTestContent); 300 clsTestContent = replaceAll(clsTestContent, '[test_case_name]', classItem.name); 301 clsTestContent = replaceAll(clsTestContent, '[file_introduce_replace]', path.basename(rootInfo.rawFilePath)); 302 clsTestContent = replaceAll(clsTestContent, '[input_introduce_replace]', 'this is a test case for class.'); 303 clsTestContent = replaceAll(clsTestContent, '[func_return_replace]', 'this is a test case for class.'); 304 305 testContent += clsTestContent; 306 }); 307 } 308 return testContent; 309} 310 311export function genClassGetSetTestcase(variable: ParamObj, classTestContent: string, classItem: ClassObj | StructObj) { 312 let variableType = transTskey2Ckey(variable.type); 313 let testValue = 'undefined; // Please give an any value.'; 314 dts2TestValue.forEach(item => { 315 if (item.key === variableType) { 316 testValue = item.value; 317 } 318 }); 319 // 成员变量的set 320 classTestContent += util.format('%sObj.%s = %s;\n ', classItem.name.toLocaleLowerCase(), variable.name, testValue); 321 // 成员变量的get //即打印log 322 classTestContent += util.format('console.log("%sObj.%s = ", %sObj.%s);\n ', classItem.name.toLocaleLowerCase(), 323 variable.name, classItem.name.toLocaleLowerCase(), variable.name); 324 return classTestContent; 325} 326 327export function genMethodTestcase(funcInfo: FuncObj, isClassMethod: boolean, className: string = '') { 328 let callFunc = ''; // 调用函数内容 329 let hilogContent = ''; // hilog内容 330 let funcParamDefine = ''; // 函数参数定义并初始化 331 let funcParamUse = ''; // 函数参数使用 332 let funcInfoParams = ''; // 注释 333 let funcInfoParamTemp = '[paramName]: [paramType]; '; 334 // 遍历方法参数,给参数赋初始值,生成注释和参数使用内容 335 // 1. 先将所有type转换为ts的type 336 for (let i = 0; i < funcInfo.parameters.length; ++i) { 337 // 注释 338 let funcInfoParamReplace = replaceAll(funcInfoParamTemp, '[paramName]', 339 funcInfo.parameters[i].name); 340 funcInfoParamReplace = replaceAll(funcInfoParamReplace, '[paramType]', 341 funcInfo.parameters[i].type); 342 funcInfoParams += funcInfoParamReplace; 343 // 参数定义并初始化 344 const param = funcInfo.parameters[i]; 345 let paramType = transTskey2Ckey(param.type); 346 let testValue = 'undefined; // Please give an any value.'; // any类型咋赋值? 347 dts2TestValue.forEach(item => { 348 if (item.key === paramType) { 349 testValue = item.value; 350 } 351 }); 352 funcParamDefine += util.format('let %s: %s = %s;\n ', 353 funcInfo.parameters[i].name, paramType, testValue); 354 funcParamUse += funcInfo.parameters[i].name + ', '; 355 // 如果是最后一个参数,去掉最后的逗号和空格 356 if (funcInfo.parameters.length === i + 1) { 357 funcParamUse = funcParamUse.slice(0, -2); // 去掉最后一个逗号和空格 358 } 359 } 360 // 返回值,如果本来就是ts类型就不用替换了:如Promise<unknown>,就不用替换了 361 let tsPromiseReg = /Promise<([^>]+)>/g; 362 let returnType = tsPromiseReg.exec(funcInfo.returns) ? funcInfo.returns : 363 transTskey2Ckey(funcInfo.returns); 364 if (returnType === 'void') { 365 callFunc = util.format('%s%s(%s)\n ', isClassMethod ? className.toLocaleLowerCase() + 'Obj.' : 'testNapi.', 366 funcInfo.name, funcParamUse); 367 } else { 368 callFunc = util.format('let result: %s = %s%s(%s)\n ', returnType, 369 isClassMethod ? className.toLocaleLowerCase() + 'Obj.' : 'testNapi.', funcInfo.name, funcParamUse); 370 hilogContent = util.format( 371 'hilog.info(0x0000, "testTag", "Test NAPI %s: ", JSON.stringify(result));\n ', funcInfo.name); 372 } 373 let funcTestReplace = funcParamDefine + callFunc + hilogContent; 374 return { funcTestReplace, funcInfoParams }; 375}