mirror of
https://github.com/dorny/test-reporter.git
synced 2026-02-01 02:45:22 -08:00
Include tests for parsing files names and line numbers in the PhpunitJunitParser
Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
23
__tests__/fixtures/phpunit/phpunit-paths.xml
Normal file
23
__tests__/fixtures/phpunit/phpunit-paths.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="SampleSuite" tests="6" failures="6" time="0.006">
|
||||||
|
<testcase name="testFailure" classname="SampleSuite" file="/home/runner/work/repo/src/Fake.php" line="42" time="0.001">
|
||||||
|
<failure type="Exception" message="Boom">/home/runner/work/repo/src/Fake.php:42</failure>
|
||||||
|
</testcase>
|
||||||
|
<testcase name="testStringFailure" classname="SampleSuite" file="/home/runner/work/repo/src/Other.php" line="10" time="0.001">
|
||||||
|
<failure>/home/runner/work/repo/src/Other.php:10</failure>
|
||||||
|
</testcase>
|
||||||
|
<testcase name="testParenFailure" classname="SampleSuite" file="/home/runner/work/repo/src/Paren.php" line="123" time="0.001">
|
||||||
|
<failure>at /home/runner/work/repo/src/Paren.php(123)</failure>
|
||||||
|
</testcase>
|
||||||
|
<testcase name="testWindowsFailure" classname="SampleSuite" file="C:\repo\src\Win.php" line="77" time="0.001">
|
||||||
|
<failure>C:\repo\src\Win.php:77</failure>
|
||||||
|
</testcase>
|
||||||
|
<testcase name="testWindowsParenFailure" classname="SampleSuite" file="C:\repo\src\WinParen.php" line="88" time="0.001">
|
||||||
|
<failure>at C:\repo\src\WinParen.php(88)</failure>
|
||||||
|
</testcase>
|
||||||
|
<testcase name="testPhptFailure" classname="SampleSuite" file="/home/runner/work/repo/tests/Sample.phpt" line="12" time="0.001">
|
||||||
|
<failure>/home/runner/work/repo/tests/Sample.phpt:12</failure>
|
||||||
|
</testcase>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
@@ -100,6 +100,67 @@ describe('phpunit-junit tests', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('maps absolute paths to tracked files for annotations', async () => {
|
||||||
|
const fixturePath = path.join(__dirname, 'fixtures', 'phpunit', 'phpunit-paths.xml')
|
||||||
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})
|
||||||
|
|
||||||
|
const opts: ParseOptions = {
|
||||||
|
parseErrors: true,
|
||||||
|
trackedFiles: [
|
||||||
|
'src/Fake.php',
|
||||||
|
'src/Other.php',
|
||||||
|
'src/Paren.php',
|
||||||
|
'src/Win.php',
|
||||||
|
'src/WinParen.php',
|
||||||
|
'tests/Sample.phpt'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const parser = new PhpunitJunitParser(opts)
|
||||||
|
const result = await parser.parse(filePath, fileContent)
|
||||||
|
|
||||||
|
const suite = result.suites.find(s => s.name === 'SampleSuite')
|
||||||
|
expect(suite).toBeDefined()
|
||||||
|
|
||||||
|
const tests = suite!.groups.flatMap(g => g.tests)
|
||||||
|
const fileFailure = tests.find(t => t.name === 'testFailure')
|
||||||
|
expect(fileFailure).toBeDefined()
|
||||||
|
expect(fileFailure!.error).toBeDefined()
|
||||||
|
expect(fileFailure!.error!.path).toBe('src/Fake.php')
|
||||||
|
expect(fileFailure!.error!.line).toBe(42)
|
||||||
|
|
||||||
|
const stringFailure = tests.find(t => t.name === 'testStringFailure')
|
||||||
|
expect(stringFailure).toBeDefined()
|
||||||
|
expect(stringFailure!.error).toBeDefined()
|
||||||
|
expect(stringFailure!.error!.path).toBe('src/Other.php')
|
||||||
|
expect(stringFailure!.error!.line).toBe(10)
|
||||||
|
|
||||||
|
const parenFailure = tests.find(t => t.name === 'testParenFailure')
|
||||||
|
expect(parenFailure).toBeDefined()
|
||||||
|
expect(parenFailure!.error).toBeDefined()
|
||||||
|
expect(parenFailure!.error!.path).toBe('src/Paren.php')
|
||||||
|
expect(parenFailure!.error!.line).toBe(123)
|
||||||
|
|
||||||
|
const windowsFailure = tests.find(t => t.name === 'testWindowsFailure')
|
||||||
|
expect(windowsFailure).toBeDefined()
|
||||||
|
expect(windowsFailure!.error).toBeDefined()
|
||||||
|
expect(windowsFailure!.error!.path).toBe('src/Win.php')
|
||||||
|
expect(windowsFailure!.error!.line).toBe(77)
|
||||||
|
|
||||||
|
const windowsParenFailure = tests.find(t => t.name === 'testWindowsParenFailure')
|
||||||
|
expect(windowsParenFailure).toBeDefined()
|
||||||
|
expect(windowsParenFailure!.error).toBeDefined()
|
||||||
|
expect(windowsParenFailure!.error!.path).toBe('src/WinParen.php')
|
||||||
|
expect(windowsParenFailure!.error!.line).toBe(88)
|
||||||
|
|
||||||
|
const phptFailure = tests.find(t => t.name === 'testPhptFailure')
|
||||||
|
expect(phptFailure).toBeDefined()
|
||||||
|
expect(phptFailure!.error).toBeDefined()
|
||||||
|
expect(phptFailure!.error!.path).toBe('tests/Sample.phpt')
|
||||||
|
expect(phptFailure!.error!.line).toBe(12)
|
||||||
|
})
|
||||||
|
|
||||||
it('parses junit-basic.xml with nested suites and failure', async () => {
|
it('parses junit-basic.xml with nested suites and failure', async () => {
|
||||||
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'phpunit', 'junit-basic.xml')
|
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'phpunit', 'junit-basic.xml')
|
||||||
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {ParseOptions, TestParser} from '../../test-parser'
|
|||||||
import {parseStringPromise} from 'xml2js'
|
import {parseStringPromise} from 'xml2js'
|
||||||
|
|
||||||
import {PhpunitReport, SingleSuiteReport, TestCase, TestSuite} from './phpunit-junit-types'
|
import {PhpunitReport, SingleSuiteReport, TestCase, TestSuite} from './phpunit-junit-types'
|
||||||
import {normalizeFilePath} from '../../utils/path-utils'
|
import {getBasePath, normalizeFilePath} from '../../utils/path-utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TestExecutionResult,
|
TestExecutionResult,
|
||||||
@@ -15,9 +15,12 @@ import {
|
|||||||
|
|
||||||
export class PhpunitJunitParser implements TestParser {
|
export class PhpunitJunitParser implements TestParser {
|
||||||
readonly trackedFiles: Set<string>
|
readonly trackedFiles: Set<string>
|
||||||
|
readonly trackedFilesList: string[]
|
||||||
|
private assumedWorkDir: string | undefined
|
||||||
|
|
||||||
constructor(readonly options: ParseOptions) {
|
constructor(readonly options: ParseOptions) {
|
||||||
this.trackedFiles = new Set(options.trackedFiles.map(f => normalizeFilePath(f)))
|
this.trackedFilesList = options.trackedFiles.map(f => normalizeFilePath(f))
|
||||||
|
this.trackedFiles = new Set(this.trackedFilesList)
|
||||||
}
|
}
|
||||||
|
|
||||||
async parse(filePath: string, content: string): Promise<TestRunResult> {
|
async parse(filePath: string, content: string): Promise<TestRunResult> {
|
||||||
@@ -127,16 +130,16 @@ export class PhpunitJunitParser implements TestParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const failure = failures[0]
|
const failure = failures[0]
|
||||||
const details = failure._ ?? ''
|
const details = typeof failure === 'string' ? failure : failure._ ?? ''
|
||||||
|
|
||||||
// PHPUnit provides file path directly in testcase attributes
|
// PHPUnit provides file path directly in testcase attributes
|
||||||
let filePath: string | undefined
|
let filePath: string | undefined
|
||||||
let line: number | undefined
|
let line: number | undefined
|
||||||
|
|
||||||
if (tc.$.file) {
|
if (tc.$.file) {
|
||||||
const normalizedPath = normalizeFilePath(tc.$.file)
|
const relativePath = this.getRelativePath(tc.$.file)
|
||||||
if (this.trackedFiles.has(normalizedPath)) {
|
if (this.trackedFiles.has(relativePath)) {
|
||||||
filePath = normalizedPath
|
filePath = relativePath
|
||||||
}
|
}
|
||||||
if (tc.$.line) {
|
if (tc.$.line) {
|
||||||
line = parseInt(tc.$.line)
|
line = parseInt(tc.$.line)
|
||||||
@@ -153,7 +156,7 @@ export class PhpunitJunitParser implements TestParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let message: string | undefined
|
let message: string | undefined
|
||||||
if (failure.$) {
|
if (typeof failure !== 'string' && failure.$) {
|
||||||
message = failure.$.message
|
message = failure.$.message
|
||||||
if (failure.$.type) {
|
if (failure.$.type) {
|
||||||
message = message ? `${failure.$.type}: ${message}` : failure.$.type
|
message = message ? `${failure.$.type}: ${message}` : failure.$.type
|
||||||
@@ -174,17 +177,48 @@ export class PhpunitJunitParser implements TestParser {
|
|||||||
|
|
||||||
for (const str of lines) {
|
for (const str of lines) {
|
||||||
// Match patterns like /path/to/file.php:123 or at /path/to/file.php(123)
|
// Match patterns like /path/to/file.php:123 or at /path/to/file.php(123)
|
||||||
const match = str.match(/([^\s:()]+\.php):(\d+)|([^\s:()]+\.php)\((\d+)\)/)
|
const matchColon = str.match(/((?:[A-Za-z]:)?[^\s:()]+?\.(?:php|phpt)):(\d+)/)
|
||||||
if (match) {
|
if (matchColon) {
|
||||||
const path = match[1] ?? match[3]
|
const relativePath = this.getRelativePath(matchColon[1])
|
||||||
const lineStr = match[2] ?? match[4]
|
if (this.trackedFiles.has(relativePath)) {
|
||||||
const normalizedPath = normalizeFilePath(path)
|
return {filePath: relativePath, line: parseInt(matchColon[2])}
|
||||||
if (this.trackedFiles.has(normalizedPath)) {
|
}
|
||||||
return {filePath: normalizedPath, line: parseInt(lineStr)}
|
}
|
||||||
|
|
||||||
|
const matchParen = str.match(/((?:[A-Za-z]:)?[^\s:()]+?\.(?:php|phpt))\((\d+)\)/)
|
||||||
|
if (matchParen) {
|
||||||
|
const relativePath = this.getRelativePath(matchParen[1])
|
||||||
|
if (this.trackedFiles.has(relativePath)) {
|
||||||
|
return {filePath: relativePath, line: parseInt(matchParen[2])}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getRelativePath(path: string): string {
|
||||||
|
path = normalizeFilePath(path)
|
||||||
|
const workDir = this.getWorkDir(path)
|
||||||
|
if (workDir !== undefined && path.startsWith(workDir)) {
|
||||||
|
path = path.substr(workDir.length)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWorkDir(path: string): string | undefined {
|
||||||
|
if (this.options.workDir) {
|
||||||
|
return this.options.workDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.assumedWorkDir && path.startsWith(this.assumedWorkDir)) {
|
||||||
|
return this.assumedWorkDir
|
||||||
|
}
|
||||||
|
|
||||||
|
const basePath = getBasePath(path, this.trackedFilesList)
|
||||||
|
if (basePath !== undefined) {
|
||||||
|
this.assumedWorkDir = basePath
|
||||||
|
}
|
||||||
|
return basePath
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user