Skip to content

Commit ea0d569

Browse files
committed
Add a command to dev.py to parse the pytest report
1 parent 7d7621a commit ea0d569

7 files changed

Lines changed: 540 additions & 382 deletions

File tree

.github/workflows/daily.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,22 @@ on:
33
schedule:
44
# Runs at 03:00 UTC daily — adjust as needed
55
- cron: '0 3 * * *'
6-
workflow_dispatch: {}
6+
workflow_dispatch: { }
77

88
jobs:
99
integration:
1010
uses: ./.github/workflows/run_tests.yml
1111
with:
1212
marker: 'integration'
13+
additional_params: "--json-report --json-report-file=.report.json"
1314
secrets: inherit
1415

1516
notify-slack:
16-
needs: [integration]
17+
needs: [ integration ]
1718
if: ${{ always() && needs.integration.result == 'failure' }}
1819
uses: ./.github/workflows/notify_slack.yml
1920
with:
2021
workflow-name: ${{ github.workflow }}
22+
report-file: ".report.json"
2123
summary: Daily integration workflow failed.
22-
details: |
23-
Marker: integration
24-
Result: integration=${{ needs.integration.result }}
2524
secrets: inherit

.github/workflows/notify_slack.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ on:
66
description: 'Display name of the workflow that failed.'
77
required: true
88
type: string
9+
report-file:
10+
description: 'JSON file with the test report.'
11+
required: true
12+
type: string
913
summary:
1014
description: 'Short summary for the Slack notification.'
1115
required: true
1216
type: string
13-
details:
14-
description: 'Additional details to include in the Slack notification.'
15-
required: false
16-
default: ''
17-
type: string
1817
secrets:
1918
SLACK_WEBHOOK_URL:
2019
required: true
@@ -24,6 +23,8 @@ jobs:
2423
name: Notify Slack on failure
2524
runs-on: ubuntu-latest
2625
steps:
26+
- name: Generate pytest summary from JSON report
27+
run: uv run --frozen dev.py parse-pytest-report --file .report.json > details.md
2728
- name: Post failure notification
2829
uses: slackapi/slack-github-action@v2.1.1
2930
with:
@@ -39,4 +40,4 @@ jobs:
3940
- type: section
4041
text:
4142
type: mrkdwn
42-
text: "*Details*\n${{ inputs.details }}"
43+
text: "*Details*\n$(cat details.md)""

.github/workflows/run_tests.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ on:
66
description: 'pytest -m expression (e.g., `not integration` or `integration`)'
77
required: true
88
type: string
9+
pytest_params:
10+
description: 'additional pytest params'
11+
required: false
12+
default: ""
13+
type: string
914
concurrency:
1015
group: ${{ github.workflow }}-${{ github.ref }}
1116
cancel-in-progress: true
@@ -89,6 +94,6 @@ jobs:
8994
- name: Install dependencies
9095
uses: ./.github/actions/python-uv-setup
9196
- name: Run core tests
92-
run: uv run pytest -m "${{ inputs.marker }}" tests/
97+
run: uv run pytest -m "${{ inputs.marker }}" tests/ ${{ pytest_params }}
9398
- name: Run plugin tests
94-
run: uv run pytest -m "${{ inputs.marker }}" plugins/*/tests/*.py
99+
run: uv run pytest -m "${{ inputs.marker }}" plugins/*/tests/*.py ${{ pytest_params }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,6 @@ profile.html
9696
.claude/
9797

9898
.uv-cache/
99+
100+
# pytest json report
101+
.report.json

agents-core/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Website = "https://visionagents.ai/"
3939
Source = "https://github.com/GetStream/Vision-Agents"
4040

4141
[project.optional-dependencies]
42-
dev = ["pytest", "mypy", "ruff", "click"]
42+
dev = ["pytest", "pytest-json-report", "mypy", "ruff", "click"]
4343
anthropic = ["vision-agents-plugins-anthropic"]
4444
cartesia = ["vision-agents-plugins-cartesia"]
4545
deepgram = ["vision-agents-plugins-deepgram"]

dev.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
Essential dev commands for testing, linting, and type checking
55
"""
66

7+
import datetime
8+
import json
79
import os
810
import shlex
911
import subprocess
@@ -216,5 +218,92 @@ def check():
216218
click.echo("\n✅ All checks passed!")
217219

218220

221+
def _extract_first_failure(
222+
tests: list[dict],
223+
) -> tuple[str, str, str] | tuple[None, None, None]:
224+
"""
225+
Return (nodeid, phase, message) for first failed test,
226+
checking setup → call → teardown.
227+
"""
228+
for t in tests:
229+
if t.get("outcome") in ("failed", "error"):
230+
nodeid = t.get("nodeid", "unknown test")
231+
232+
for phase in ["setup", "call", "teardown"]:
233+
phase_data = t.get(phase)
234+
if phase_data and phase_data.get("outcome") == "failed":
235+
longrepr = phase_data.get("longrepr", "")
236+
if isinstance(longrepr, dict):
237+
message = longrepr.get("reprcrash", {}).get(
238+
"message", str(longrepr)
239+
)
240+
else:
241+
message = str(longrepr)
242+
243+
return nodeid, phase, message
244+
245+
return nodeid, "unknown", "No error details available"
246+
247+
return None, None, None
248+
249+
250+
@cli.command()
251+
@click.option(
252+
"--file",
253+
"report_file",
254+
default=".report.json",
255+
type=click.File("r"),
256+
show_default=True,
257+
help="Path to pytest JSON report file",
258+
)
259+
def parse_pytest_report(report_file):
260+
"""Parse pytest JSON report and print summary for CI (GitHub Actions friendly)."""
261+
report = json.load(report_file)
262+
263+
summary = report.get("summary", {})
264+
duration = str(datetime.timedelta(report.get("duration", 0) / 1000))
265+
exit_code = report.get("exitcode", 0)
266+
267+
failed = summary.get("failed", 0)
268+
error = summary.get("error", 0)
269+
passed = summary.get("passed", 0)
270+
skipped = summary.get("skipped", 0)
271+
total = summary.get("total", 0)
272+
273+
# Header
274+
status = (
275+
"❌ Test Results - Failed" if failed or error else "✅ Test Results - Success"
276+
)
277+
click.echo(f"**{status}**")
278+
click.echo()
279+
280+
# Duration
281+
click.echo(f"**Duration:** {duration}")
282+
click.echo()
283+
284+
# Summary
285+
click.echo("**Summary:**")
286+
click.echo(f"- Total: {total}")
287+
click.echo(f"- Passed: {passed}")
288+
click.echo(f"- Failed: {failed}")
289+
click.echo(f"- Error: {error}")
290+
click.echo(f"- Skipped: {skipped}")
291+
click.echo()
292+
293+
# Failure details
294+
if failed or error:
295+
nodeid, phase, message = _extract_first_failure(report.get("tests", []))
296+
297+
click.echo("**Failure Details:**")
298+
click.echo(f"- Exit code: `{exit_code}`")
299+
300+
if nodeid:
301+
click.echo(f"- First failed test: `{nodeid}`")
302+
if phase:
303+
click.echo(f"- Phase: `{phase}`")
304+
if message:
305+
click.echo(f"- Error:\n\n```\n{message}\n```")
306+
307+
219308
if __name__ == "__main__":
220309
cli()

0 commit comments

Comments
 (0)