chore: import upstream snapshot with attribution
Harness Compat / harness compat (push) Failing after 0s
CI / test on 3.12 (standard) (push) Has been cancelled
CI / test on 3.13 (standard) (push) Has been cancelled
CI / test on 3.14 (standard) (push) Has been cancelled
CI / test on 3.10 (all-extras) (push) Has been cancelled
CI / test on 3.11 (all-extras) (push) Has been cancelled
CI / test on 3.12 (all-extras) (push) Has been cancelled
CI / test on 3.14 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.10 (pydantic-evals) (push) Has been cancelled
CI / test on 3.11 (pydantic-evals) (push) Has been cancelled
CI / test on 3.12 (pydantic-evals) (push) Has been cancelled
CI / deploy-docs-preview (push) Has been cancelled
CI / build release artifacts (push) Has been cancelled
CI / publish to PyPI (push) Has been cancelled
CI / Send tweet (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / mypy (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / test on 3.10 (standard) (push) Has been cancelled
CI / test on 3.11 (standard) (push) Has been cancelled
CI / test on 3.13 (all-extras) (push) Has been cancelled
CI / test on 3.14 (all-extras) (push) Has been cancelled
CI / test on 3.10 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.11 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.12 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-evals) (push) Has been cancelled
CI / test on 3.14 (pydantic-evals) (push) Has been cancelled
CI / test on 3.10 (lowest-versions) (push) Has been cancelled
CI / test on 3.11 (lowest-versions) (push) Has been cancelled
CI / test on 3.12 (lowest-versions) (push) Has been cancelled
CI / test on 3.13 (lowest-versions) (push) Has been cancelled
CI / test on 3.14 (lowest-versions) (push) Has been cancelled
CI / test examples on 3.11 (push) Has been cancelled
CI / test examples on 3.12 (push) Has been cancelled
CI / test examples on 3.13 (push) Has been cancelled
CI / test examples on 3.14 (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / check (push) Has been cancelled
CI / deploy-docs (push) Has been cancelled
Harness Compat / harness compat (push) Failing after 0s
CI / test on 3.12 (standard) (push) Has been cancelled
CI / test on 3.13 (standard) (push) Has been cancelled
CI / test on 3.14 (standard) (push) Has been cancelled
CI / test on 3.10 (all-extras) (push) Has been cancelled
CI / test on 3.11 (all-extras) (push) Has been cancelled
CI / test on 3.12 (all-extras) (push) Has been cancelled
CI / test on 3.14 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.10 (pydantic-evals) (push) Has been cancelled
CI / test on 3.11 (pydantic-evals) (push) Has been cancelled
CI / test on 3.12 (pydantic-evals) (push) Has been cancelled
CI / deploy-docs-preview (push) Has been cancelled
CI / build release artifacts (push) Has been cancelled
CI / publish to PyPI (push) Has been cancelled
CI / Send tweet (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / mypy (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / test on 3.10 (standard) (push) Has been cancelled
CI / test on 3.11 (standard) (push) Has been cancelled
CI / test on 3.13 (all-extras) (push) Has been cancelled
CI / test on 3.14 (all-extras) (push) Has been cancelled
CI / test on 3.10 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.11 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.12 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-evals) (push) Has been cancelled
CI / test on 3.14 (pydantic-evals) (push) Has been cancelled
CI / test on 3.10 (lowest-versions) (push) Has been cancelled
CI / test on 3.11 (lowest-versions) (push) Has been cancelled
CI / test on 3.12 (lowest-versions) (push) Has been cancelled
CI / test on 3.13 (lowest-versions) (push) Has been cancelled
CI / test on 3.14 (lowest-versions) (push) Has been cancelled
CI / test examples on 3.11 (push) Has been cancelled
CI / test examples on 3.12 (push) Has been cancelled
CI / test examples on 3.13 (push) Has been cancelled
CI / test examples on 3.14 (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / check (push) Has been cancelled
CI / deploy-docs (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import urllib.error
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
import ci_duration
|
||||
|
||||
|
||||
def test_normalize_matrix_job_signature():
|
||||
job = ci_duration.normalize_job(
|
||||
{
|
||||
'id': 123,
|
||||
'name': 'test on 3.10 (all-extras)',
|
||||
'status': 'completed',
|
||||
'conclusion': 'success',
|
||||
'started_at': '2026-06-13T17:15:03Z',
|
||||
'completed_at': '2026-06-13T17:24:05Z',
|
||||
'runner_name': 'GitHub Actions 1001364942',
|
||||
'runner_group_name': 'GitHub Actions',
|
||||
'html_url': 'https://github.com/pydantic/pydantic-ai/actions/runs/1/job/123',
|
||||
'steps': [],
|
||||
}
|
||||
)
|
||||
|
||||
assert job.job_family == 'test'
|
||||
assert job.matrix_python == '3.10'
|
||||
assert job.matrix_extra == 'all-extras'
|
||||
assert job.runner_class == 'github-hosted'
|
||||
assert job.job_signature == 'job=test / runner=github-hosted / py=3.10 / extra=all-extras'
|
||||
assert job.duration_seconds == 542
|
||||
assert ci_duration.is_tracked_test_job(job)
|
||||
|
||||
|
||||
def test_non_test_jobs_are_not_tracked():
|
||||
jobs = [
|
||||
ci_duration.normalize_job(
|
||||
{
|
||||
'id': 123,
|
||||
'name': name,
|
||||
'status': 'completed',
|
||||
'conclusion': 'success',
|
||||
'started_at': '2026-06-13T17:15:03Z',
|
||||
'completed_at': '2026-06-13T17:16:03Z',
|
||||
'runner_name': 'GitHub Actions 1001364942',
|
||||
'runner_group_name': 'GitHub Actions',
|
||||
'html_url': 'https://github.com/pydantic/pydantic-ai/actions/runs/1/job/123',
|
||||
'steps': [],
|
||||
}
|
||||
)
|
||||
for name in ['lint', 'test examples on 3.13']
|
||||
]
|
||||
|
||||
assert [ci_duration.is_tracked_test_job(job) for job in jobs] == [False, False]
|
||||
|
||||
|
||||
def test_classify_slow_job_requires_relative_and_absolute_delta():
|
||||
baseline = ci_duration.compute_baseline([360, 370, 380, 390, 400, 410, 420, 430, 440, 450])
|
||||
job = ci_duration.JobRecord(
|
||||
job_id=123,
|
||||
raw_name='test on 3.10 (all-extras)',
|
||||
job_family='test',
|
||||
job_signature='job=test / runner=github-hosted / py=3.10 / extra=all-extras',
|
||||
matrix_python='3.10',
|
||||
matrix_extra='all-extras',
|
||||
conclusion='success',
|
||||
status='completed',
|
||||
started_at='2026-06-13T17:15:03Z',
|
||||
completed_at='2026-06-13T17:24:05Z',
|
||||
duration_seconds=600,
|
||||
runner_name='GitHub Actions 1001364942',
|
||||
runner_group_name='GitHub Actions',
|
||||
runner_class='github-hosted',
|
||||
html_url='https://github.com/pydantic/pydantic-ai/actions/runs/1/job/123',
|
||||
steps=[],
|
||||
)
|
||||
|
||||
row = ci_duration.classify_job(job, baseline)
|
||||
|
||||
assert row.status == 'slow'
|
||||
assert row.delta_seconds == 195
|
||||
|
||||
|
||||
def test_render_report_uses_sticky_marker_and_threshold_context():
|
||||
workflow: ci_duration.JsonObject = {
|
||||
'duration_seconds': 840,
|
||||
'html_url': 'https://github.com/pydantic/pydantic-ai/actions/runs/1',
|
||||
}
|
||||
row = ci_duration.ReportRow(
|
||||
job_name='test on 3.10 (all-extras)',
|
||||
job_signature='job=test / runner=github-hosted / py=3.10 / extra=all-extras',
|
||||
duration_seconds=600,
|
||||
baseline=ci_duration.compute_baseline([360, 370, 380, 390, 400, 410, 420, 430, 440, 450]),
|
||||
delta_seconds=195,
|
||||
delta_percent=48,
|
||||
status='slow',
|
||||
)
|
||||
|
||||
report = ci_duration.render_report(123, 'abcdef1234567890', workflow, [row])
|
||||
|
||||
assert report.startswith('<!-- ci-duration-report -->\n## CI Duration Report')
|
||||
assert 'Tracked test jobs: 1' in report
|
||||
assert 'Total tracked test job duration: 10m 00s' in report
|
||||
assert 'Baseline: up to 30 successful `main` CI runs and 60 successful PR CI runs' in report
|
||||
assert 'Minimum baseline sample: 10 successful matching jobs' in report
|
||||
assert '| test on 3.10 (all-extras) | 10m 00s | 6m 45s | 7m 08s | +3m 15s (+48%) | slow |' in report
|
||||
assert 'trigger:ci-duration-report' in report
|
||||
|
||||
|
||||
def test_collect_baselines_skips_unavailable_historical_run():
|
||||
class StubGitHubClient(ci_duration.GitHubClient):
|
||||
def request_paginated(self, path: str, *, max_items: int | None = None) -> list[ci_duration.JsonObject]:
|
||||
if path == 'actions/workflows/ci.yml/runs?branch=main&event=push&status=success':
|
||||
return [
|
||||
{
|
||||
'id': run_id,
|
||||
'run_attempt': 1,
|
||||
'head_sha': f'baseline-{run_id}',
|
||||
}
|
||||
for run_id in range(11)
|
||||
]
|
||||
if path == 'actions/workflows/ci.yml/runs?event=pull_request&status=success':
|
||||
return []
|
||||
if path == 'actions/runs/0/attempts/1/jobs':
|
||||
raise urllib.error.URLError('timed out')
|
||||
if path.startswith('actions/runs/') and path.endswith('/attempts/1/jobs'):
|
||||
return [
|
||||
{
|
||||
'id': 123,
|
||||
'name': 'test on 3.10 (all-extras)',
|
||||
'status': 'completed',
|
||||
'conclusion': 'success',
|
||||
'started_at': '2026-06-13T17:15:03Z',
|
||||
'completed_at': '2026-06-13T17:24:05Z',
|
||||
'runner_name': 'GitHub Actions 1001364942',
|
||||
'runner_group_name': 'GitHub Actions',
|
||||
'html_url': 'https://github.com/pydantic/pydantic-ai/actions/runs/1/job/123',
|
||||
'steps': [],
|
||||
}
|
||||
]
|
||||
raise RuntimeError(f'Unexpected path: {path}')
|
||||
|
||||
baselines = ci_duration.collect_baselines(StubGitHubClient('pydantic/pydantic-ai', 'token'), 'current-sha')
|
||||
|
||||
assert baselines['job=test / runner=github-hosted / py=3.10 / extra=all-extras'].sample_size == 10
|
||||
|
||||
|
||||
def test_collect_baselines_stops_after_time_budget():
|
||||
class StubGitHubClient(ci_duration.GitHubClient):
|
||||
def request_paginated(self, path: str, *, max_items: int | None = None) -> list[ci_duration.JsonObject]:
|
||||
if path == 'actions/workflows/ci.yml/runs?branch=main&event=push&status=success':
|
||||
return [
|
||||
{
|
||||
'id': 1,
|
||||
'run_attempt': 1,
|
||||
'head_sha': 'baseline-1',
|
||||
}
|
||||
]
|
||||
if path == 'actions/workflows/ci.yml/runs?event=pull_request&status=success':
|
||||
return []
|
||||
raise RuntimeError(f'Unexpected path: {path}')
|
||||
|
||||
monotonic_values = [0.0, ci_duration.BASELINE_COLLECTION_MAX_SECONDS]
|
||||
original_monotonic = ci_duration.time.monotonic
|
||||
|
||||
def monotonic() -> float:
|
||||
if monotonic_values:
|
||||
return monotonic_values.pop(0)
|
||||
return ci_duration.BASELINE_COLLECTION_MAX_SECONDS
|
||||
|
||||
ci_duration.time.monotonic = monotonic
|
||||
try:
|
||||
baselines = ci_duration.collect_baselines(StubGitHubClient('pydantic/pydantic-ai', 'token'), 'current-sha')
|
||||
finally:
|
||||
ci_duration.time.monotonic = original_monotonic
|
||||
|
||||
assert baselines == {}
|
||||
Reference in New Issue
Block a user