Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for pytest trace back parsing + directory-mapping #238

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a007309
feat: add pytest junit parser and simple test
unki2aut Mar 13, 2023
7442569
feat: support pytest trace back parsing
unki2aut Mar 13, 2023
0d3dd79
feat: consider the workdir in pytest annotations
unki2aut Mar 14, 2023
3312569
feat: consider tracked files to get absolute paths
unki2aut Mar 14, 2023
cdfbf90
refactor: improve `getWorkDir()` implementation
unki2aut Mar 15, 2023
3606ee2
revert: `getBasePath()` logic to original
unki2aut Mar 20, 2023
8cb6ade
test: directory mapping
unki2aut Mar 20, 2023
c0b203e
update workflow versions
unki2aut Mar 20, 2023
fb476f0
change directory mapping
unki2aut Mar 20, 2023
599ed4b
change directory mapping
unki2aut Mar 20, 2023
748bf26
change directory mapping
unki2aut Mar 20, 2023
0582e4d
revert directory mapping
unki2aut Mar 24, 2023
85f8936
test: log annotations
unki2aut Mar 24, 2023
5e285f5
test: log annotations
unki2aut Mar 24, 2023
60eba29
fix: build
unki2aut Mar 24, 2023
3ec78ee
test: write integration test for pytest
unki2aut Mar 27, 2023
9b50343
feat: add directory mapping to map between Docker image and local path
unki2aut Mar 27, 2023
fc35e5f
fix: directory mapping is not required
unki2aut Mar 27, 2023
c9e9d8c
fix: directory mapping is not required
unki2aut Mar 27, 2023
a1e1190
Merge remote-tracking branch 'upstream/main'
unki2aut Nov 11, 2023
58976f3
chore: upgrade to use NodeJS v20
unki2aut Jul 1, 2024
24bbe17
lint: fix linting issues
unki2aut Jul 1, 2024
d8517ff
chore: bump versions in GH workflow files
unki2aut Jul 1, 2024
cabe507
chore: fix versions in GH workflow files
unki2aut Jul 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/check-dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set Node.js 16.x
uses: actions/setup-node@v3
- name: Set Node.js 20.x
uses: actions/setup-node@v4
with:
node-version: 16.x
node-version: 20.x

- name: Install dependencies
run: npm ci
Expand All @@ -46,7 +46,7 @@ jobs:
id: diff

# If index.js was different than expected, upload the expected version as an artifact
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:

- name: Upload test results
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: test-results
path: __tests__/__results__/*.xml
39 changes: 22 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ jobs:
# flutter-json
# java-junit
# jest-junit
# pytest-junit
# mocha-json
reporter: ''

Expand Down Expand Up @@ -277,21 +278,23 @@ Some heuristic was necessary to figure out the mapping between the line in the s
It will create test results in Junit XML format which can be then processed by this action.
You can use the following example configuration in `package.json`:
```json
"scripts": {
"test": "jest --ci --reporters=default --reporters=jest-junit"
},
"devDependencies": {
"jest": "^26.5.3",
"jest-junit": "^12.0.0"
},
"jest-junit": {
"outputDirectory": "reports",
"outputName": "jest-junit.xml",
"ancestorSeparator": " › ",
"uniqueOutputName": "false",
"suiteNameTemplate": "{filepath}",
"classNameTemplate": "{classname}",
"titleTemplate": "{title}"
{
"scripts": {
"test": "jest --ci --reporters=default --reporters=jest-junit"
},
"devDependencies": {
"jest": "^26.5.3",
"jest-junit": "^12.0.0"
},
"jest-junit": {
"outputDirectory": "reports",
"outputName": "jest-junit.xml",
"ancestorSeparator": " › ",
"uniqueOutputName": "false",
"suiteNameTemplate": "{filepath}",
"classNameTemplate": "{classname}",
"titleTemplate": "{title}"
}
}
```

Expand All @@ -307,8 +310,10 @@ Configuration of `uniqueOutputName`, `suiteNameTemplate`, `classNameTemplate`, `

You can use the following example configuration in `package.json`:
```json
"scripts": {
"test": "mocha --reporter json > test-results.json"
{
"scripts": {
"test": "mocha --reporter json > test-results.json"
}
}
```

Expand Down
12 changes: 12 additions & 0 deletions __tests__/fixtures/external/pytest/report-tb-short.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite name="pytest" errors="0" failures="1" skipped="0" tests="1" time="0.316"
timestamp="2023-03-10T11:26:51.659606" hostname="c29b94e3532a">
<testcase classname="product_changes.tests.first_test.MyTestCase" name="test_something" time="0.075">
<failure message="assert False">mnt/extra-addons/product_changes/tests/first_test.py:6: in test_something
assert False
E assert False
</failure>
</testcase>
</testsuite>
</testsuites>
7 changes: 7 additions & 0 deletions __tests__/fixtures/external/pytest/single-case.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<testsuites>
<testsuite name="pytest" errors="0" failures="0" skipped="0" tests="1" time="0.178"
timestamp="2023-03-08T11:47:09.377238" hostname="0e634555ad5c">
<testcase classname="product_changes.tests.first_test.MyTestCase" name="test_something" time="0.133"/>
</testsuite>
</testsuites>
79 changes: 79 additions & 0 deletions __tests__/integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import fs from 'fs'
import {LocalFileProvider} from '../src/input-providers/local-file-provider'

const input: Record<string, string> = {
name: 'Test Results',
path: 'test-report.xml',
reporter: 'pytest-junit',
'path-replace-backslashes': 'false',
'list-suites': 'all',
'list-tests': 'all',
'max-annotations': '10',
'fail-on-error': 'true',
'only-summary': 'false',
'directory-mapping': 'mnt/extra-addons:mypath',
token: '***'
}

const update = jest.fn().mockReturnValue({data: {}, status: 0})

jest.mock('@actions/core', () => ({
getInput: jest.fn().mockImplementation((name: string) => input[name]),
setFailed: jest.fn(),
setOutput: jest.fn(),
startGroup: jest.fn(),
endGroup: jest.fn(),
info: jest.fn(),
warning: jest.fn()
}))
jest.mock('@actions/github', () => {
return {
getOctokit: jest.fn().mockReturnValue({
rest: {
checks: {
update,
create: jest.fn().mockReturnValue({data: {}})
}
}
}),
context: {
eventName: '',
payload: {}
}
}
})

jest.mock('../src/input-providers/local-file-provider')

describe('integration test', () => {
it('pytest', async () => {
jest.spyOn(LocalFileProvider.prototype, 'load').mockResolvedValue({
'report-tb-short.xml': [
{
file: 'report-tb-short.xml',
content: fs.readFileSync(__dirname + '/fixtures/external/pytest/report-tb-short.xml', {encoding: 'utf8'})
}
]
})
jest
.spyOn(LocalFileProvider.prototype, 'listTrackedFiles')
.mockResolvedValue(['mypath/product_changes/tests/first_test.py'])

await import('../src/main')
// trick to wait for the pending "main" Promise
await new Promise(resolve => setTimeout(resolve))

expect(update).toHaveBeenCalledTimes(1)
expect(update).toHaveBeenCalledWith(
expect.objectContaining({
output: expect.objectContaining({
annotations: [
expect.objectContaining({
path: 'mypath/product_changes/tests/first_test.py'
})
]
})
})
)
})
})
49 changes: 49 additions & 0 deletions __tests__/pytest-junit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as fs from 'fs'
import * as path from 'path'

import {PytestJunitParser} from '../src/parsers/pytest-junit/pytest-junit-parser'
import {ParseOptions} from '../src/test-parser'
import {normalizeFilePath} from '../src/utils/path-utils'
import {getAnnotations} from '../src/report/get-annotations'

describe('pytest-junit tests', () => {
it('test with one successful test', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'pytest', 'single-case.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})

const opts: ParseOptions = {
parseErrors: true,
trackedFiles: []
}

const parser = new PytestJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result.tests).toBe(1)
expect(result.result).toBe('success')
})

it('test failure with trace back', async () => {
const fixturePath = path.join(__dirname, 'fixtures', 'external', 'pytest', 'report-tb-short.xml')
const filePath = normalizeFilePath(path.relative(__dirname, fixturePath))
const fileContent = fs.readFileSync(fixturePath, {encoding: 'utf8'})

const opts: ParseOptions = {
parseErrors: true,
trackedFiles: ['addons/product_changes/tests/first_test.py']
}

const parser = new PytestJunitParser(opts)
const result = await parser.parse(filePath, fileContent)
expect(result.tests).toBe(1)
expect(result.result).toBe('failed')
expect(result.failedSuites[0].failedGroups[0].failedTests[0].error).toMatchObject({
line: 6,
message: 'assert False'
})

const annotations = getAnnotations([result], 1)
expect(annotations.length).toBe(1)
expect(annotations[0].path).toBe('addons/product_changes/tests/first_test.py')
})
})
12 changes: 12 additions & 0 deletions __tests__/utils/path-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {getBasePath} from '../../src/utils/path-utils'

describe('getBasePath', () => {
it('tracked file in path', () => {
const path = 'C:/Users/Michal/Workspace/dorny/test-check/reports/jest/__tests__/main.test.js'
const trackedFiles = ['__tests__/main.test.js', '__tests__/second.test.js', 'lib/main.js']

const result = getBasePath(path, trackedFiles)

expect(result).toBe('C:/Users/Michal/Workspace/dorny/test-check/reports/jest/')
})
})
8 changes: 7 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ inputs:
- flutter-json
- java-junit
- jest-junit
- pytest-junit
- mocha-json
required: true
list-suites:
Expand Down Expand Up @@ -71,6 +72,11 @@ inputs:
Detailed listing of test suites and test cases will be skipped.
default: 'false'
required: false
directory-mapping:
description: |
Map part of the file paths to something else, so they match the paths of the repository.
This is needed when you use run your code in a container with a different path than the source code repository.
required: false
token:
description: GitHub Access Token
required: false
Expand All @@ -94,7 +100,7 @@ outputs:
url_html:
description: Check run URL HTML
runs:
using: 'node16'
using: 'node20'
main: 'dist/index.js'
branding:
color: blue
Expand Down
Loading