@abapify/adt-aunit 0.4.1 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{aunit-IDVjCJSy.mjs → aunit-9mhc0LYi.mjs} +25 -9
- package/dist/aunit-9mhc0LYi.mjs.map +1 -0
- package/dist/commands/aunit.d.mts +0 -1
- package/dist/commands/aunit.d.mts.map +1 -1
- package/dist/commands/aunit.mjs +1 -1
- package/dist/formatters/jacoco.d.mts +2 -2
- package/dist/formatters/jacoco.mjs +83 -22
- package/dist/formatters/jacoco.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{jacoco-DvrlvSby.d.mts → jacoco-BNRO5NVU.d.mts} +6 -3
- package/dist/jacoco-BNRO5NVU.d.mts.map +1 -0
- package/package.json +5 -5
- package/dist/aunit-IDVjCJSy.mjs.map +0 -1
- package/dist/jacoco-DvrlvSby.d.mts.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { toJacocoXml, toSonarGenericCoverageXml } from "./formatters/jacoco.mjs";
|
|
2
|
-
import { extractCoverageMeasurementId } from "@abapify/adt-contracts";
|
|
1
|
+
import { createAbapGitCoverageSourceResolver, toJacocoXml, toSonarGenericCoverageXml } from "./formatters/jacoco.mjs";
|
|
2
|
+
import { buildCoverageQuery, buildStatementsBulkRequest, extractCoverageMeasurementId, extractCoverageStatementUris } from "@abapify/adt-contracts";
|
|
3
3
|
import { writeFile } from "node:fs/promises";
|
|
4
4
|
import { writeFileSync } from "node:fs";
|
|
5
5
|
//#region src/formatters/junit.ts
|
|
@@ -90,7 +90,8 @@ function toJunitXml(result) {
|
|
|
90
90
|
* Write JUnit XML report to file
|
|
91
91
|
*/
|
|
92
92
|
async function outputJunitReport(result, outputFile) {
|
|
93
|
-
|
|
93
|
+
const xml = toJunitXml(result);
|
|
94
|
+
await writeFile(outputFile, xml, "utf-8");
|
|
94
95
|
console.log(`\n📄 JUnit XML report written to: ${outputFile}`);
|
|
95
96
|
console.log(`📊 ${result.totalTests} tests (${result.passCount} passed, ${result.failCount} failed, ${result.errorCount} errors)`);
|
|
96
97
|
}
|
|
@@ -170,7 +171,8 @@ function toSonarXml(result) {
|
|
|
170
171
|
* Write Sonar Generic Execution XML report to file
|
|
171
172
|
*/
|
|
172
173
|
function outputSonarReport(result, filePath) {
|
|
173
|
-
|
|
174
|
+
const xml = toSonarXml(result);
|
|
175
|
+
writeFileSync(filePath, xml, "utf-8");
|
|
174
176
|
}
|
|
175
177
|
//#endregion
|
|
176
178
|
//#region src/commands/aunit.ts
|
|
@@ -181,6 +183,18 @@ const ansi = {
|
|
|
181
183
|
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
182
184
|
dim: (s) => `\x1b[2m${s}\x1b[0m`
|
|
183
185
|
};
|
|
186
|
+
async function fetchCoverageData(coverage, measurementId, targetUris) {
|
|
187
|
+
const measurements = await coverage.measurements.post(measurementId, buildCoverageQuery(targetUris));
|
|
188
|
+
const statementUris = extractCoverageStatementUris(measurements);
|
|
189
|
+
if (statementUris.length === 0) return {
|
|
190
|
+
measurements,
|
|
191
|
+
statements: void 0
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
measurements,
|
|
195
|
+
statements: await coverage.statements.post(measurementId, buildStatementsBulkRequest(statementUris))
|
|
196
|
+
};
|
|
197
|
+
}
|
|
184
198
|
/**
|
|
185
199
|
* Build the default runConfiguration request body.
|
|
186
200
|
* Set `coverage` to enable the `<external><coverage active="true"/></external>`
|
|
@@ -501,15 +515,17 @@ const aunitCommand = {
|
|
|
501
515
|
else if (!client.adt.runtime) ctx.logger.warn?.("⚠️ Coverage link present but the runtime/traces contract is not available on this client.");
|
|
502
516
|
else try {
|
|
503
517
|
const cov = client.adt.runtime.traces.coverage;
|
|
504
|
-
const measurements = await cov
|
|
505
|
-
const statements = await cov.statements.get(measurementId);
|
|
518
|
+
const { measurements, statements } = await fetchCoverageData(cov, measurementId, targetUris);
|
|
506
519
|
const format = options.coverageFormat ?? "jacoco";
|
|
520
|
+
const sourcePathResolver = createAbapGitCoverageSourceResolver();
|
|
507
521
|
const xml = format === "sonar-generic" ? toSonarGenericCoverageXml({
|
|
508
522
|
measurements,
|
|
509
|
-
statements
|
|
523
|
+
statements,
|
|
524
|
+
sourcePathResolver
|
|
510
525
|
}) : toJacocoXml({
|
|
511
526
|
measurements,
|
|
512
|
-
statements
|
|
527
|
+
statements,
|
|
528
|
+
sourcePathResolver
|
|
513
529
|
});
|
|
514
530
|
if (options.coverageOutput) {
|
|
515
531
|
const { writeFileSync } = await import("node:fs");
|
|
@@ -527,4 +543,4 @@ const aunitCommand = {
|
|
|
527
543
|
//#endregion
|
|
528
544
|
export { outputJunitReport as n, toJunitXml as r, aunitCommand as t };
|
|
529
545
|
|
|
530
|
-
//# sourceMappingURL=aunit-
|
|
546
|
+
//# sourceMappingURL=aunit-9mhc0LYi.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aunit-9mhc0LYi.mjs","names":["escapeXml"],"sources":["../src/formatters/junit.ts","../src/formatters/sonar.ts","../src/commands/aunit.ts"],"sourcesContent":["/**\n * JUnit XML Formatter\n *\n * Outputs AUnit results in JUnit XML format compatible with\n * GitLab CI unit_test_reports.\n *\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n *\n * GitLab parses:\n * - testsuites > testsuite (name, time)\n * - testsuite > testcase (classname, name, file, time)\n * - testcase > failure, error, skipped\n * - testcase > system-out, system-err\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AunitResult, AunitTestMethod } from '../types';\n\n/**\n * Escape XML special characters\n */\nfunction escapeXml(str: string): string {\n return str\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n}\n\n/**\n * Build failure/error message from alerts\n */\nfunction buildAlertMessage(method: AunitTestMethod): string {\n return method.alerts\n .map((alert) => {\n const lines: string[] = [];\n lines.push(alert.title);\n for (const detail of alert.details) {\n lines.push(` ${detail}`);\n }\n if (alert.stack.length > 0) {\n lines.push('Stack:');\n for (const entry of alert.stack) {\n const desc = entry.description || entry.name || '';\n const uriPart = entry.uri ? ` (${entry.uri})` : '';\n lines.push(` at ${desc}${uriPart}`);\n }\n }\n return lines.join('\\n');\n })\n .join('\\n---\\n');\n}\n\n/**\n * Build testcase XML lines for a single test method\n */\nfunction buildTestCaseXml(\n method: AunitTestMethod,\n classname: string,\n): string[] {\n const caseLines: string[] = [\n ` <testcase classname=\"${escapeXml(classname)}\" name=\"${escapeXml(method.name)}\" time=\"${method.executionTime.toFixed(3)}\">`,\n ];\n\n if (method.status === 'fail') {\n const message = method.alerts[0]?.title || 'Assertion failed';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <failure message=\"${escapeXml(message)}\">${escapeXml(body)}</failure>`,\n );\n } else if (method.status === 'error') {\n const message = method.alerts[0]?.title || 'Error';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <error message=\"${escapeXml(message)}\">${escapeXml(body)}</error>`,\n );\n } else if (method.status === 'skip') {\n caseLines.push(' <skipped/>');\n }\n\n if (method.uri) {\n caseLines.push(` <system-out>${escapeXml(method.uri)}</system-out>`);\n }\n\n caseLines.push(' </testcase>');\n return caseLines;\n}\n\n/**\n * Convert AUnit result to JUnit XML string\n */\nexport function toJunitXml(result: AunitResult): string {\n // Collect all test suites (one per test class)\n const suites: string[] = [];\n\n for (const program of result.programs) {\n for (const testClass of program.testClasses) {\n const tests = testClass.methods.length;\n const failures = testClass.methods.filter(\n (m) => m.status === 'fail',\n ).length;\n const errors = testClass.methods.filter(\n (m) => m.status === 'error',\n ).length;\n const skipped = testClass.methods.filter(\n (m) => m.status === 'skip',\n ).length;\n const time = testClass.methods.reduce(\n (sum, m) => sum + m.executionTime,\n 0,\n );\n\n const suiteLines: string[] = [\n ` <testsuite name=\"${escapeXml(program.name)}.${escapeXml(testClass.name)}\" tests=\"${tests}\" failures=\"${failures}\" errors=\"${errors}\" skipped=\"${skipped}\" time=\"${time.toFixed(3)}\">`,\n ];\n\n for (const method of testClass.methods) {\n const classname = `${program.name}.${testClass.name}`;\n suiteLines.push(...buildTestCaseXml(method, classname));\n }\n\n suiteLines.push(' </testsuite>');\n suites.push(suiteLines.join('\\n'));\n }\n }\n\n const header = `<testsuites tests=\"${result.totalTests}\" failures=\"${result.failCount}\" errors=\"${result.errorCount}\" skipped=\"${result.skipCount}\" time=\"${result.totalTime.toFixed(3)}\">`;\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n header,\n ...suites,\n '</testsuites>',\n ].join('\\n');\n}\n\n/**\n * Write JUnit XML report to file\n */\nexport async function outputJunitReport(\n result: AunitResult,\n outputFile: string,\n): Promise<void> {\n const xml = toJunitXml(result);\n await writeFile(outputFile, xml, 'utf-8');\n console.log(`\\n📄 JUnit XML report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalTests} tests (${result.passCount} passed, ${result.failCount} failed, ${result.errorCount} errors)`,\n );\n}\n","/**\n * AUnit Sonar Generic Execution Format Formatter\n *\n * Converts AUnit test results to Sonar Generic Execution format.\n * @see https://docs.sonarsource.com/sonarqube/latest/analyzing-source-code/test-coverage/generic-test-data/#generic-execution\n *\n * Output structure:\n * <testExecutions version=\"1\">\n * <file path=\"...\">\n * <testCase name=\"...\" duration=\"...\" />\n * <testCase name=\"...\">\n * <failure message=\"...\">stack trace</failure>\n * </testCase>\n * </file>\n * </testExecutions>\n */\n\nimport { writeFileSync } from 'node:fs';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n} from '../types';\n\nfunction escapeXml(s: string): string {\n return s\n .replaceAll(/&/g, '&')\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>')\n .replaceAll(/\"/g, '"')\n .replaceAll(/'/g, ''');\n}\n\n/**\n * Build a file path for the test object.\n * Mirrors sapcli behaviour: uses the object type and name as a pseudo-path\n * so that Sonar can link findings to source files in a checkout.\n */\nfunction objectPath(program: AunitProgram): string {\n const type = (program.type ?? '').replace('/', '_');\n const name = program.name.toLowerCase();\n if (type.startsWith('CLAS')) {\n return `src/${name}.clas.abap`;\n }\n if (type.startsWith('PROG')) {\n return `src/${name}.prog.abap`;\n }\n if (type.startsWith('INTF')) {\n return `src/${name}.intf.abap`;\n }\n return `src/${name}.abap`;\n}\n\nfunction methodElement(method: AunitTestMethod, cls: AunitTestClass): string {\n const durationMs = Math.round(method.executionTime * 1000);\n const testName = escapeXml(`${cls.name}.${method.name}`);\n\n if (method.status === 'pass' || method.status === 'skip') {\n return ` <testCase name=\"${testName}\" duration=\"${durationMs}\" />`;\n }\n\n const kind = method.status === 'fail' ? 'failure' : 'error';\n const messages = method.alerts\n .map((a) => {\n const detail = [a.title, ...a.details].join('\\n');\n return detail;\n })\n .join('\\n---\\n');\n\n const stack = method.alerts\n .flatMap((a) => a.stack.map((s) => s.description ?? s.name ?? ''))\n .filter(Boolean)\n .join('\\n');\n\n const msgAttr = escapeXml(method.alerts[0]?.title ?? method.status);\n const body = escapeXml([messages, stack].filter(Boolean).join('\\n\\n'));\n\n return [\n ` <testCase name=\"${testName}\" duration=\"${durationMs}\">`,\n ` <${kind} message=\"${msgAttr}\">${body}</${kind}>`,\n ` </testCase>`,\n ].join('\\n');\n}\n\nfunction programElement(program: AunitProgram): string {\n const path = escapeXml(objectPath(program));\n const methods = program.testClasses.flatMap((cls) =>\n cls.methods.map((m) => methodElement(m, cls)),\n );\n if (methods.length === 0) return '';\n return [` <file path=\"${path}\">`, ...methods, ` </file>`].join('\\n');\n}\n\n/**\n * Convert AUnit result to Sonar Generic Execution XML string\n */\nexport function toSonarXml(result: AunitResult): string {\n const files = result.programs.map(programElement).filter(Boolean).join('\\n');\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<testExecutions version=\"1\">',\n files,\n '</testExecutions>',\n ].join('\\n');\n}\n\n/**\n * Write Sonar Generic Execution XML report to file\n */\nexport function outputSonarReport(result: AunitResult, filePath: string): void {\n const xml = toSonarXml(result);\n writeFileSync(filePath, xml, 'utf-8');\n}\n","/**\n * AUnit Command Plugin\n *\n * CLI-agnostic command for running ABAP Unit tests.\n * Uses the CliContext.getAdtClient() factory for API access.\n *\n * Supports JUnit XML output for GitLab CI integration:\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport {\n buildCoverageQuery,\n buildStatementsBulkRequest,\n extractCoverageMeasurementId,\n extractCoverageStatementUris,\n} from '@abapify/adt-contracts';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n// Simple ANSI color helpers (no external dependency)\nconst ansi = {\n red: (s: string) => `\\x1b[31m${s}\\x1b[0m`,\n green: (s: string) => `\\x1b[32m${s}\\x1b[0m`,\n yellow: (s: string) => `\\x1b[33m${s}\\x1b[0m`,\n cyan: (s: string) => `\\x1b[36m${s}\\x1b[0m`,\n dim: (s: string) => `\\x1b[2m${s}\\x1b[0m`,\n};\nimport { outputJunitReport, outputSonarReport } from '../formatters';\nimport {\n createAbapGitCoverageSourceResolver,\n toJacocoXml,\n toSonarGenericCoverageXml,\n} from '../formatters/jacoco';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n AunitAlert,\n OutputFormat,\n} from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.aunit namespace provides the testruns operation\ninterface AdtClient {\n adt: {\n aunit: {\n testruns: {\n post: (body: RunConfigurationBody) => Promise<RunResultResponse>;\n };\n };\n runtime?: {\n traces: {\n coverage: {\n measurements: {\n post: (id: string, body: string) => Promise<AcoverageResultSchema>;\n };\n statements: {\n post: (\n id: string,\n body: string,\n ) => Promise<AcoverageStatementsSchema>;\n };\n };\n };\n };\n };\n}\n\ntype CoverageClient = NonNullable<\n AdtClient['adt']['runtime']\n>['traces']['coverage'];\n\nasync function fetchCoverageData(\n coverage: CoverageClient,\n measurementId: string,\n targetUris: string[],\n): Promise<{\n measurements: AcoverageResultSchema;\n statements: AcoverageStatementsSchema | undefined;\n}> {\n const measurements = await coverage.measurements.post(\n measurementId,\n buildCoverageQuery(targetUris),\n );\n const statementUris = extractCoverageStatementUris(measurements);\n if (statementUris.length === 0)\n return { measurements, statements: undefined };\n const statements = await coverage.statements.post(\n measurementId,\n buildStatementsBulkRequest(statementUris),\n );\n return { measurements, statements };\n}\n\n// Request body shape (matches aunitRun schema)\ninterface RunConfigurationBody {\n runConfiguration: {\n external?: {\n coverage?: { active?: string };\n };\n options?: {\n uriType?: { value?: string };\n testDeterminationStrategy?: {\n sameProgram?: string;\n assignedTests?: string;\n appendAssignedTestsPreview?: string;\n };\n testRiskLevels?: {\n harmless?: string;\n dangerous?: string;\n critical?: string;\n };\n testDurations?: {\n short?: string;\n medium?: string;\n long?: string;\n };\n withNavigationUri?: { enabled?: string };\n };\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences?: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n}\n\n// Response shape (matches aunitResult schema)\ninterface RunResultResponse {\n runResult: {\n program?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n uriType?: string;\n testClasses?: {\n testClass?: Array<{\n uri?: string;\n name?: string;\n uriType?: string;\n durationCategory?: string;\n riskLevel?: string;\n testMethods?: {\n testMethod?: Array<{\n uri?: string;\n name?: string;\n executionTime?: string;\n uriType?: string;\n unit?: string;\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n }>;\n };\n }>;\n };\n}\n\n/**\n * Build the default runConfiguration request body.\n * Set `coverage` to enable the `<external><coverage active=\"true\"/></external>`\n * block – SAP will then include an atom:link to the coverage\n * measurement on the runResult.\n */\nfunction buildRunConfiguration(\n targetUris: string[],\n coverage = false,\n): RunConfigurationBody {\n return {\n runConfiguration: {\n external: {\n coverage: { active: coverage ? 'true' : 'false' },\n },\n options: {\n uriType: { value: 'semantic' },\n testDeterminationStrategy: {\n sameProgram: 'true',\n assignedTests: 'false',\n appendAssignedTestsPreview: 'true',\n },\n testRiskLevels: {\n harmless: 'true',\n dangerous: 'true',\n critical: 'true',\n },\n testDurations: {\n short: 'true',\n medium: 'true',\n long: 'true',\n },\n withNavigationUri: { enabled: 'false' },\n },\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n}\n\n/**\n * Convert a single test method alert to an AunitAlert, updating the status.\n */\nfunction convertAlerts(\n rawAlerts: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: { detail?: Array<{ text?: string }> };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>,\n): { alerts: AunitAlert[]; status: AunitTestMethod['status'] } {\n const alerts: AunitAlert[] = [];\n let status: AunitTestMethod['status'] = 'pass';\n\n for (const alert of rawAlerts) {\n const details = (alert.details?.detail ?? []).map((d) => d.text || '');\n const stack = (alert.stack?.stackEntry ?? []).map((s) => ({\n uri: s.uri,\n type: s.type,\n name: s.name,\n description: s.description,\n }));\n\n alerts.push({\n kind: alert.kind || 'unknown',\n severity: alert.severity || 'unknown',\n title: alert.title || '',\n details,\n stack,\n });\n\n if (alert.severity === 'critical' || alert.kind === 'failedAssertion') {\n status = 'fail';\n } else if (alert.severity === 'fatal' || alert.kind === 'error') {\n status = 'error';\n }\n }\n\n return { alerts, status };\n}\n\n/**\n * Convert SAP AUnit response to our normalized AunitResult\n */\nfunction convertResponse(response: RunResultResponse): AunitResult {\n const programs: AunitProgram[] = [];\n let totalTests = 0;\n let passCount = 0;\n let failCount = 0;\n let errorCount = 0;\n let skipCount = 0;\n let totalTime = 0;\n\n for (const prog of response.runResult.program || []) {\n const testClasses: AunitTestClass[] = [];\n\n for (const tc of prog.testClasses?.testClass || []) {\n const methods: AunitTestMethod[] = [];\n\n for (const tm of tc.testMethods?.testMethod || []) {\n const execTime = Number.parseFloat(tm.executionTime || '0');\n totalTime += execTime;\n\n const { alerts, status } = convertAlerts(tm.alerts?.alert || []);\n\n totalTests++;\n if (status === 'pass') passCount++;\n else if (status === 'fail') failCount++;\n else if (status === 'error') errorCount++;\n else if (status === 'skip') skipCount++;\n\n methods.push({\n name: tm.name || 'UNKNOWN',\n uri: tm.uri,\n executionTime: execTime,\n status,\n alerts,\n });\n }\n\n testClasses.push({\n name: tc.name || 'UNKNOWN',\n uri: tc.uri,\n riskLevel: tc.riskLevel,\n durationCategory: tc.durationCategory,\n methods,\n });\n }\n\n programs.push({\n name: prog.name || 'UNKNOWN',\n type: prog.type,\n uri: prog.uri,\n testClasses,\n });\n }\n\n return {\n programs,\n totalTests,\n passCount,\n failCount,\n errorCount,\n skipCount,\n totalTime,\n };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? ansi.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(ansi.cyan(name), url);\n}\n\n/**\n * Display a single failed/errored test method in console\n */\nfunction displayFailedMethod(method: AunitTestMethod): void {\n const icon = method.status === 'fail' ? ansi.red('✗') : ansi.red('⚠');\n console.log(` ${icon} ${method.name} (${method.executionTime}s)`);\n for (const alert of method.alerts) {\n console.log(` ${ansi.dim(alert.title)}`);\n for (const detail of alert.details) {\n const dimDetail = ansi.dim(` ${detail}`);\n console.log(` ${dimDetail}`);\n }\n }\n}\n\n/**\n * Display AUnit results summary in console\n */\nfunction displaySummary(result: AunitResult): void {\n const allPassed = result.failCount === 0 && result.errorCount === 0;\n const statusIcon = allPassed ? '✅' : '❌';\n console.log(`\\n${statusIcon} ABAP Unit Test Results:`);\n console.log(\n ` 📋 Total: ${result.totalTests} tests in ${result.totalTime.toFixed(3)}s`,\n );\n if (result.passCount > 0) {\n const passLine = `✓ ${result.passCount} passed`;\n console.log(` ${ansi.green(passLine)}`);\n }\n if (result.failCount > 0) {\n const failLine = `✗ ${result.failCount} failed`;\n console.log(` ${ansi.red(failLine)}`);\n }\n if (result.errorCount > 0) {\n const errorLine = `⚠ ${result.errorCount} errors`;\n console.log(` ${ansi.red(errorLine)}`);\n }\n if (result.skipCount > 0) {\n const skipLine = `○ ${result.skipCount} skipped`;\n console.log(` ${ansi.yellow(skipLine)}`);\n }\n}\n\n/**\n * Display AUnit results in console\n */\nfunction displayResults(result: AunitResult, systemName?: string): void {\n if (result.totalTests === 0) {\n console.log(`\\n⚠️ No tests found`);\n return;\n }\n\n displaySummary(result);\n\n // Show failed tests\n for (const prog of result.programs) {\n for (const tc of prog.testClasses) {\n const failedMethods = tc.methods.filter(\n (m) => m.status === 'fail' || m.status === 'error',\n );\n if (failedMethods.length === 0) continue;\n\n const classLink = adtLink(\n `${prog.name} → ${tc.name}`,\n tc.uri || prog.uri || '',\n systemName,\n );\n console.log(`\\n ${classLink}`);\n\n for (const method of failedMethods) {\n displayFailedMethod(method);\n }\n }\n }\n}\n\n/**\n * Resolve target URIs and name from command options\n */\nasync function resolveTargets(options: {\n fromFile?: string;\n transport?: string;\n class?: string;\n package?: string;\n object?: string;\n}): Promise<{ targetUris: string[]; targetName: string }> {\n if (options.fromFile) {\n const { readFileSync } = await import('node:fs');\n const content = readFileSync(options.fromFile, 'utf-8');\n const targetUris = content\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#'));\n return {\n targetUris,\n targetName: `${targetUris.length} objects from ${options.fromFile}`,\n };\n }\n if (options.transport) {\n return {\n targetUris: [\n `/sap/bc/adt/cts/transportrequests/${options.transport.toUpperCase()}`,\n ],\n targetName: `Transport ${options.transport.toUpperCase()}`,\n };\n }\n if (options.class) {\n return {\n targetUris: [`/sap/bc/adt/oo/classes/${options.class.toLowerCase()}`],\n targetName: `Class ${options.class.toUpperCase()}`,\n };\n }\n if (options.package) {\n return {\n targetUris: [`/sap/bc/adt/packages/${options.package.toUpperCase()}`],\n targetName: `Package ${options.package.toUpperCase()}`,\n };\n }\n return { targetUris: [options.object!], targetName: options.object! };\n}\n\n/**\n * AUnit Command Plugin\n */\nexport const aunitCommand: CliCommandPlugin = {\n name: 'aunit',\n description: 'Run ABAP Unit tests',\n\n options: [\n {\n flags: '-p, --package <package>',\n description: 'Run tests on package',\n },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run tests on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-c, --class <name>',\n description: 'Run tests on ABAP class (e.g., ZCL_MY_CLASS)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run tests on transport request (e.g., NPLK900042)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run tests on objects listed in file (one URI per line)',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, junit, sonar',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description:\n 'Output file (required for junit/sonar format, e.g., aunit-report.xml)',\n },\n {\n flags: '--coverage',\n description:\n 'Request ABAP code coverage with the test run and follow the resulting measurement link',\n },\n {\n flags: '--coverage-output <file>',\n description:\n 'Write the coverage report to this file (default: stdout for jacoco/sonar-generic)',\n },\n {\n flags: '--coverage-format <format>',\n description: 'Coverage format: jacoco | sonar-generic',\n default: 'jacoco',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n class?: string;\n transport?: string;\n fromFile?: string;\n format?: OutputFormat;\n output?: string;\n coverage?: boolean;\n coverageOutput?: string;\n coverageFormat?: 'jacoco' | 'sonar-generic';\n };\n\n // Validate: at least one target\n const targetCount = [\n options.package,\n options.object,\n options.class,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --class, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --class, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for junit/sonar format\n if (\n (options.format === 'junit' || options.format === 'sonar') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client\n if (!ctx.getAdtClient) {\n ctx.logger.error('❌ ADT client not available. Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n // Determine targets\n const { targetUris, targetName } = await resolveTargets(options);\n\n if (options.fromFile && targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n\n ctx.logger.info(`🧪 Running ABAP Unit tests on ${targetName}...`);\n\n // Execute test run\n const body = buildRunConfiguration(targetUris, options.coverage === true);\n const response = await client.adt.aunit.testruns.post(body);\n\n // Convert to normalized result\n const result = convertResponse(response);\n\n // Output\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'junit' && options.output) {\n await outputJunitReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else if (options.format === 'sonar' && options.output) {\n outputSonarReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else {\n displayResults(result, ctx.adtSystemName);\n }\n\n // Coverage reporting – only if the user explicitly asked for it.\n if (options.coverage) {\n const measurementId = extractCoverageMeasurementId(response);\n if (!measurementId) {\n ctx.logger.warn?.(\n '⚠️ Coverage requested but SAP returned no measurement link. ' +\n 'The system may not have coverage collection enabled for this user/package, ' +\n 'or additional system configuration is required.',\n );\n } else if (!client.adt.runtime) {\n ctx.logger.warn?.(\n '⚠️ Coverage link present but the runtime/traces contract is not available on this client.',\n );\n } else {\n try {\n const cov = client.adt.runtime.traces.coverage;\n const { measurements, statements } = await fetchCoverageData(\n cov,\n measurementId,\n targetUris,\n );\n const format = options.coverageFormat ?? 'jacoco';\n const sourcePathResolver = createAbapGitCoverageSourceResolver();\n const xml =\n format === 'sonar-generic'\n ? toSonarGenericCoverageXml({\n measurements,\n statements,\n sourcePathResolver,\n })\n : toJacocoXml({\n measurements,\n statements,\n sourcePathResolver,\n });\n if (options.coverageOutput) {\n const { writeFileSync } = await import('node:fs');\n writeFileSync(options.coverageOutput, xml, 'utf-8');\n ctx.logger.info(\n `📊 Coverage report (${format}) written to ${options.coverageOutput}`,\n );\n } else {\n console.log(xml);\n }\n } catch (err) {\n ctx.logger.error(\n `❌ Coverage collection failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n // Exit with non-zero code equal to number of failures+errors (sapcli convention)\n const failures = result.failCount + result.errorCount;\n if (failures > 0) {\n process.exit(failures > 125 ? 125 : failures);\n }\n },\n};\n\nexport default aunitCommand;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aunit.d.mts","names":[],"sources":["../../src/commands/aunit.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"aunit.d.mts","names":[],"sources":["../../src/commands/aunit.ts"],"mappings":";;;;;cAwgBa,cAAc"}
|
package/dist/commands/aunit.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as aunitCommand } from "../aunit-
|
|
1
|
+
import { t as aunitCommand } from "../aunit-9mhc0LYi.mjs";
|
|
2
2
|
export { aunitCommand, aunitCommand as default };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { JacocoInput, outputJacocoReport, outputSonarGenericCoverageReport, toJacocoXml, toSonarGenericCoverageXml };
|
|
1
|
+
import { a as outputSonarGenericCoverageReport, i as outputJacocoReport, n as JacocoInput, o as toJacocoXml, r as createAbapGitCoverageSourceResolver, s as toSonarGenericCoverageXml, t as CoverageSourcePathResolver } from "../jacoco-BNRO5NVU.mjs";
|
|
2
|
+
export { CoverageSourcePathResolver, JacocoInput, createAbapGitCoverageSourceResolver, outputJacocoReport, outputSonarGenericCoverageReport, toJacocoXml, toSonarGenericCoverageXml };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, relative, sep } from "node:path";
|
|
2
3
|
import { adtUriToAbapGitPath } from "@abapify/adt-plugin-abapgit";
|
|
3
4
|
//#region src/formatters/jacoco.ts
|
|
4
5
|
/**
|
|
@@ -6,9 +7,9 @@ import { adtUriToAbapGitPath } from "@abapify/adt-plugin-abapgit";
|
|
|
6
7
|
*
|
|
7
8
|
* Consumes parsed coverage data from the adt-contracts runtime endpoints:
|
|
8
9
|
*
|
|
9
|
-
* measurements: client.adt.runtime.traces.coverage.measurements.post(id)
|
|
10
|
+
* measurements: client.adt.runtime.traces.coverage.measurements.post(id, query)
|
|
10
11
|
* → AcoverageResultSchema (tree of DEVC → CLAS → methods)
|
|
11
|
-
* statements: client.adt.runtime.traces.coverage.statements.
|
|
12
|
+
* statements: client.adt.runtime.traces.coverage.statements.post(id, bulkRequest)
|
|
12
13
|
* → AcoverageStatementsSchema (per-method line hits/misses)
|
|
13
14
|
*
|
|
14
15
|
* Emits JaCoCo 1.1 XML:
|
|
@@ -33,10 +34,10 @@ import { adtUriToAbapGitPath } from "@abapify/adt-plugin-abapgit";
|
|
|
33
34
|
* <counter …/>
|
|
34
35
|
* </report>
|
|
35
36
|
*
|
|
36
|
-
* Improvement over sapcli: we use adtUriToAbapGitPath()
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
37
|
+
* Improvement over sapcli: we use adtUriToAbapGitPath() and an optional
|
|
38
|
+
* repository resolver so JaCoCo package + sourcefile names reconstruct the
|
|
39
|
+
* tracked abapGit path. GitLab can then match changed files for MR diff
|
|
40
|
+
* annotations, while standard JaCoCo consumers retain the same path.
|
|
40
41
|
*/
|
|
41
42
|
const COUNTER_TYPE_MAPPING = {
|
|
42
43
|
branch: "BRANCH",
|
|
@@ -49,6 +50,44 @@ function escapeAttr(s) {
|
|
|
49
50
|
function indent(level) {
|
|
50
51
|
return " ".repeat(level);
|
|
51
52
|
}
|
|
53
|
+
function toPosix(filePath) {
|
|
54
|
+
return sep === "/" ? filePath : filePath.split(sep).join("/");
|
|
55
|
+
}
|
|
56
|
+
function collectAbapSources(root) {
|
|
57
|
+
const sources = [];
|
|
58
|
+
const pending = [root];
|
|
59
|
+
while (pending.length > 0) {
|
|
60
|
+
const directory = pending.pop();
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = readdirSync(directory, { withFileTypes: true });
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const entry of entries) {
|
|
68
|
+
const candidate = join(directory, entry.name);
|
|
69
|
+
if (entry.isDirectory()) pending.push(candidate);
|
|
70
|
+
else if (entry.isFile() && entry.name.endsWith(".abap")) sources.push(candidate);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return sources;
|
|
74
|
+
}
|
|
75
|
+
/** Resolve an abapGit basename to a unique repository-relative source path. */
|
|
76
|
+
function createAbapGitCoverageSourceResolver(sourceRoot = "src", repositoryRoot = process.cwd()) {
|
|
77
|
+
const absoluteSourceRoot = join(repositoryRoot, sourceRoot);
|
|
78
|
+
const byBasename = /* @__PURE__ */ new Map();
|
|
79
|
+
if (existsSync(absoluteSourceRoot)) for (const source of collectAbapSources(absoluteSourceRoot)) {
|
|
80
|
+
const name = basename(source);
|
|
81
|
+
const matches = byBasename.get(name) ?? [];
|
|
82
|
+
matches.push(toPosix(relative(repositoryRoot, source)));
|
|
83
|
+
byBasename.set(name, matches);
|
|
84
|
+
}
|
|
85
|
+
return (reportedPath) => {
|
|
86
|
+
const matches = byBasename.get(basename(reportedPath)) ?? [];
|
|
87
|
+
if (matches.length > 1) throw new Error(`Ambiguous ABAP coverage source ${basename(reportedPath)}: ${matches.join(", ")}`);
|
|
88
|
+
return matches[0] ?? null;
|
|
89
|
+
};
|
|
90
|
+
}
|
|
52
91
|
/**
|
|
53
92
|
* Parse a `#start=L,C` fragment out of an ADT URI. Returns `null`
|
|
54
93
|
* if the URI has no fragment or the fragment is not well-formed.
|
|
@@ -100,16 +139,15 @@ function emitCounters(node, indentLevel, out) {
|
|
|
100
139
|
out.push(`${indent(indentLevel)}<counter type="${jacocoType}" missed="${missed}" covered="${executed}"/>`);
|
|
101
140
|
}
|
|
102
141
|
}
|
|
103
|
-
function
|
|
142
|
+
function sourcefilePathFor(ref, resolver) {
|
|
104
143
|
const uri = ref?.uri;
|
|
105
144
|
const abapgit = uri ? adtUriToAbapGitPath(uri) : null;
|
|
106
|
-
if (abapgit) return abapgit
|
|
145
|
+
if (abapgit) return resolver?.(abapgit) ?? abapgit;
|
|
107
146
|
return (ref?.name ?? "UNKNOWN").toLowerCase();
|
|
108
147
|
}
|
|
109
|
-
function emitClass(classNode, lineMap, indentLevel, out) {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const sourcefile = sourcefileNameFor(ref);
|
|
148
|
+
function emitClass(classNode, sourcePath, lineMap, indentLevel, out) {
|
|
149
|
+
const className = classNode.objectReference?.name ?? "UNKNOWN";
|
|
150
|
+
const sourcefile = basename(sourcePath);
|
|
113
151
|
out.push(`${indent(indentLevel)}<class name="${escapeAttr(className)}" sourcefilename="${escapeAttr(sourcefile)}">`);
|
|
114
152
|
const classLines = [];
|
|
115
153
|
const methods = classNode.nodes?.node ?? [];
|
|
@@ -136,13 +174,35 @@ function emitClass(classNode, lineMap, indentLevel, out) {
|
|
|
136
174
|
out.push(`${indent(indentLevel)}</sourcefile>`);
|
|
137
175
|
}
|
|
138
176
|
}
|
|
139
|
-
function emitPackage(packageNode, lineMap, indentLevel, out) {
|
|
140
|
-
const packageName = packageNode.objectReference?.name ?? "UNKNOWN";
|
|
141
|
-
out.push(`${indent(indentLevel)}<package name="${escapeAttr(packageName)}">`);
|
|
177
|
+
function emitPackage(packageNode, lineMap, resolver, indentLevel, out) {
|
|
142
178
|
const classNodes = packageNode.nodes?.node ?? [];
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
179
|
+
const byDirectory = /* @__PURE__ */ new Map();
|
|
180
|
+
for (const node of classNodes) {
|
|
181
|
+
const sourcePath = sourcefilePathFor(node.objectReference, resolver);
|
|
182
|
+
const directory = dirname(sourcePath);
|
|
183
|
+
const group = byDirectory.get(directory) ?? [];
|
|
184
|
+
group.push({
|
|
185
|
+
node,
|
|
186
|
+
sourcePath
|
|
187
|
+
});
|
|
188
|
+
byDirectory.set(directory, group);
|
|
189
|
+
}
|
|
190
|
+
for (const [directory, classes] of byDirectory) {
|
|
191
|
+
out.push(`${indent(indentLevel)}<package name="${escapeAttr(directory)}">`);
|
|
192
|
+
for (const { node, sourcePath } of classes) emitClass(node, sourcePath, lineMap, indentLevel + 1, out);
|
|
193
|
+
for (const counterType of Object.keys(COUNTER_TYPE_MAPPING)) {
|
|
194
|
+
let total = 0;
|
|
195
|
+
let executed = 0;
|
|
196
|
+
for (const { node } of classes) {
|
|
197
|
+
const counter = node.coverages?.coverage?.find((coverage) => coverage.type === counterType);
|
|
198
|
+
total += counter?.total ?? 0;
|
|
199
|
+
executed += counter?.executed ?? 0;
|
|
200
|
+
}
|
|
201
|
+
if (total === 0 && executed === 0) continue;
|
|
202
|
+
out.push(`${indent(indentLevel + 1)}<counter type="${COUNTER_TYPE_MAPPING[counterType]}" missed="${Math.max(0, total - executed)}" covered="${executed}"/>`);
|
|
203
|
+
}
|
|
204
|
+
out.push(`${indent(indentLevel)}</package>`);
|
|
205
|
+
}
|
|
146
206
|
}
|
|
147
207
|
function toJacocoXml(input) {
|
|
148
208
|
const root = input.measurements?.result;
|
|
@@ -151,7 +211,7 @@ function toJacocoXml(input) {
|
|
|
151
211
|
out.push("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>", "<!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">", `<report name="${escapeAttr(input.reportName ?? "ABAP Coverage")}">`);
|
|
152
212
|
if (root) {
|
|
153
213
|
const packages = root.nodes?.node ?? [];
|
|
154
|
-
for (const pkg of packages) emitPackage(pkg, lineMap, 1, out);
|
|
214
|
+
for (const pkg of packages) emitPackage(pkg, lineMap, input.sourcePathResolver, 1, out);
|
|
155
215
|
emitCounters(root, 1, out);
|
|
156
216
|
}
|
|
157
217
|
out.push("</report>");
|
|
@@ -167,7 +227,8 @@ function toSonarGenericCoverageXml(input) {
|
|
|
167
227
|
function walk(node) {
|
|
168
228
|
const ref = node.objectReference;
|
|
169
229
|
if (ref?.uri && ref.type?.startsWith("CLAS")) {
|
|
170
|
-
const
|
|
230
|
+
const reportedPath = adtUriToAbapGitPath(ref.uri);
|
|
231
|
+
const filePath = reportedPath ? input.sourcePathResolver?.(reportedPath) ?? reportedPath : null;
|
|
171
232
|
if (filePath) {
|
|
172
233
|
const className = ref.name ?? "";
|
|
173
234
|
const methods = node.nodes?.node ?? [];
|
|
@@ -198,6 +259,6 @@ function outputSonarGenericCoverageReport(input, filePath) {
|
|
|
198
259
|
writeFileSync(filePath, toSonarGenericCoverageXml(input), "utf-8");
|
|
199
260
|
}
|
|
200
261
|
//#endregion
|
|
201
|
-
export { outputJacocoReport, outputSonarGenericCoverageReport, toJacocoXml, toSonarGenericCoverageXml };
|
|
262
|
+
export { createAbapGitCoverageSourceResolver, outputJacocoReport, outputSonarGenericCoverageReport, toJacocoXml, toSonarGenericCoverageXml };
|
|
202
263
|
|
|
203
264
|
//# sourceMappingURL=jacoco.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jacoco.mjs","names":[],"sources":["../../src/formatters/jacoco.ts"],"sourcesContent":["/**\n * JaCoCo XML coverage report formatter.\n *\n * Consumes parsed coverage data from the adt-contracts runtime endpoints:\n *\n * measurements: client.adt.runtime.traces.coverage.measurements.post(id)\n * → AcoverageResultSchema (tree of DEVC → CLAS → methods)\n * statements: client.adt.runtime.traces.coverage.statements.get(id)\n * → AcoverageStatementsSchema (per-method line hits/misses)\n *\n * Emits JaCoCo 1.1 XML:\n *\n * <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n * <!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">\n * <report name=\"ABAP Coverage\">\n * <package name=\"TEST_PACKAGE\">\n * <class name=\"CL_FOO\" sourcefilename=\"cl_foo.clas.abap\">\n * <method name=\"METHOD_A\" desc=\"\" line=\"52\">\n * <counter type=\"BRANCH\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"METHOD\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"INSTRUCTION\" missed=\"…\" covered=\"…\"/>\n * </method>\n * <counter type=\"…\" …/>\n * </class>\n * <sourcefile name=\"cl_foo.clas.abap\">\n * <line nr=\"N\" mi=\"0|1\" ci=\"1|0\" mb=\"0\" cb=\"0\"/>\n * </sourcefile>\n * <counter …/>\n * </package>\n * <counter …/>\n * </report>\n *\n * Improvement over sapcli: we use adtUriToAbapGitPath() so the\n * <sourcefile name=…> matches the on-disk abapGit filename (e.g.\n * `cl_foo.clas.abap`), making the report directly consumable by\n * SonarQube.\n */\n\nimport { writeFileSync } from 'node:fs';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\nimport { adtUriToAbapGitPath } from '@abapify/adt-plugin-abapgit';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n\n// ─── Counter mapping ──────────────────────────────────────────────────\n\nconst COUNTER_TYPE_MAPPING: Record<string, string> = {\n branch: 'BRANCH',\n procedure: 'METHOD',\n statement: 'INSTRUCTION',\n};\n\n// ─── Narrow, recursive views of the schema types ──────────────────────\n//\n// The generated types mark deep recursion as `unknown`; we cast once\n// here so the rest of the formatter stays type-safe.\n\ninterface Coverage {\n type?: string;\n total?: number;\n executed?: number;\n}\n\ninterface ObjectRef {\n uri?: string;\n type?: string;\n name?: string;\n}\n\ninterface CoverageNode {\n objectReference?: ObjectRef;\n coverages?: { coverage?: Coverage[] };\n nodes?: { node?: CoverageNode[] };\n}\n\ninterface StatementEntry {\n objectReference?: ObjectRef;\n executed?: number;\n}\n\ninterface StatementResponse {\n name?: string;\n procedure?: StatementEntry[];\n statement?: StatementEntry[];\n}\n\n// ─── XML helpers ──────────────────────────────────────────────────────\n\nfunction escapeAttr(s: string): string {\n return s\n .replaceAll(/&/g, '&')\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>')\n .replaceAll(/\"/g, '"');\n}\n\nfunction indent(level: number): string {\n return ' '.repeat(level);\n}\n\n/**\n * Parse a `#start=L,C` fragment out of an ADT URI. Returns `null`\n * if the URI has no fragment or the fragment is not well-formed.\n */\nfunction parseStartLine(uri: string | undefined): number | null {\n if (!uri) return null;\n const m = /#start=(\\d+)/.exec(uri);\n return m ? Number.parseInt(m[1], 10) : null;\n}\n\n/**\n * For a given statement entry, return the start line encoded in its\n * `adtcore:uri` fragment (1-based). Returns `null` when unavailable.\n */\nfunction statementLine(entry: StatementEntry): number | null {\n return parseStartLine(entry.objectReference?.uri);\n}\n\n// ─── Mapping: (class_name, method_name) → lines ───────────────────────\n\ntype MethodKey = string;\n\nfunction methodKey(cls: string, method: string): MethodKey {\n return `${cls}.${method}`;\n}\n\ninterface LineHit {\n line: number;\n covered: boolean;\n}\n\nfunction buildMethodLinesMapping(\n statements: AcoverageStatementsSchema | undefined,\n): Map<MethodKey, LineHit[]> {\n const result = new Map<MethodKey, LineHit[]>();\n const responses: StatementResponse[] =\n (statements?.statementsBulkResponse\n ?.statementsResponse as unknown as StatementResponse[]) ?? [];\n\n for (const response of responses) {\n const parts = (response.name ?? '').split('.');\n if (parts.length < 2) continue;\n const methodName = parts.at(-1)!;\n const className = parts.at(-2)!;\n const key = methodKey(className, methodName);\n const bucket = result.get(key) ?? [];\n for (const stmt of response.statement ?? []) {\n const line = statementLine(stmt);\n if (line == null) continue;\n bucket.push({ line, covered: (stmt.executed ?? 0) > 0 });\n }\n result.set(key, bucket);\n }\n return result;\n}\n\n// ─── Emit <counter …/> rollups ────────────────────────────────────────\n\nfunction emitCounters(\n node: CoverageNode,\n indentLevel: number,\n out: string[],\n): void {\n for (const c of node.coverages?.coverage ?? []) {\n if (!c.type) continue;\n const jacocoType = COUNTER_TYPE_MAPPING[c.type];\n if (!jacocoType) continue;\n const total = c.total ?? 0;\n const executed = c.executed ?? 0;\n const missed = Math.max(0, total - executed);\n out.push(\n `${indent(indentLevel)}<counter type=\"${jacocoType}\" missed=\"${missed}\" covered=\"${executed}\"/>`,\n );\n }\n}\n\n// ─── Emit <class>, <sourcefile> ───────────────────────────────────────\n\nfunction sourcefileNameFor(ref: ObjectRef | undefined): string {\n const uri = ref?.uri;\n const abapgit = uri ? adtUriToAbapGitPath(uri) : null;\n if (abapgit) {\n // Strip the leading `src/` since JaCoCo `name` is the bare filename;\n // full path can still be reconstructed by consumers.\n return abapgit.replace(/^src\\//, '');\n }\n // Fallback: raw name\n return (ref?.name ?? 'UNKNOWN').toLowerCase();\n}\n\nfunction emitClass(\n classNode: CoverageNode,\n lineMap: Map<MethodKey, LineHit[]>,\n indentLevel: number,\n out: string[],\n): void {\n const ref = classNode.objectReference;\n const className = ref?.name ?? 'UNKNOWN';\n const sourcefile = sourcefileNameFor(ref);\n\n out.push(\n `${indent(indentLevel)}<class name=\"${escapeAttr(className)}\" sourcefilename=\"${escapeAttr(sourcefile)}\">`,\n );\n\n const classLines: LineHit[] = [];\n const methods = classNode.nodes?.node ?? [];\n\n for (const method of methods) {\n const methodRef = method.objectReference;\n const methodName = methodRef?.name ?? 'UNKNOWN';\n const line = parseStartLine(methodRef?.uri) ?? 0;\n\n out.push(\n `${indent(indentLevel + 1)}<method name=\"${escapeAttr(methodName)}\" desc=\"\" line=\"${line}\">`,\n );\n emitCounters(method, indentLevel + 2, out);\n out.push(`${indent(indentLevel + 1)}</method>`);\n\n // Gather line data\n const key = methodKey(className, methodName);\n const hits = lineMap.get(key);\n if (hits) classLines.push(...hits);\n }\n\n emitCounters(classNode, indentLevel + 1, out);\n out.push(`${indent(indentLevel)}</class>`);\n\n if (classLines.length > 0) {\n out.push(\n `${indent(indentLevel)}<sourcefile name=\"${escapeAttr(sourcefile)}\">`,\n );\n for (const { line, covered } of classLines) {\n const ci = covered ? 1 : 0;\n const mi = covered ? 0 : 1;\n out.push(\n `${indent(indentLevel + 1)}<line nr=\"${line}\" mi=\"${mi}\" ci=\"${ci}\" mb=\"0\" cb=\"0\"/>`,\n );\n }\n out.push(`${indent(indentLevel)}</sourcefile>`);\n }\n}\n\n// ─── Emit <package> ───────────────────────────────────────────────────\n\nfunction emitPackage(\n packageNode: CoverageNode,\n lineMap: Map<MethodKey, LineHit[]>,\n indentLevel: number,\n out: string[],\n): void {\n const packageName = packageNode.objectReference?.name ?? 'UNKNOWN';\n out.push(`${indent(indentLevel)}<package name=\"${escapeAttr(packageName)}\">`);\n const classNodes = packageNode.nodes?.node ?? [];\n for (const classNode of classNodes) {\n emitClass(classNode, lineMap, indentLevel + 1, out);\n }\n emitCounters(packageNode, indentLevel + 1, out);\n out.push(`${indent(indentLevel)}</package>`);\n}\n\n// ─── Public API ───────────────────────────────────────────────────────\n\nexport interface JacocoInput {\n measurements: AcoverageResultSchema;\n statements?: AcoverageStatementsSchema;\n reportName?: string;\n}\n\nexport function toJacocoXml(input: JacocoInput): string {\n const root = input.measurements?.result;\n const lineMap = buildMethodLinesMapping(input.statements);\n const out: string[] = [];\n\n out.push(\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>',\n '<!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">',\n `<report name=\"${escapeAttr(input.reportName ?? 'ABAP Coverage')}\">`,\n );\n\n if (root) {\n const packages = (root.nodes?.node as unknown as CoverageNode[]) ?? [];\n for (const pkg of packages) {\n emitPackage(pkg, lineMap, 1, out);\n }\n emitCounters(root as unknown as CoverageNode, 1, out);\n }\n\n out.push('</report>');\n return out.join('\\n');\n}\n\nexport function outputJacocoReport(input: JacocoInput, filePath: string): void {\n writeFileSync(filePath, toJacocoXml(input), 'utf-8');\n}\n\n// ─── Alt: Sonar Generic Coverage format ───────────────────────────────\n//\n// Simpler line-based format:\n// <coverage version=\"1\">\n// <file path=\"src/zcl_foo.clas.abap\">\n// <lineToCover lineNumber=\"42\" covered=\"true\"/>\n// </file>\n// </coverage>\n\nexport function toSonarGenericCoverageXml(input: JacocoInput): string {\n const lineMap = buildMethodLinesMapping(input.statements);\n const root = input.measurements?.result;\n\n // Build file → lines map by walking measurements tree for class refs,\n // then looking up lines via their method keys.\n const fileLines = new Map<string, LineHit[]>();\n\n function walk(node: CoverageNode): void {\n const ref = node.objectReference;\n if (ref?.uri && ref.type?.startsWith('CLAS')) {\n const filePath = adtUriToAbapGitPath(ref.uri);\n if (filePath) {\n const className = ref.name ?? '';\n const methods = node.nodes?.node ?? [];\n for (const m of methods) {\n const methodName = m.objectReference?.name ?? '';\n const lines = lineMap.get(methodKey(className, methodName)) ?? [];\n const bucket = fileLines.get(filePath) ?? [];\n bucket.push(...lines);\n fileLines.set(filePath, bucket);\n }\n }\n }\n for (const child of node.nodes?.node ?? []) walk(child);\n }\n if (root) walk(root as unknown as CoverageNode);\n\n const out: string[] = [];\n out.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>', '<coverage version=\"1\">');\n for (const [file, lines] of fileLines) {\n if (lines.length === 0) continue;\n out.push(` <file path=\"${escapeAttr(file)}\">`);\n for (const { line, covered } of lines) {\n out.push(\n ` <lineToCover lineNumber=\"${line}\" covered=\"${covered ? 'true' : 'false'}\"/>`,\n );\n }\n out.push(' </file>');\n }\n out.push('</coverage>');\n return out.join('\\n');\n}\n\nexport function outputSonarGenericCoverageReport(\n input: JacocoInput,\n filePath: string,\n): void {\n writeFileSync(filePath, toSonarGenericCoverageXml(input), 'utf-8');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"jacoco.mjs","names":[],"sources":["../../src/formatters/jacoco.ts"],"sourcesContent":["/**\n * JaCoCo XML coverage report formatter.\n *\n * Consumes parsed coverage data from the adt-contracts runtime endpoints:\n *\n * measurements: client.adt.runtime.traces.coverage.measurements.post(id, query)\n * → AcoverageResultSchema (tree of DEVC → CLAS → methods)\n * statements: client.adt.runtime.traces.coverage.statements.post(id, bulkRequest)\n * → AcoverageStatementsSchema (per-method line hits/misses)\n *\n * Emits JaCoCo 1.1 XML:\n *\n * <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n * <!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">\n * <report name=\"ABAP Coverage\">\n * <package name=\"TEST_PACKAGE\">\n * <class name=\"CL_FOO\" sourcefilename=\"cl_foo.clas.abap\">\n * <method name=\"METHOD_A\" desc=\"\" line=\"52\">\n * <counter type=\"BRANCH\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"METHOD\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"INSTRUCTION\" missed=\"…\" covered=\"…\"/>\n * </method>\n * <counter type=\"…\" …/>\n * </class>\n * <sourcefile name=\"cl_foo.clas.abap\">\n * <line nr=\"N\" mi=\"0|1\" ci=\"1|0\" mb=\"0\" cb=\"0\"/>\n * </sourcefile>\n * <counter …/>\n * </package>\n * <counter …/>\n * </report>\n *\n * Improvement over sapcli: we use adtUriToAbapGitPath() and an optional\n * repository resolver so JaCoCo package + sourcefile names reconstruct the\n * tracked abapGit path. GitLab can then match changed files for MR diff\n * annotations, while standard JaCoCo consumers retain the same path.\n */\n\nimport { existsSync, readdirSync, writeFileSync, type Dirent } from 'node:fs';\nimport { basename, dirname, join, relative, sep } from 'node:path';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\nimport { adtUriToAbapGitPath } from '@abapify/adt-plugin-abapgit';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n\n// ─── Counter mapping ──────────────────────────────────────────────────\n\nconst COUNTER_TYPE_MAPPING: Record<string, string> = {\n branch: 'BRANCH',\n procedure: 'METHOD',\n statement: 'INSTRUCTION',\n};\n\n// ─── Narrow, recursive views of the schema types ──────────────────────\n//\n// The generated types mark deep recursion as `unknown`; we cast once\n// here so the rest of the formatter stays type-safe.\n\ninterface Coverage {\n type?: string;\n total?: number;\n executed?: number;\n}\n\ninterface ObjectRef {\n uri?: string;\n type?: string;\n name?: string;\n}\n\ninterface CoverageNode {\n objectReference?: ObjectRef;\n coverages?: { coverage?: Coverage[] };\n nodes?: { node?: CoverageNode[] };\n}\n\ninterface StatementEntry {\n objectReference?: ObjectRef;\n executed?: number;\n}\n\ninterface StatementResponse {\n name?: string;\n procedure?: StatementEntry[];\n statement?: StatementEntry[];\n}\n\n// ─── XML helpers ──────────────────────────────────────────────────────\n\nfunction escapeAttr(s: string): string {\n return s\n .replaceAll(/&/g, '&')\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>')\n .replaceAll(/\"/g, '"');\n}\n\nfunction indent(level: number): string {\n return ' '.repeat(level);\n}\n\nfunction toPosix(filePath: string): string {\n return sep === '/' ? filePath : filePath.split(sep).join('/');\n}\n\nfunction collectAbapSources(root: string): string[] {\n const sources: string[] = [];\n const pending = [root];\n while (pending.length > 0) {\n const directory = pending.pop()!;\n let entries: Dirent[];\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n const candidate = join(directory, entry.name);\n if (entry.isDirectory()) pending.push(candidate);\n else if (entry.isFile() && entry.name.endsWith('.abap')) {\n sources.push(candidate);\n }\n }\n }\n return sources;\n}\n\nexport type CoverageSourcePathResolver = (\n reportedPath: string,\n) => string | null;\n\n/** Resolve an abapGit basename to a unique repository-relative source path. */\nexport function createAbapGitCoverageSourceResolver(\n sourceRoot = 'src',\n repositoryRoot = process.cwd(),\n): CoverageSourcePathResolver {\n const absoluteSourceRoot = join(repositoryRoot, sourceRoot);\n const byBasename = new Map<string, string[]>();\n if (existsSync(absoluteSourceRoot)) {\n for (const source of collectAbapSources(absoluteSourceRoot)) {\n const name = basename(source);\n const matches = byBasename.get(name) ?? [];\n matches.push(toPosix(relative(repositoryRoot, source)));\n byBasename.set(name, matches);\n }\n }\n\n return (reportedPath: string): string | null => {\n const matches = byBasename.get(basename(reportedPath)) ?? [];\n if (matches.length > 1) {\n throw new Error(\n `Ambiguous ABAP coverage source ${basename(reportedPath)}: ${matches.join(', ')}`,\n );\n }\n return matches[0] ?? null;\n };\n}\n\n/**\n * Parse a `#start=L,C` fragment out of an ADT URI. Returns `null`\n * if the URI has no fragment or the fragment is not well-formed.\n */\nfunction parseStartLine(uri: string | undefined): number | null {\n if (!uri) return null;\n const m = /#start=(\\d+)/.exec(uri);\n return m ? Number.parseInt(m[1], 10) : null;\n}\n\n/**\n * For a given statement entry, return the start line encoded in its\n * `adtcore:uri` fragment (1-based). Returns `null` when unavailable.\n */\nfunction statementLine(entry: StatementEntry): number | null {\n return parseStartLine(entry.objectReference?.uri);\n}\n\n// ─── Mapping: (class_name, method_name) → lines ───────────────────────\n\ntype MethodKey = string;\n\nfunction methodKey(cls: string, method: string): MethodKey {\n return `${cls}.${method}`;\n}\n\ninterface LineHit {\n line: number;\n covered: boolean;\n}\n\nfunction buildMethodLinesMapping(\n statements: AcoverageStatementsSchema | undefined,\n): Map<MethodKey, LineHit[]> {\n const result = new Map<MethodKey, LineHit[]>();\n const responses: StatementResponse[] =\n (statements?.statementsBulkResponse\n ?.statementsResponse as unknown as StatementResponse[]) ?? [];\n\n for (const response of responses) {\n const parts = (response.name ?? '').split('.');\n if (parts.length < 2) continue;\n const methodName = parts.at(-1)!;\n const className = parts.at(-2)!;\n const key = methodKey(className, methodName);\n const bucket = result.get(key) ?? [];\n for (const stmt of response.statement ?? []) {\n const line = statementLine(stmt);\n if (line == null) continue;\n bucket.push({ line, covered: (stmt.executed ?? 0) > 0 });\n }\n result.set(key, bucket);\n }\n return result;\n}\n\n// ─── Emit <counter …/> rollups ────────────────────────────────────────\n\nfunction emitCounters(\n node: CoverageNode,\n indentLevel: number,\n out: string[],\n): void {\n for (const c of node.coverages?.coverage ?? []) {\n if (!c.type) continue;\n const jacocoType = COUNTER_TYPE_MAPPING[c.type];\n if (!jacocoType) continue;\n const total = c.total ?? 0;\n const executed = c.executed ?? 0;\n const missed = Math.max(0, total - executed);\n out.push(\n `${indent(indentLevel)}<counter type=\"${jacocoType}\" missed=\"${missed}\" covered=\"${executed}\"/>`,\n );\n }\n}\n\n// ─── Emit <class>, <sourcefile> ───────────────────────────────────────\n\nfunction sourcefilePathFor(\n ref: ObjectRef | undefined,\n resolver: CoverageSourcePathResolver | undefined,\n): string {\n const uri = ref?.uri;\n const abapgit = uri ? adtUriToAbapGitPath(uri) : null;\n if (abapgit) {\n return resolver?.(abapgit) ?? abapgit;\n }\n // Fallback: raw name\n return (ref?.name ?? 'UNKNOWN').toLowerCase();\n}\n\nfunction emitClass(\n classNode: CoverageNode,\n sourcePath: string,\n lineMap: Map<MethodKey, LineHit[]>,\n indentLevel: number,\n out: string[],\n): void {\n const ref = classNode.objectReference;\n const className = ref?.name ?? 'UNKNOWN';\n const sourcefile = basename(sourcePath);\n\n out.push(\n `${indent(indentLevel)}<class name=\"${escapeAttr(className)}\" sourcefilename=\"${escapeAttr(sourcefile)}\">`,\n );\n\n const classLines: LineHit[] = [];\n const methods = classNode.nodes?.node ?? [];\n\n for (const method of methods) {\n const methodRef = method.objectReference;\n const methodName = methodRef?.name ?? 'UNKNOWN';\n const line = parseStartLine(methodRef?.uri) ?? 0;\n\n out.push(\n `${indent(indentLevel + 1)}<method name=\"${escapeAttr(methodName)}\" desc=\"\" line=\"${line}\">`,\n );\n emitCounters(method, indentLevel + 2, out);\n out.push(`${indent(indentLevel + 1)}</method>`);\n\n // Gather line data\n const key = methodKey(className, methodName);\n const hits = lineMap.get(key);\n if (hits) classLines.push(...hits);\n }\n\n emitCounters(classNode, indentLevel + 1, out);\n out.push(`${indent(indentLevel)}</class>`);\n\n if (classLines.length > 0) {\n out.push(\n `${indent(indentLevel)}<sourcefile name=\"${escapeAttr(sourcefile)}\">`,\n );\n for (const { line, covered } of classLines) {\n const ci = covered ? 1 : 0;\n const mi = covered ? 0 : 1;\n out.push(\n `${indent(indentLevel + 1)}<line nr=\"${line}\" mi=\"${mi}\" ci=\"${ci}\" mb=\"0\" cb=\"0\"/>`,\n );\n }\n out.push(`${indent(indentLevel)}</sourcefile>`);\n }\n}\n\n// ─── Emit <package> ───────────────────────────────────────────────────\n\nfunction emitPackage(\n packageNode: CoverageNode,\n lineMap: Map<MethodKey, LineHit[]>,\n resolver: CoverageSourcePathResolver | undefined,\n indentLevel: number,\n out: string[],\n): void {\n const classNodes = packageNode.nodes?.node ?? [];\n const byDirectory = new Map<\n string,\n Array<{ node: CoverageNode; sourcePath: string }>\n >();\n for (const node of classNodes) {\n const sourcePath = sourcefilePathFor(node.objectReference, resolver);\n const directory = dirname(sourcePath);\n const group = byDirectory.get(directory) ?? [];\n group.push({ node, sourcePath });\n byDirectory.set(directory, group);\n }\n\n for (const [directory, classes] of byDirectory) {\n out.push(`${indent(indentLevel)}<package name=\"${escapeAttr(directory)}\">`);\n for (const { node, sourcePath } of classes) {\n emitClass(node, sourcePath, lineMap, indentLevel + 1, out);\n }\n // Repository-path resolution can split one SAP package across multiple\n // JaCoCo packages. Roll up the class nodes actually emitted in this\n // directory so each package counter remains consistent with its children.\n for (const counterType of Object.keys(COUNTER_TYPE_MAPPING)) {\n let total = 0;\n let executed = 0;\n for (const { node } of classes) {\n const counter = node.coverages?.coverage?.find(\n (coverage) => coverage.type === counterType,\n );\n total += counter?.total ?? 0;\n executed += counter?.executed ?? 0;\n }\n if (total === 0 && executed === 0) continue;\n out.push(\n `${indent(indentLevel + 1)}<counter type=\"${COUNTER_TYPE_MAPPING[counterType]}\" missed=\"${Math.max(0, total - executed)}\" covered=\"${executed}\"/>`,\n );\n }\n out.push(`${indent(indentLevel)}</package>`);\n }\n}\n\n// ─── Public API ───────────────────────────────────────────────────────\n\nexport interface JacocoInput {\n measurements: AcoverageResultSchema;\n statements?: AcoverageStatementsSchema;\n reportName?: string;\n sourcePathResolver?: CoverageSourcePathResolver;\n}\n\nexport function toJacocoXml(input: JacocoInput): string {\n const root = input.measurements?.result;\n const lineMap = buildMethodLinesMapping(input.statements);\n const out: string[] = [];\n\n out.push(\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>',\n '<!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">',\n `<report name=\"${escapeAttr(input.reportName ?? 'ABAP Coverage')}\">`,\n );\n\n if (root) {\n const packages = (root.nodes?.node as unknown as CoverageNode[]) ?? [];\n for (const pkg of packages) {\n emitPackage(pkg, lineMap, input.sourcePathResolver, 1, out);\n }\n emitCounters(root as unknown as CoverageNode, 1, out);\n }\n\n out.push('</report>');\n return out.join('\\n');\n}\n\nexport function outputJacocoReport(input: JacocoInput, filePath: string): void {\n writeFileSync(filePath, toJacocoXml(input), 'utf-8');\n}\n\n// ─── Alt: Sonar Generic Coverage format ───────────────────────────────\n//\n// Simpler line-based format:\n// <coverage version=\"1\">\n// <file path=\"src/zcl_foo.clas.abap\">\n// <lineToCover lineNumber=\"42\" covered=\"true\"/>\n// </file>\n// </coverage>\n\nexport function toSonarGenericCoverageXml(input: JacocoInput): string {\n const lineMap = buildMethodLinesMapping(input.statements);\n const root = input.measurements?.result;\n\n // Build file → lines map by walking measurements tree for class refs,\n // then looking up lines via their method keys.\n const fileLines = new Map<string, LineHit[]>();\n\n function walk(node: CoverageNode): void {\n const ref = node.objectReference;\n if (ref?.uri && ref.type?.startsWith('CLAS')) {\n const reportedPath = adtUriToAbapGitPath(ref.uri);\n const filePath = reportedPath\n ? (input.sourcePathResolver?.(reportedPath) ?? reportedPath)\n : null;\n if (filePath) {\n const className = ref.name ?? '';\n const methods = node.nodes?.node ?? [];\n for (const m of methods) {\n const methodName = m.objectReference?.name ?? '';\n const lines = lineMap.get(methodKey(className, methodName)) ?? [];\n const bucket = fileLines.get(filePath) ?? [];\n bucket.push(...lines);\n fileLines.set(filePath, bucket);\n }\n }\n }\n for (const child of node.nodes?.node ?? []) walk(child);\n }\n if (root) walk(root as unknown as CoverageNode);\n\n const out: string[] = [];\n out.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>', '<coverage version=\"1\">');\n for (const [file, lines] of fileLines) {\n if (lines.length === 0) continue;\n out.push(` <file path=\"${escapeAttr(file)}\">`);\n for (const { line, covered } of lines) {\n out.push(\n ` <lineToCover lineNumber=\"${line}\" covered=\"${covered ? 'true' : 'false'}\"/>`,\n );\n }\n out.push(' </file>');\n }\n out.push('</coverage>');\n return out.join('\\n');\n}\n\nexport function outputSonarGenericCoverageReport(\n input: JacocoInput,\n filePath: string,\n): void {\n writeFileSync(filePath, toSonarGenericCoverageXml(input), 'utf-8');\n}\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/formatters/junit.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/formatters/junit.ts"],"mappings":";;;;;;;;;;;UASiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,QAAQ;;;;;UAMO;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,SAAS;;;;;UAMM;;EAEf;;EAEA;;EAEA;;EAEA,aAAa;;;;;UAME;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,OAAO;;;;;UAMQ;EACf;EACA;EACA;EACA;;;;;UAMe;;EAEf,UAAU;;EAEV;EACA;EACA;EACA;EACA;;EAEA;;;;;KAMU;;;;;;iBCLI,WAAW,QAAQ;;;;iBA+Cb,kBACpB,QAAQ,aACR,qBACC"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as outputJunitReport, r as toJunitXml, t as aunitCommand } from "./aunit-
|
|
1
|
+
import { n as outputJunitReport, r as toJunitXml, t as aunitCommand } from "./aunit-9mhc0LYi.mjs";
|
|
2
2
|
export { aunitCommand, outputJunitReport, toJunitXml };
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { InferTypedSchema, acoverageResult, acoverageStatements } from "@abapify/adt-schemas";
|
|
2
|
-
|
|
3
2
|
//#region src/formatters/jacoco.d.ts
|
|
4
3
|
type AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;
|
|
5
4
|
type AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;
|
|
5
|
+
type CoverageSourcePathResolver = (reportedPath: string) => string | null;
|
|
6
|
+
/** Resolve an abapGit basename to a unique repository-relative source path. */
|
|
7
|
+
declare function createAbapGitCoverageSourceResolver(sourceRoot?: string, repositoryRoot?: string): CoverageSourcePathResolver;
|
|
6
8
|
interface JacocoInput {
|
|
7
9
|
measurements: AcoverageResultSchema;
|
|
8
10
|
statements?: AcoverageStatementsSchema;
|
|
9
11
|
reportName?: string;
|
|
12
|
+
sourcePathResolver?: CoverageSourcePathResolver;
|
|
10
13
|
}
|
|
11
14
|
declare function toJacocoXml(input: JacocoInput): string;
|
|
12
15
|
declare function outputJacocoReport(input: JacocoInput, filePath: string): void;
|
|
13
16
|
declare function toSonarGenericCoverageXml(input: JacocoInput): string;
|
|
14
17
|
declare function outputSonarGenericCoverageReport(input: JacocoInput, filePath: string): void;
|
|
15
18
|
//#endregion
|
|
16
|
-
export {
|
|
17
|
-
//# sourceMappingURL=jacoco-
|
|
19
|
+
export { outputSonarGenericCoverageReport as a, outputJacocoReport as i, JacocoInput as n, toJacocoXml as o, createAbapGitCoverageSourceResolver as r, toSonarGenericCoverageXml as s, CoverageSourcePathResolver as t };
|
|
20
|
+
//# sourceMappingURL=jacoco-BNRO5NVU.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jacoco-BNRO5NVU.d.mts","names":[],"sources":["../src/formatters/jacoco.ts"],"mappings":";;KA+CK,wBAAwB,wBAAwB;KAChD,4BAA4B,wBAAwB;KAoF7C,8BACV;;iBAIc,oCACd,qBACA,0BACC;UA0Nc;EACf,cAAc;EACd,aAAa;EACb;EACA,qBAAqB;;iBAGP,YAAY,OAAO;iBAuBnB,mBAAmB,OAAO,aAAa;iBAavC,0BAA0B,OAAO;iBA+CjC,iCACd,OAAO,aACP"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abapify/adt-aunit",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "ABAP Unit Test CLI plugin for adt-cli with JUnit XML output for GitLab CI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"gitlab-ci"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@abapify/adt-plugin": "0.4.
|
|
33
|
-
"@abapify/adt-schemas": "0.4.
|
|
34
|
-
"@abapify/adt-contracts": "0.4.
|
|
35
|
-
"@abapify/adt-plugin-abapgit": "0.4.
|
|
32
|
+
"@abapify/adt-plugin": "0.4.4",
|
|
33
|
+
"@abapify/adt-schemas": "0.4.4",
|
|
34
|
+
"@abapify/adt-contracts": "0.4.4",
|
|
35
|
+
"@abapify/adt-plugin-abapgit": "0.4.4"
|
|
36
36
|
},
|
|
37
37
|
"module": "./dist/index.mjs",
|
|
38
38
|
"repository": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"aunit-IDVjCJSy.mjs","names":["escapeXml"],"sources":["../src/formatters/junit.ts","../src/formatters/sonar.ts","../src/commands/aunit.ts"],"sourcesContent":["/**\n * JUnit XML Formatter\n *\n * Outputs AUnit results in JUnit XML format compatible with\n * GitLab CI unit_test_reports.\n *\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n *\n * GitLab parses:\n * - testsuites > testsuite (name, time)\n * - testsuite > testcase (classname, name, file, time)\n * - testcase > failure, error, skipped\n * - testcase > system-out, system-err\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AunitResult, AunitTestMethod } from '../types';\n\n/**\n * Escape XML special characters\n */\nfunction escapeXml(str: string): string {\n return str\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''');\n}\n\n/**\n * Build failure/error message from alerts\n */\nfunction buildAlertMessage(method: AunitTestMethod): string {\n return method.alerts\n .map((alert) => {\n const lines: string[] = [];\n lines.push(alert.title);\n for (const detail of alert.details) {\n lines.push(` ${detail}`);\n }\n if (alert.stack.length > 0) {\n lines.push('Stack:');\n for (const entry of alert.stack) {\n const desc = entry.description || entry.name || '';\n const uriPart = entry.uri ? ` (${entry.uri})` : '';\n lines.push(` at ${desc}${uriPart}`);\n }\n }\n return lines.join('\\n');\n })\n .join('\\n---\\n');\n}\n\n/**\n * Build testcase XML lines for a single test method\n */\nfunction buildTestCaseXml(\n method: AunitTestMethod,\n classname: string,\n): string[] {\n const caseLines: string[] = [\n ` <testcase classname=\"${escapeXml(classname)}\" name=\"${escapeXml(method.name)}\" time=\"${method.executionTime.toFixed(3)}\">`,\n ];\n\n if (method.status === 'fail') {\n const message = method.alerts[0]?.title || 'Assertion failed';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <failure message=\"${escapeXml(message)}\">${escapeXml(body)}</failure>`,\n );\n } else if (method.status === 'error') {\n const message = method.alerts[0]?.title || 'Error';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <error message=\"${escapeXml(message)}\">${escapeXml(body)}</error>`,\n );\n } else if (method.status === 'skip') {\n caseLines.push(' <skipped/>');\n }\n\n if (method.uri) {\n caseLines.push(` <system-out>${escapeXml(method.uri)}</system-out>`);\n }\n\n caseLines.push(' </testcase>');\n return caseLines;\n}\n\n/**\n * Convert AUnit result to JUnit XML string\n */\nexport function toJunitXml(result: AunitResult): string {\n // Collect all test suites (one per test class)\n const suites: string[] = [];\n\n for (const program of result.programs) {\n for (const testClass of program.testClasses) {\n const tests = testClass.methods.length;\n const failures = testClass.methods.filter(\n (m) => m.status === 'fail',\n ).length;\n const errors = testClass.methods.filter(\n (m) => m.status === 'error',\n ).length;\n const skipped = testClass.methods.filter(\n (m) => m.status === 'skip',\n ).length;\n const time = testClass.methods.reduce(\n (sum, m) => sum + m.executionTime,\n 0,\n );\n\n const suiteLines: string[] = [\n ` <testsuite name=\"${escapeXml(program.name)}.${escapeXml(testClass.name)}\" tests=\"${tests}\" failures=\"${failures}\" errors=\"${errors}\" skipped=\"${skipped}\" time=\"${time.toFixed(3)}\">`,\n ];\n\n for (const method of testClass.methods) {\n const classname = `${program.name}.${testClass.name}`;\n suiteLines.push(...buildTestCaseXml(method, classname));\n }\n\n suiteLines.push(' </testsuite>');\n suites.push(suiteLines.join('\\n'));\n }\n }\n\n const header = `<testsuites tests=\"${result.totalTests}\" failures=\"${result.failCount}\" errors=\"${result.errorCount}\" skipped=\"${result.skipCount}\" time=\"${result.totalTime.toFixed(3)}\">`;\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n header,\n ...suites,\n '</testsuites>',\n ].join('\\n');\n}\n\n/**\n * Write JUnit XML report to file\n */\nexport async function outputJunitReport(\n result: AunitResult,\n outputFile: string,\n): Promise<void> {\n const xml = toJunitXml(result);\n await writeFile(outputFile, xml, 'utf-8');\n console.log(`\\n📄 JUnit XML report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalTests} tests (${result.passCount} passed, ${result.failCount} failed, ${result.errorCount} errors)`,\n );\n}\n","/**\n * AUnit Sonar Generic Execution Format Formatter\n *\n * Converts AUnit test results to Sonar Generic Execution format.\n * @see https://docs.sonarsource.com/sonarqube/latest/analyzing-source-code/test-coverage/generic-test-data/#generic-execution\n *\n * Output structure:\n * <testExecutions version=\"1\">\n * <file path=\"...\">\n * <testCase name=\"...\" duration=\"...\" />\n * <testCase name=\"...\">\n * <failure message=\"...\">stack trace</failure>\n * </testCase>\n * </file>\n * </testExecutions>\n */\n\nimport { writeFileSync } from 'node:fs';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n} from '../types';\n\nfunction escapeXml(s: string): string {\n return s\n .replaceAll(/&/g, '&')\n .replaceAll(/</g, '<')\n .replaceAll(/>/g, '>')\n .replaceAll(/\"/g, '"')\n .replaceAll(/'/g, ''');\n}\n\n/**\n * Build a file path for the test object.\n * Mirrors sapcli behaviour: uses the object type and name as a pseudo-path\n * so that Sonar can link findings to source files in a checkout.\n */\nfunction objectPath(program: AunitProgram): string {\n const type = (program.type ?? '').replace('/', '_');\n const name = program.name.toLowerCase();\n if (type.startsWith('CLAS')) {\n return `src/${name}.clas.abap`;\n }\n if (type.startsWith('PROG')) {\n return `src/${name}.prog.abap`;\n }\n if (type.startsWith('INTF')) {\n return `src/${name}.intf.abap`;\n }\n return `src/${name}.abap`;\n}\n\nfunction methodElement(method: AunitTestMethod, cls: AunitTestClass): string {\n const durationMs = Math.round(method.executionTime * 1000);\n const testName = escapeXml(`${cls.name}.${method.name}`);\n\n if (method.status === 'pass' || method.status === 'skip') {\n return ` <testCase name=\"${testName}\" duration=\"${durationMs}\" />`;\n }\n\n const kind = method.status === 'fail' ? 'failure' : 'error';\n const messages = method.alerts\n .map((a) => {\n const detail = [a.title, ...a.details].join('\\n');\n return detail;\n })\n .join('\\n---\\n');\n\n const stack = method.alerts\n .flatMap((a) => a.stack.map((s) => s.description ?? s.name ?? ''))\n .filter(Boolean)\n .join('\\n');\n\n const msgAttr = escapeXml(method.alerts[0]?.title ?? method.status);\n const body = escapeXml([messages, stack].filter(Boolean).join('\\n\\n'));\n\n return [\n ` <testCase name=\"${testName}\" duration=\"${durationMs}\">`,\n ` <${kind} message=\"${msgAttr}\">${body}</${kind}>`,\n ` </testCase>`,\n ].join('\\n');\n}\n\nfunction programElement(program: AunitProgram): string {\n const path = escapeXml(objectPath(program));\n const methods = program.testClasses.flatMap((cls) =>\n cls.methods.map((m) => methodElement(m, cls)),\n );\n if (methods.length === 0) return '';\n return [` <file path=\"${path}\">`, ...methods, ` </file>`].join('\\n');\n}\n\n/**\n * Convert AUnit result to Sonar Generic Execution XML string\n */\nexport function toSonarXml(result: AunitResult): string {\n const files = result.programs.map(programElement).filter(Boolean).join('\\n');\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<testExecutions version=\"1\">',\n files,\n '</testExecutions>',\n ].join('\\n');\n}\n\n/**\n * Write Sonar Generic Execution XML report to file\n */\nexport function outputSonarReport(result: AunitResult, filePath: string): void {\n const xml = toSonarXml(result);\n writeFileSync(filePath, xml, 'utf-8');\n}\n","/**\n * AUnit Command Plugin\n *\n * CLI-agnostic command for running ABAP Unit tests.\n * Uses the CliContext.getAdtClient() factory for API access.\n *\n * Supports JUnit XML output for GitLab CI integration:\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport { extractCoverageMeasurementId } from '@abapify/adt-contracts';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n// Simple ANSI color helpers (no external dependency)\nconst ansi = {\n red: (s: string) => `\\x1b[31m${s}\\x1b[0m`,\n green: (s: string) => `\\x1b[32m${s}\\x1b[0m`,\n yellow: (s: string) => `\\x1b[33m${s}\\x1b[0m`,\n cyan: (s: string) => `\\x1b[36m${s}\\x1b[0m`,\n dim: (s: string) => `\\x1b[2m${s}\\x1b[0m`,\n};\nimport { outputJunitReport, outputSonarReport } from '../formatters';\nimport { toJacocoXml, toSonarGenericCoverageXml } from '../formatters/jacoco';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n AunitAlert,\n OutputFormat,\n} from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.aunit namespace provides the testruns operation\ninterface AdtClient {\n adt: {\n aunit: {\n testruns: {\n post: (body: RunConfigurationBody) => Promise<RunResultResponse>;\n };\n };\n runtime?: {\n traces: {\n coverage: {\n measurements: {\n post: (id: string) => Promise<AcoverageResultSchema>;\n };\n statements: {\n get: (id: string) => Promise<AcoverageStatementsSchema>;\n };\n };\n };\n };\n };\n}\n\n// Request body shape (matches aunitRun schema)\ninterface RunConfigurationBody {\n runConfiguration: {\n external?: {\n coverage?: { active?: string };\n };\n options?: {\n uriType?: { value?: string };\n testDeterminationStrategy?: {\n sameProgram?: string;\n assignedTests?: string;\n appendAssignedTestsPreview?: string;\n };\n testRiskLevels?: {\n harmless?: string;\n dangerous?: string;\n critical?: string;\n };\n testDurations?: {\n short?: string;\n medium?: string;\n long?: string;\n };\n withNavigationUri?: { enabled?: string };\n };\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences?: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n}\n\n// Response shape (matches aunitResult schema)\ninterface RunResultResponse {\n runResult: {\n program?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n uriType?: string;\n testClasses?: {\n testClass?: Array<{\n uri?: string;\n name?: string;\n uriType?: string;\n durationCategory?: string;\n riskLevel?: string;\n testMethods?: {\n testMethod?: Array<{\n uri?: string;\n name?: string;\n executionTime?: string;\n uriType?: string;\n unit?: string;\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n }>;\n };\n }>;\n };\n}\n\n/**\n * Build the default runConfiguration request body.\n * Set `coverage` to enable the `<external><coverage active=\"true\"/></external>`\n * block – SAP will then include an atom:link to the coverage\n * measurement on the runResult.\n */\nfunction buildRunConfiguration(\n targetUris: string[],\n coverage = false,\n): RunConfigurationBody {\n return {\n runConfiguration: {\n external: {\n coverage: { active: coverage ? 'true' : 'false' },\n },\n options: {\n uriType: { value: 'semantic' },\n testDeterminationStrategy: {\n sameProgram: 'true',\n assignedTests: 'false',\n appendAssignedTestsPreview: 'true',\n },\n testRiskLevels: {\n harmless: 'true',\n dangerous: 'true',\n critical: 'true',\n },\n testDurations: {\n short: 'true',\n medium: 'true',\n long: 'true',\n },\n withNavigationUri: { enabled: 'false' },\n },\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n}\n\n/**\n * Convert a single test method alert to an AunitAlert, updating the status.\n */\nfunction convertAlerts(\n rawAlerts: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: { detail?: Array<{ text?: string }> };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>,\n): { alerts: AunitAlert[]; status: AunitTestMethod['status'] } {\n const alerts: AunitAlert[] = [];\n let status: AunitTestMethod['status'] = 'pass';\n\n for (const alert of rawAlerts) {\n const details = (alert.details?.detail ?? []).map((d) => d.text || '');\n const stack = (alert.stack?.stackEntry ?? []).map((s) => ({\n uri: s.uri,\n type: s.type,\n name: s.name,\n description: s.description,\n }));\n\n alerts.push({\n kind: alert.kind || 'unknown',\n severity: alert.severity || 'unknown',\n title: alert.title || '',\n details,\n stack,\n });\n\n if (alert.severity === 'critical' || alert.kind === 'failedAssertion') {\n status = 'fail';\n } else if (alert.severity === 'fatal' || alert.kind === 'error') {\n status = 'error';\n }\n }\n\n return { alerts, status };\n}\n\n/**\n * Convert SAP AUnit response to our normalized AunitResult\n */\nfunction convertResponse(response: RunResultResponse): AunitResult {\n const programs: AunitProgram[] = [];\n let totalTests = 0;\n let passCount = 0;\n let failCount = 0;\n let errorCount = 0;\n let skipCount = 0;\n let totalTime = 0;\n\n for (const prog of response.runResult.program || []) {\n const testClasses: AunitTestClass[] = [];\n\n for (const tc of prog.testClasses?.testClass || []) {\n const methods: AunitTestMethod[] = [];\n\n for (const tm of tc.testMethods?.testMethod || []) {\n const execTime = Number.parseFloat(tm.executionTime || '0');\n totalTime += execTime;\n\n const { alerts, status } = convertAlerts(tm.alerts?.alert || []);\n\n totalTests++;\n if (status === 'pass') passCount++;\n else if (status === 'fail') failCount++;\n else if (status === 'error') errorCount++;\n else if (status === 'skip') skipCount++;\n\n methods.push({\n name: tm.name || 'UNKNOWN',\n uri: tm.uri,\n executionTime: execTime,\n status,\n alerts,\n });\n }\n\n testClasses.push({\n name: tc.name || 'UNKNOWN',\n uri: tc.uri,\n riskLevel: tc.riskLevel,\n durationCategory: tc.durationCategory,\n methods,\n });\n }\n\n programs.push({\n name: prog.name || 'UNKNOWN',\n type: prog.type,\n uri: prog.uri,\n testClasses,\n });\n }\n\n return {\n programs,\n totalTests,\n passCount,\n failCount,\n errorCount,\n skipCount,\n totalTime,\n };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? ansi.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(ansi.cyan(name), url);\n}\n\n/**\n * Display a single failed/errored test method in console\n */\nfunction displayFailedMethod(method: AunitTestMethod): void {\n const icon = method.status === 'fail' ? ansi.red('✗') : ansi.red('⚠');\n console.log(` ${icon} ${method.name} (${method.executionTime}s)`);\n for (const alert of method.alerts) {\n console.log(` ${ansi.dim(alert.title)}`);\n for (const detail of alert.details) {\n const dimDetail = ansi.dim(` ${detail}`);\n console.log(` ${dimDetail}`);\n }\n }\n}\n\n/**\n * Display AUnit results summary in console\n */\nfunction displaySummary(result: AunitResult): void {\n const allPassed = result.failCount === 0 && result.errorCount === 0;\n const statusIcon = allPassed ? '✅' : '❌';\n console.log(`\\n${statusIcon} ABAP Unit Test Results:`);\n console.log(\n ` 📋 Total: ${result.totalTests} tests in ${result.totalTime.toFixed(3)}s`,\n );\n if (result.passCount > 0) {\n const passLine = `✓ ${result.passCount} passed`;\n console.log(` ${ansi.green(passLine)}`);\n }\n if (result.failCount > 0) {\n const failLine = `✗ ${result.failCount} failed`;\n console.log(` ${ansi.red(failLine)}`);\n }\n if (result.errorCount > 0) {\n const errorLine = `⚠ ${result.errorCount} errors`;\n console.log(` ${ansi.red(errorLine)}`);\n }\n if (result.skipCount > 0) {\n const skipLine = `○ ${result.skipCount} skipped`;\n console.log(` ${ansi.yellow(skipLine)}`);\n }\n}\n\n/**\n * Display AUnit results in console\n */\nfunction displayResults(result: AunitResult, systemName?: string): void {\n if (result.totalTests === 0) {\n console.log(`\\n⚠️ No tests found`);\n return;\n }\n\n displaySummary(result);\n\n // Show failed tests\n for (const prog of result.programs) {\n for (const tc of prog.testClasses) {\n const failedMethods = tc.methods.filter(\n (m) => m.status === 'fail' || m.status === 'error',\n );\n if (failedMethods.length === 0) continue;\n\n const classLink = adtLink(\n `${prog.name} → ${tc.name}`,\n tc.uri || prog.uri || '',\n systemName,\n );\n console.log(`\\n ${classLink}`);\n\n for (const method of failedMethods) {\n displayFailedMethod(method);\n }\n }\n }\n}\n\n/**\n * Resolve target URIs and name from command options\n */\nasync function resolveTargets(options: {\n fromFile?: string;\n transport?: string;\n class?: string;\n package?: string;\n object?: string;\n}): Promise<{ targetUris: string[]; targetName: string }> {\n if (options.fromFile) {\n const { readFileSync } = await import('node:fs');\n const content = readFileSync(options.fromFile, 'utf-8');\n const targetUris = content\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#'));\n return {\n targetUris,\n targetName: `${targetUris.length} objects from ${options.fromFile}`,\n };\n }\n if (options.transport) {\n return {\n targetUris: [\n `/sap/bc/adt/cts/transportrequests/${options.transport.toUpperCase()}`,\n ],\n targetName: `Transport ${options.transport.toUpperCase()}`,\n };\n }\n if (options.class) {\n return {\n targetUris: [`/sap/bc/adt/oo/classes/${options.class.toLowerCase()}`],\n targetName: `Class ${options.class.toUpperCase()}`,\n };\n }\n if (options.package) {\n return {\n targetUris: [`/sap/bc/adt/packages/${options.package.toUpperCase()}`],\n targetName: `Package ${options.package.toUpperCase()}`,\n };\n }\n return { targetUris: [options.object!], targetName: options.object! };\n}\n\n/**\n * AUnit Command Plugin\n */\nexport const aunitCommand: CliCommandPlugin = {\n name: 'aunit',\n description: 'Run ABAP Unit tests',\n\n options: [\n {\n flags: '-p, --package <package>',\n description: 'Run tests on package',\n },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run tests on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-c, --class <name>',\n description: 'Run tests on ABAP class (e.g., ZCL_MY_CLASS)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run tests on transport request (e.g., NPLK900042)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run tests on objects listed in file (one URI per line)',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, junit, sonar',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description:\n 'Output file (required for junit/sonar format, e.g., aunit-report.xml)',\n },\n {\n flags: '--coverage',\n description:\n 'Request ABAP code coverage with the test run and follow the resulting measurement link',\n },\n {\n flags: '--coverage-output <file>',\n description:\n 'Write the coverage report to this file (default: stdout for jacoco/sonar-generic)',\n },\n {\n flags: '--coverage-format <format>',\n description: 'Coverage format: jacoco | sonar-generic',\n default: 'jacoco',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n class?: string;\n transport?: string;\n fromFile?: string;\n format?: OutputFormat;\n output?: string;\n coverage?: boolean;\n coverageOutput?: string;\n coverageFormat?: 'jacoco' | 'sonar-generic';\n };\n\n // Validate: at least one target\n const targetCount = [\n options.package,\n options.object,\n options.class,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --class, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --class, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for junit/sonar format\n if (\n (options.format === 'junit' || options.format === 'sonar') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client\n if (!ctx.getAdtClient) {\n ctx.logger.error('❌ ADT client not available. Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n // Determine targets\n const { targetUris, targetName } = await resolveTargets(options);\n\n if (options.fromFile && targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n\n ctx.logger.info(`🧪 Running ABAP Unit tests on ${targetName}...`);\n\n // Execute test run\n const body = buildRunConfiguration(targetUris, options.coverage === true);\n const response = await client.adt.aunit.testruns.post(body);\n\n // Convert to normalized result\n const result = convertResponse(response);\n\n // Output\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'junit' && options.output) {\n await outputJunitReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else if (options.format === 'sonar' && options.output) {\n outputSonarReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else {\n displayResults(result, ctx.adtSystemName);\n }\n\n // Coverage reporting – only if the user explicitly asked for it.\n if (options.coverage) {\n const measurementId = extractCoverageMeasurementId(response);\n if (!measurementId) {\n ctx.logger.warn?.(\n '⚠️ Coverage requested but SAP returned no measurement link. ' +\n 'The system may not have coverage collection enabled for this user/package, ' +\n 'or additional system configuration is required.',\n );\n } else if (!client.adt.runtime) {\n ctx.logger.warn?.(\n '⚠️ Coverage link present but the runtime/traces contract is not available on this client.',\n );\n } else {\n try {\n const cov = client.adt.runtime.traces.coverage;\n const measurements = await cov.measurements.post(measurementId);\n const statements = await cov.statements.get(measurementId);\n const format = options.coverageFormat ?? 'jacoco';\n const xml =\n format === 'sonar-generic'\n ? toSonarGenericCoverageXml({ measurements, statements })\n : toJacocoXml({ measurements, statements });\n if (options.coverageOutput) {\n const { writeFileSync } = await import('node:fs');\n writeFileSync(options.coverageOutput, xml, 'utf-8');\n ctx.logger.info(\n `📊 Coverage report (${format}) written to ${options.coverageOutput}`,\n );\n } else {\n console.log(xml);\n }\n } catch (err) {\n ctx.logger.error(\n `❌ Coverage collection failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n // Exit with non-zero code equal to number of failures+errors (sapcli convention)\n const failures = result.failCount + result.errorCount;\n if (failures > 0) {\n process.exit(failures > 125 ? 125 : failures);\n }\n },\n};\n\nexport default aunitCommand;\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jacoco-DvrlvSby.d.mts","names":[],"sources":["../src/formatters/jacoco.ts"],"mappings":";;;KA8CK,qBAAA,GAAwB,gBAAA,QAAwB,eAAA;AAAA,KAChD,yBAAA,GAA4B,gBAAA,QAAwB,mBAAA;AAAA,UA4NxC,WAAA;EACf,YAAA,EAAc,qBAAA;EACd,UAAA,GAAa,yBAAA;EACb,UAAA;AAAA;AAAA,iBAGc,WAAA,CAAY,KAAA,EAAO,WAAA;AAAA,iBAuBnB,kBAAA,CAAmB,KAAA,EAAO,WAAA,EAAa,QAAA;AAAA,iBAavC,yBAAA,CAA0B,KAAA,EAAO,WAAA;AAAA,iBA4CjC,gCAAA,CACd,KAAA,EAAO,WAAA,EACP,QAAA"}
|