diff --git a/cli/src/builders/make/index.ts b/cli/src/builders/make/index.ts index 3081df6..aa8b032 100644 --- a/cli/src/builders/make/index.ts +++ b/cli/src/builders/make/index.ts @@ -45,6 +45,7 @@ export class MakeProject { private noChildren: boolean = false; private settings: iProject; private folderSettings: {[folder: string]: FolderOptions} = {}; + private pseudoTargets: {[name: string]: string[]} = {}; constructor(private cwd: string, private targets: Targets) { this.settings = MakeProject.getDefaultSettings(); @@ -67,12 +68,11 @@ export class MakeProject { command: `CRTPGM`, parameters: { pgm: `$(BIN_LIB)/$*`, - entry: `$*`, - modules: `*MODULES`, + entmod: `$*`, + module: `*MODULES`, tgtrls: `*CURRENT`, - tgtccsid: `*JOB`, bnddir: `$(BNDDIR)`, - dftactgrp: `*NO` + replace: `*YES` } }, "pgm.rpgle": { @@ -334,15 +334,31 @@ export class MakeProject { } public getMakefile(specificObjects?: ILEObject[]) { + let customTargetLines = []; + + for (const pseudoTarget in this.pseudoTargets) { + customTargetLines.push( + `${pseudoTarget}:`, + ...this.pseudoTargets[pseudoTarget].map(t => `\t${t}`), + `` + ); + } + return [ ...this.generateHeader(), ``, ...this.generateTargets(specificObjects), ``, - ...this.generateGenericRules() + ...this.generateGenericRules(), + ``, + ...customTargetLines ]; } + addPseudoTarget(name: string, commands: string[]) { + this.pseudoTargets[name] = commands; + } + public generateHeader(): string[] { let baseBinders = [ ...(this.targets.binderRequired() ? [`($(BIN_LIB)/$(APP_BNDDIR))`] : []), diff --git a/cli/src/cli.ts b/cli/src/cli.ts index 446ba5d..74cfdf1 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -10,7 +10,8 @@ export let cliSettings = { fileList: false, lookupFiles: [] as string[], userBranch: ``, - makeFileNoChildren: false + makeFileNoChildren: false, + writeTestRunner: false, }; export function infoOut(message: string) { diff --git a/cli/src/index.ts b/cli/src/index.ts index bb2c415..e9ffe89 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -10,7 +10,8 @@ import { BobProject } from "./builders/bob"; import { ImpactMarkdown } from "./builders/imd"; import { allExtensions } from "./extensions"; import { getBranchLibraryName } from "./builders/environment"; -import { getFiles, renameFiles, replaceIncludes } from './utils'; +import { getFiles, mkdir, renameFiles, replaceIncludes } from './utils'; +import { TestBuilder } from './tester'; const isCli = process.argv.length >= 2 && process.argv[1].endsWith(`so`); @@ -78,6 +79,11 @@ async function main() { cliSettings.fileList = true; break; + case `-wt`: + case `--withTests`: + cliSettings.writeTestRunner = true; + break; + case `-h`: case `--help`: console.log(``); @@ -192,6 +198,22 @@ async function main() { } } + const testModules = targets.getResolvedObjects().filter(o => o.testModule); + if (testModules.length > 0) { + const testBuilder = new TestBuilder(testModules, targets.logger); + const results = testBuilder.getRunnerStructure(); + + if (cliSettings.writeTestRunner) { + targets.storeResolved(path.join(cwd, TestBuilder.getRunnerSourcePath()), results.newObjects.module); + targets.storeResolved(path.join(cwd, TestBuilder.getRunnerSourcePath(true)), results.newObjects.program); + targets.addNewTarget(results.newObjects.program); + + const modulePath = path.join(cwd, TestBuilder.getRunnerSourcePath(false)); + mkdir(path.dirname(modulePath)); // Ensure the directory exists + writeFileSync(modulePath, results.lines.join(`\n`)); + } + } + switch (cliSettings.buildFile) { case `bob`: const bobProj = new BobProject(targets); @@ -206,6 +228,14 @@ async function main() { const makeProj = new MakeProject(cwd, targets); makeProj.setNoChildrenInBuild(cliSettings.makeFileNoChildren); + if (cliSettings.writeTestRunner) { + makeProj.addPseudoTarget(`test`, [ + `liblist -c $(BIN_LIB);\\`, + `liblist -a $(LIBL);\\`, + ...TestBuilder.getRunnerPaseCommands() + ]); + } + let specificObjects: ILEObject[] | undefined = cliSettings.fileList ? cliSettings.lookupFiles.map(f => targets.getResolvedObject(path.join(cwd, f))).filter(o => o) : undefined; writeFileSync(path.join(cwd, `makefile`), makeProj.getMakefile(specificObjects).join(`\n`)); diff --git a/cli/src/logger.ts b/cli/src/logger.ts index 71c053c..9f87125 100644 --- a/cli/src/logger.ts +++ b/cli/src/logger.ts @@ -28,41 +28,41 @@ export class Logger { constructor() {} - flush(specificPath?: string) { - if (specificPath) { - this.logs[specificPath] = []; + flush(relativePath?: string) { + if (relativePath) { + this.logs[relativePath] = []; } else { this.logs = {} } } - fileLog(path: string, log: FileLog) { + fileLog(relativePath: string, log: FileLog) { switch (log.type) { - case `info`: infoOut(`${path}${log.line ? `:${log.line}` : ``} - ${log.message}`); break; - case `warning`: warningOut(`${path}${log.line ? `:${log.line}` : ``} - ${log.message}`); break; + case `info`: infoOut(`${relativePath}${log.line ? `:${log.line}` : ``} - ${log.message}`); break; + case `warning`: warningOut(`${relativePath}${log.line ? `:${log.line}` : ``} - ${log.message}`); break; } - if (!this.logs[path]) { - this.logs[path] = []; + if (!this.logs[relativePath]) { + this.logs[relativePath] = []; } if (log.type === `rename`) { // If this path already contains a rename, ignore this - if (this.logs[path].some(l => l.type === `rename`)) return; + if (this.logs[relativePath].some(l => l.type === `rename`)) return; } - this.logs[path].push(log); + this.logs[relativePath].push(log); } - exists(path: string, type: LogType) { - return this.logs[path] && this.logs[path].some(l => l.type === type) + exists(relativePath: string, type: LogType) { + return this.logs[relativePath] && this.logs[relativePath].some(l => l.type === type) } getAllLogs() { return this.logs; } - getLogsFor(path: string): FileLog[]|undefined { - return this.logs[path]; + getLogsFor(relativePath: string): FileLog[]|undefined { + return this.logs[relativePath]; } } \ No newline at end of file diff --git a/cli/src/targets.ts b/cli/src/targets.ts index 8a8e2b0..00c6a17 100644 --- a/cli/src/targets.ts +++ b/cli/src/targets.ts @@ -36,6 +36,7 @@ export interface ILEObject { systemName: string; longName?: string; type: ObjectType; + testModule?: boolean; text?: string, relativePath?: string; extension?: string; @@ -119,7 +120,7 @@ export class Targets { return path.relative(this.cwd, fullPath); } - private storeResolved(localPath: string, ileObject: ILEObject) { + public storeResolved(localPath: string, ileObject: ILEObject) { this.resolvedObjects[localPath] = ileObject; } @@ -132,19 +133,30 @@ export class Targets { const detail = path.parse(localPath); const relativePath = this.getRelative(localPath); + let subTypeLength = 0; const isProgram = detail.name.toUpperCase().endsWith(`.PGM`); - const name = getSystemNameFromPath(isProgram ? detail.name.substring(0, detail.name.length - 4) : detail.name); + const isTestModule = detail.name.toUpperCase().endsWith(`.TEST`); + + if (isProgram) subTypeLength = 4; + if (isTestModule) subTypeLength = 5; + + const validBaseName = (subTypeLength > 0 ? detail.name.substring(0, detail.name.length - subTypeLength) : detail.name); + const systemName = getSystemNameFromPath((isTestModule ? `t_` : ``) + validBaseName); const extension = detail.ext.length > 1 ? detail.ext.substring(1) : detail.ext; const type: ObjectType = (isProgram ? "PGM" : this.getObjectType(relativePath, extension)); const theObject: ILEObject = { - systemName: name, + systemName, type: type, text: newText, relativePath, extension }; + // The reason we assign seperately is because + // we don't need to clog up the result + if (isTestModule) theObject.testModule = true; + this.storeResolved(localPath, theObject); return theObject; @@ -940,10 +952,6 @@ export class Targets { const pathDetail = path.parse(localPath); const sourceName = pathDetail.base; const ileObject = this.resolvePathToObject(localPath, options.text); - const target: ILEObjectTarget = { - ...ileObject, - deps: [] - }; infoOut(`${ileObject.systemName}.${ileObject.type}: ${ileObject.relativePath}`); @@ -1054,6 +1062,40 @@ export class Targets { }); } + // define internal imports + ileObject.imports = cache.procedures + .filter((proc: any) => proc.keyword[`EXTPROC`]) + .map(ref => { + const keyword = ref.keyword; + let importName: string = ref.name; + const extproc: string | boolean = keyword[`EXTPROC`]; + if (extproc) { + if (extproc === true) importName = ref.name; + else importName = extproc; + } + + if (importName.includes(`:`)) { + const parmParms = importName.split(`:`); + importName = parmParms.filter(p => !p.startsWith(`*`)).join(``); + } + + importName = trimQuotes(importName); + + return importName; + }); + + // define exported functions + if (cache.keyword[`NOMAIN`]) { + ileObject.exports = cache.procedures + .filter((proc: any) => proc.keyword[`EXPORT`]) + .map(ref => ref.name.toUpperCase()); + } + + const target: ILEObjectTarget = { + ...ileObject, + deps: [] + }; + // Find external programs cache.procedures .filter((proc: any) => proc.keyword[`EXTPGM`]) @@ -1245,35 +1287,6 @@ export class Targets { const resolvedObject = this.searchForObject({systemName: ileObject.systemName, type: `CMD`}, ileObject); if (resolvedObject) this.createOrAppend(resolvedObject, target); - // define internal imports - target.imports = cache.procedures - .filter((proc: any) => proc.keyword[`EXTPROC`]) - .map(ref => { - const keyword = ref.keyword; - let importName: string = ref.name; - const extproc: string | boolean = keyword[`EXTPROC`]; - if (extproc) { - if (extproc === true) importName = ref.name; - else importName = extproc; - } - - if (importName.includes(`:`)) { - const parmParms = importName.split(`:`); - importName = parmParms.filter(p => !p.startsWith(`*`)).join(``); - } - - importName = trimQuotes(importName); - - return importName; - }); - - // define exported functions - if (cache.keyword[`NOMAIN`]) { - target.exports = cache.procedures - .filter((proc: any) => proc.keyword[`EXPORT`]) - .map(ref => ref.name.toUpperCase()); - } - if (target.deps.length > 0) infoOut(`Depends on: ${target.deps.map(d => `${d.systemName}.${d.type}`).join(` `)}`); @@ -1468,7 +1481,7 @@ export class Targets { return existingTarget; } - private addNewTarget(dep: ILEObjectTarget) { + public addNewTarget(dep: ILEObjectTarget) { this.targets[`${dep.systemName}.${dep.type}`] = dep; } diff --git a/cli/src/tester.ts b/cli/src/tester.ts new file mode 100644 index 0000000..ccd52b0 --- /dev/null +++ b/cli/src/tester.ts @@ -0,0 +1,112 @@ +import { warningOut } from "./cli"; +import { FileLog, Logger } from "./logger"; +import { ILEObject, ILEObjectTarget, Targets } from "./targets"; +import * as path from "path"; + +export class TestBuilder { + constructor(private testModules: ILEObject[], private logger: Logger) { + } + + static getRunnerSourcePath(pgm?: boolean) { + return pgm ? `.so/runner.pgm.rpgle` : `.so/runner.rpgle`; + } + + static getRunnerPaseCommands(withCc?: boolean) { + if (withCc) { + throw new Error(`Not implemented`); + } else { + return [ + `system "CALL RUNNER PARM('')"` + ] + } + } + + static generatePrototypesForTestExports(exports: string[]) { + return exports.map(e => [`Dcl-Pr ${e} ExtProc;`, `End-Pr;`, ``].join(`\n`)).join(`\n`); + } + + getRunnerStructure() { + let entryModule: ILEObject = { + systemName: `RUNNER`, + type: `MODULE`, + relativePath: TestBuilder.getRunnerSourcePath(), + extension: `rpgle`, + testModule: true, + text: `Module for test runner`, + }; + + let runnerProgram: ILEObjectTarget = { + systemName: `RUNNER`, + type: `PGM`, + extension: `pgm`, + deps: [entryModule, ...this.testModules], + text: `Program for test runner`, + }; + + let lines = [ + `**free`, + ``, + `ctl-opt main(main);`, + ``, + `Dcl-PR printf Int(10) extproc('printf');`, + ` *n Pointer value options(*string);`, + `end-pr;`, + ``, + TestBuilder.generatePrototypesForTestExports(this.testModules.map(m => m.exports).flat()), + ``, + ]; + + lines.push( + `dcl-proc main;`, + `dcl-pi *n;`, + ` runName char(32);`, + `end-pi;`, + ); + + for (const testModule of this.testModules) { + this.logger.flush(testModule.relativePath); + const pathDetail = path.parse(testModule.relativePath); + + if (testModule.exports === undefined || testModule.exports?.length === 0) { + this.logger.fileLog(testModule.relativePath, { type: `warning`, message: `No exports found in module.` }); + continue; + } + + if (testModule.imports) { + if (!testModule.imports.some(i => i.toUpperCase() === `ASSERT`)) { + this.logger.fileLog(testModule.relativePath, { type: `warning`, message: `No assert import found in module.` }); + } + } + + for (const exportName of testModule.exports) { + if (exportName.length > 32) { + this.logger.fileLog(testModule.relativePath, { type: `info`, message: `Export name ${exportName} is more than 32 characters. It is recommended to use shorter than 32 characters.` }); + } + + lines.push(`if (runName = '' OR runName = '${pathDetail.name}' OR runName = '${exportName}');`); + lines.push(`printf('${exportName}:START' + x'25');`); + lines.push(`monitor;`); + lines.push(` ${exportName}();`); + lines.push(` printf(x'25');`); + lines.push(` printf('${exportName}:SUCCESS' + x'25');`); + lines.push(`on-error;`); + lines.push(` printf(x'25');`); + lines.push(` printf('${exportName}:LOG:Use CALL RUNNER ''${exportName}'')' + x'25');`); + lines.push(` printf('${exportName}:CRASH' + x'25');`); + lines.push(`endmon;`); + lines.push(`printf('${exportName}:END' + x'25');`); + lines.push(`endif;`); + } + } + + lines.push(`end-proc;`); + + return { + lines, + newObjects: { + program: runnerProgram, + module: entryModule, + } + } + } +} \ No newline at end of file diff --git a/cli/src/utils.ts b/cli/src/utils.ts index 51788e6..e3706a4 100644 --- a/cli/src/utils.ts +++ b/cli/src/utils.ts @@ -7,7 +7,6 @@ import * as path from "path"; import * as os from "os" export function getSystemNameFromPath(inputName: string) { - let baseName = inputName.includes(`-`) ? inputName.split(`-`)[0] : inputName; // If the name is of valid length, return it @@ -114,6 +113,12 @@ export function renameFiles(logger: Logger) { } } +export function mkdir(dirPath: string) { + try { + fs.mkdirSync(dirPath, {recursive: true}); + } catch (e) {}; +} + /** * * @param command Optionally qualified CL command diff --git a/cli/test/fixtures/projects.ts b/cli/test/fixtures/projects.ts index b82301e..b04c5be 100644 --- a/cli/test/fixtures/projects.ts +++ b/cli/test/fixtures/projects.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; import * as path from "path"; +import { mkdir } from "../../src/utils"; const projectFolder = path.join(__dirname, `..`, `..`, `..`, `testData`); @@ -59,6 +60,17 @@ export function setupPseudo() { return projectPath; } +export function setupTestBuilderSuite() { + const fixturePath = path.join(__dirname, `testing`); + const projectPath = path.join(projectFolder, `testing`); + + deleteDir(projectPath); + mkdir(projectPath); + fs.cpSync(fixturePath, projectPath, {recursive: true}); + + return projectPath; +} + export function createTestBuildScript() { const lines = [ `# First build company system`, @@ -81,12 +93,6 @@ export function createTestBuildScript() { fs.writeFileSync(scriptPath, lines); } -function mkdir(dirPath: string) { - try { - fs.mkdirSync(dirPath, {recursive: true}); - } catch (e) {}; -} - function deleteDir(dirPath: string) { try { fs.rmSync(dirPath, {recursive: true, force: true}); diff --git a/cli/test/fixtures/testing/qrpgleref/banking.rpgleinc b/cli/test/fixtures/testing/qrpgleref/banking.rpgleinc new file mode 100644 index 0000000..15a2bbe --- /dev/null +++ b/cli/test/fixtures/testing/qrpgleref/banking.rpgleinc @@ -0,0 +1,5 @@ +**free + +dcl-pr doubleIt packed(11:2) extproc('doubleIt'); + inputNum packed(11:2) value; +end-pr; \ No newline at end of file diff --git a/cli/test/fixtures/testing/qrpgleref/utils.rpgleinc b/cli/test/fixtures/testing/qrpgleref/utils.rpgleinc new file mode 100644 index 0000000..3697fb4 --- /dev/null +++ b/cli/test/fixtures/testing/qrpgleref/utils.rpgleinc @@ -0,0 +1,5 @@ +**free + +dcl-pr ToLower char(50) extproc; + inputString char(50) value; +end-pr; \ No newline at end of file diff --git a/cli/test/fixtures/testing/qrpglesrc/banking.sqlrpgle b/cli/test/fixtures/testing/qrpglesrc/banking.sqlrpgle new file mode 100644 index 0000000..6ecfb8a --- /dev/null +++ b/cli/test/fixtures/testing/qrpglesrc/banking.sqlrpgle @@ -0,0 +1,11 @@ +**free + +ctl-opt nomain; + +dcl-proc doubleIt export; + dcl-pi *n packed(11:2); + inputNum packed(11:2) value; + end-pi; + + return inputNum * 2; +end-proc; diff --git a/cli/test/fixtures/testing/qrpglesrc/bankingTest.test.rpgle b/cli/test/fixtures/testing/qrpglesrc/bankingTest.test.rpgle new file mode 100644 index 0000000..ffb8465 --- /dev/null +++ b/cli/test/fixtures/testing/qrpglesrc/bankingTest.test.rpgle @@ -0,0 +1,14 @@ +**free + +ctl-opt nomain; + +/copy 'qrpgleref/banking.rpgleinc' + +dcl-pr assert extproc('assert'); + cond int; +end-pr; + +dcl-proc test_doubleIt export; + // really does nothing!! + dsply 'Hello world' +end-proc; \ No newline at end of file diff --git a/cli/test/fixtures/testing/qrpglesrc/stupid.test.rpgle b/cli/test/fixtures/testing/qrpglesrc/stupid.test.rpgle new file mode 100644 index 0000000..46cdc3b --- /dev/null +++ b/cli/test/fixtures/testing/qrpglesrc/stupid.test.rpgle @@ -0,0 +1,16 @@ +**free + +ctl-opt nomain; + +/copy 'qrpgleref/utils.rpgleinc' + +dcl-pr assert extproc('assert'); + cond int; +end-pr; + +dcl-proc test_tolower; + dcl-s result char(20); + result = ToLower('HELLO'); + + assert(result = 'hello'); +end-proc; \ No newline at end of file diff --git a/cli/test/fixtures/testing/qrpglesrc/utils.sqlrpgle b/cli/test/fixtures/testing/qrpglesrc/utils.sqlrpgle new file mode 100644 index 0000000..b5a07d6 --- /dev/null +++ b/cli/test/fixtures/testing/qrpglesrc/utils.sqlrpgle @@ -0,0 +1,13 @@ +**free + +ctl-opt nomain; + +dcl-proc ToLower export; + dcl-pi ToLower char(50); + inputString char(50) value; + end-pi; + + exec sql set :inputString = LOWER(:inputString); + + return inputString; +end-proc; \ No newline at end of file diff --git a/cli/test/fixtures/testing/qrpglesrc/utils.test.sqlrpgle b/cli/test/fixtures/testing/qrpglesrc/utils.test.sqlrpgle new file mode 100644 index 0000000..7318b84 --- /dev/null +++ b/cli/test/fixtures/testing/qrpglesrc/utils.test.sqlrpgle @@ -0,0 +1,12 @@ +**free + +ctl-opt nomain; + +/copy 'qrpgleref/utils.rpgleinc' + +dcl-proc test_tolower export; + dcl-s result char(20); + result = ToLower('HELLO'); + + assert(result = 'hello'); +end-proc; \ No newline at end of file diff --git a/cli/test/fixtures/testing/qsrvsrc/banking.bnd b/cli/test/fixtures/testing/qsrvsrc/banking.bnd new file mode 100644 index 0000000..6c02915 --- /dev/null +++ b/cli/test/fixtures/testing/qsrvsrc/banking.bnd @@ -0,0 +1,3 @@ +STRPGMEXP PGMLVL(*CURRENT) + EXPORT SYMBOL(DOUBLEIT) +ENDPGMEXP \ No newline at end of file diff --git a/cli/test/fixtures/testing/qsrvsrc/utils.bnd b/cli/test/fixtures/testing/qsrvsrc/utils.bnd new file mode 100644 index 0000000..f2312e5 --- /dev/null +++ b/cli/test/fixtures/testing/qsrvsrc/utils.bnd @@ -0,0 +1,3 @@ +STRPGMEXP PGMLVL(*CURRENT) + EXPORT SYMBOL('TOLOWER') +ENDPGMEXP \ No newline at end of file diff --git a/cli/test/make.test.ts b/cli/test/make.test.ts index 8421d50..da02c1e 100644 --- a/cli/test/make.test.ts +++ b/cli/test/make.test.ts @@ -133,7 +133,7 @@ test(`Multi-module program and service program`, () => { '$(PREPATH)/MYWEBAPP.PGM: ', '\tliblist -c $(BIN_LIB);\\', '\tliblist -a $(LIBL);\\', - '\tsystem "CRTPGM PGM($(BIN_LIB)/MYWEBAPP) ENTRY(MYWEBAPP) MODULES(HANDLERA HANDLERB MYWEBAPP) TGTRLS(*CURRENT) TGTCCSID(*JOB) BNDDIR($(BNDDIR)) DFTACTGRP(*NO)" > .logs/mywebapp.splf' + '\tsystem "CRTPGM PGM($(BIN_LIB)/MYWEBAPP) ENTMOD(MYWEBAPP) MODULE(HANDLERA HANDLERB MYWEBAPP) TGTRLS(*CURRENT) BNDDIR($(BNDDIR)) REPLACE(*YES)" > .logs/mywebapp.splf' ].join()); const webappMod = targets.getTarget({systemName: `MYWEBAPP`, type: `MODULE`}); diff --git a/cli/test/testBuilder.test.ts b/cli/test/testBuilder.test.ts new file mode 100644 index 0000000..8cda982 --- /dev/null +++ b/cli/test/testBuilder.test.ts @@ -0,0 +1,134 @@ +import { beforeAll, describe, expect, test } from 'vitest'; + +import { Targets } from '../src/targets' +import { MakeProject } from '../src/builders/make'; +import { getFiles } from '../src/utils'; +import { setupPseudo, setupTestBuilderSuite } from './fixtures/projects'; +import { scanGlob } from '../src/extensions'; +import { TestBuilder } from '../src/tester'; + +const cwd = setupTestBuilderSuite(); + +let files = getFiles(cwd, scanGlob); + +describe.skipIf(files.length === 0)(`TestBuilder tests`, () => { + const targets = new Targets(cwd); + beforeAll(async () => { + targets.loadObjectsFromPaths(files); + const parsePromises = files.map(f => targets.parseFile(f)); + await Promise.all(parsePromises); + + expect(targets.getTargets().length).toBeGreaterThan(0); + targets.resolveBinder(); + }); + + test(`Check base object count`, () => { + const modules = targets.getResolvedObjects(`MODULE`); + expect(modules.length).toBe(5); + expect(targets.getResolvedObjects(`SRVPGM`).length).toBe(2); + + expect(modules.filter(m => m.testModule === true).length).toBe(3); + }); + + test(`Check deps`, () => { + const bankingSrvPgm = targets.searchForObject({systemName: `BANKING`, type: `SRVPGM`}, undefined); + expect(bankingSrvPgm).toBeDefined(); + + const bankingTarget = targets.getTarget(bankingSrvPgm); + expect(bankingTarget).toBeDefined(); + expect(bankingTarget?.deps.length).toBe(1); + expect(bankingTarget?.deps[0].systemName).toBe(`BANKING`); + expect(bankingTarget?.deps[0].type).toBe(`MODULE`); + + const utilsSrvPgm = targets.searchForObject({systemName: `UTILS`, type: `SRVPGM`}, undefined); + expect(utilsSrvPgm).toBeDefined(); + + const utilsTarget = targets.getTarget(utilsSrvPgm); + expect(utilsTarget).toBeDefined(); + expect(utilsTarget?.deps.length).toBe(1); + expect(utilsTarget?.deps[0].systemName).toBe(`UTILS`); + expect(utilsTarget?.deps[0].type).toBe(`MODULE`); + }); + + test(`Generate runner`, () => { + const testModules = targets.getResolvedObjects(`MODULE`).filter(m => m.testModule === true); + expect(testModules.length).toBe(3); + + const testBuilder = new TestBuilder(testModules, targets.logger); + const {newObjects: {program, module}} = testBuilder.getRunnerStructure(); + + expect(module.systemName).toBe(`RUNNER`); + expect(module.type).toBe(`MODULE`); + expect(module.relativePath).toBe(`.so/runner.rpgle`); + expect(module.testModule).toBe(true); + + expect(program.systemName).toBe(`RUNNER`); + expect(program.type).toBe(`PGM`); + expect(program.deps.length).toBe(4); + + const deps = program.deps; + + const runner = deps.find(d => d.systemName === `RUNNER`); + const tbt = deps.find(d => d.systemName === `TBT`); + const tUtils = deps.find(d => d.systemName === `T_UTILS`); + const tStupid = deps.find(d => d.systemName === `T_STUPID`); + + expect(runner).toMatchObject( + { + extension: 'rpgle', + relativePath: '.so/runner.rpgle', + systemName: 'RUNNER', + testModule: true, + type: 'MODULE', + } + ); + + expect(tbt).toMatchObject( + { + extension: 'rpgle', + relativePath: 'qrpglesrc/bankingTest.test.rpgle', + systemName: 'TBT', + testModule: true, + type: 'MODULE', + } + ); + + expect(tUtils).toMatchObject( + { + extension: 'sqlrpgle', + relativePath: 'qrpglesrc/utils.test.sqlrpgle', + systemName: 'T_UTILS', + testModule: true, + type: 'MODULE', + } + ); + + expect(tStupid).toMatchObject( + { + extension: 'rpgle', + relativePath: 'qrpglesrc/stupid.test.rpgle', + systemName: 'T_STUPID', + testModule: true, + type: 'MODULE', + } + ); + + const logger = targets.logger; + + const tStupidLogs = logger.getLogsFor(tStupid.relativePath); + expect(tStupidLogs.length).toBe(1); + expect(tStupidLogs[0]).toMatchObject( + { type: 'warning', message: 'No exports found in module.' } + ); + + const tUtilsLogs = logger.getLogsFor(tUtils.relativePath); + expect(tUtilsLogs.length).toBe(1); + expect(tUtilsLogs[0]).toMatchObject( + { type: 'warning', message: 'No assert import found in module.' } + ); + + const tbtLogs = logger.getLogsFor(tbt.relativePath); + expect(tbtLogs.length).toBe(0); + + }); +});