Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 31 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Tests
on:
pull_request:
branches:
- main
permissions:
contents: read

jobs:
tests:
concurrency:
group: tests-${{ github.event_name == 'push' && github.run_number || github.ref }}
cancel-in-progress: true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"

- name: Install dependencies
run: pip install .[test]

- name: Show versions
run: pip freeze

- name: Run pytest
run: pytest tests/
13 changes: 1 addition & 12 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,8 @@ repos:
types_or: [python, rst, markdown]
additional_dependencies: [tomli]

- repo: local
hooks:
- id: mypy
# note: assumes python env is setup and activated
name: mypy
entry: mypy
language: system
pass_filenames: false
types: [python]
stages: [manual]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.1
rev: v2.0.0
hooks:
- id: mypy
require_serial: true
Expand Down
4 changes: 2 additions & 2 deletions ci/make_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ def time_to_str(x: float) -> str:
is_negative = x < 0.0
if x >= 1.0:
result = f"{x:0.3f}s"
elif x >= 0.001: # noqa: PLR2004
elif x >= 0.001:
result = f"{x * 1000:0.3f}ms"
elif x >= 0.000001: # noqa: PLR2004
elif x >= 0.000001:
result = f"{x * (1000 ** 2):0.3f}us"
else:
result = f"{x * (1000 ** 3):0.3f}ns"
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,7 @@ select = [
"W", # pycodestyle - warning
"YTT", # flake8-2020
]
ignore = ["A002"]
ignore = [
"A002",
"PLR2004", # Magic value used in comparison
]
Empty file added tests/__init__.py
Empty file.
105 changes: 105 additions & 0 deletions tests/test_find_commit_to_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from __future__ import annotations

import subprocess
from collections.abc import Callable
from pathlib import Path
from typing import Any

import pytest

from ci.find_commit_to_run import run


class _FakeCompleted:
def __init__(self, stdout: bytes) -> None:
self.stdout = stdout


def _fake_subprocess_run(shas: list[str]) -> Callable[..., _FakeCompleted]:
out = "\n".join(f"{sha} commit message {sha}" for sha in shas).encode()

def _run(cmd: Any, **kwargs: Any) -> _FakeCompleted:
return _FakeCompleted(out)

return _run


def test_run_no_existing_shas_picks_first(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
repo = tmp_path / "repo"
repo.mkdir()
monkeypatch.setattr(subprocess, "run", _fake_subprocess_run(["sha1111", "sha2222"]))

run(input_path=tmp_path, repo_path=repo)

assert capsys.readouterr().out.strip() == "sha1111"


def test_run_skips_existing_shas(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
(tmp_path / "shas.txt").write_text("sha1111\nsha2222\n")
repo = tmp_path / "repo"
repo.mkdir()
monkeypatch.setattr(
subprocess,
"run",
_fake_subprocess_run(["sha1111", "sha2222", "sha3333"]),
)

run(input_path=tmp_path, repo_path=repo)

assert capsys.readouterr().out.strip() == "sha3333"


def test_run_all_existing_prints_none(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
(tmp_path / "shas.txt").write_text("sha1111\nsha2222\n")
repo = tmp_path / "repo"
repo.mkdir()
monkeypatch.setattr(subprocess, "run", _fake_subprocess_run(["sha1111", "sha2222"]))

run(input_path=tmp_path, repo_path=repo)

assert capsys.readouterr().out.strip() == "NONE"


def test_run_accepts_string_paths(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
repo = tmp_path / "repo"
repo.mkdir()
monkeypatch.setattr(subprocess, "run", _fake_subprocess_run(["abcdef0"]))

run(input_path=str(tmp_path), repo_path=str(repo))

assert capsys.readouterr().out.strip() == "abcdef0"


def test_run_ignores_blank_lines_in_shas_file(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
(tmp_path / "shas.txt").write_text("sha1111\n\nsha2222\n")
repo = tmp_path / "repo"
repo.mkdir()
monkeypatch.setattr(
subprocess,
"run",
_fake_subprocess_run(["sha1111", "sha2222", "sha3333"]),
)

run(input_path=tmp_path, repo_path=repo)

assert capsys.readouterr().out.strip() == "sha3333"
165 changes: 165 additions & 0 deletions tests/test_make_issues.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from __future__ import annotations

import pandas as pd
import pytest

from ci.make_issues import (
escape_ansi,
get_commit_range,
make_body,
time_to_str,
)


@pytest.mark.parametrize(
"value,expected",
[
(1.5, "1.500s"),
(1.0, "1.000s"),
(0.5, "500.000ms"),
(0.001, "1.000ms"),
(0.0005, "500.000us"),
(0.000001, "1.000us"),
(0.0000005, "500.000ns"),
],
)
def test_time_to_str_positive(value: float, expected: str) -> None:
assert time_to_str(value) == expected


def test_escape_ansi_passes_plain_text() -> None:
assert escape_ansi("hello world") == "hello world"


def test_escape_ansi_strips_color_codes() -> None:
colored = "\x1b[31mhello\x1b[0m world"
assert escape_ansi(colored) == "hello world"


def _benchmarks_with_shas(shas: list[str], dates: list[str]) -> pd.DataFrame:
return pd.DataFrame({"sha": shas, "date": pd.to_datetime(dates)})


def test_get_commit_range_returns_prev_sha_to_sha() -> None:
df = _benchmarks_with_shas(
["a", "b", "c"], ["2024-01-01", "2024-01-02", "2024-01-03"]
)
assert get_commit_range(benchmarks=df, sha="c") == "b...c"
assert get_commit_range(benchmarks=df, sha="b") == "a...b"


def test_get_commit_range_orders_by_date_not_input_order() -> None:
# Rows are not in date order; the function should still pick the previous
# sha by date.
df = _benchmarks_with_shas(
["c", "a", "b"], ["2024-01-03", "2024-01-01", "2024-01-02"]
)
assert get_commit_range(benchmarks=df, sha="c") == "b...c"


def _regression_frame() -> pd.DataFrame:
return pd.DataFrame(
{
"sha": ["abc123", "abc123", "abc123", "def456"],
"is_regression": [True, True, True, True],
"name": ["bench.foo", "bench.foo", "bench.bar", "bench.foo"],
"params": ["x=1", "x=2", "", "x=1"],
"abs_change": [0.5, 0.0005, 1.5, 0.1],
"pct_change": [0.10, 0.20, 0.30, 0.40],
}
)


def test_make_body_includes_commit_range_link() -> None:
body = make_body(
base_url="https://github.com/pandas-dev/pandas/compare/",
commit_range="aaa...bbb",
benchmarks=_regression_frame(),
sha="abc123",
)
assert (
"[Commit Range](https://github.com/pandas-dev/pandas/compare/aaa...bbb)" in body
)


def test_make_body_only_includes_target_sha_regressions() -> None:
body = make_body(
base_url="https://github.com/pandas-dev/pandas/compare/",
commit_range="aaa...bbb",
benchmarks=_regression_frame(),
sha="abc123",
)
# Both benchmarks for abc123 should appear; def456 should not.
assert "bench.foo" in body
assert "bench.bar" in body
assert "def456" not in body


def test_make_body_renders_param_sublist_for_nonempty_params() -> None:
body = make_body(
base_url="https://github.com/pandas-dev/pandas/compare/",
commit_range="aaa...bbb",
benchmarks=_regression_frame(),
sha="abc123",
)
# Non-empty params should produce indented sub-bullets with severity.
assert " - [ ] [x=1]" in body
assert " - [ ] [x=2]" in body
assert "10.000% (500.000ms)" in body
assert "20.000% (500.000us)" in body


def test_make_body_inlines_severity_when_params_empty() -> None:
body = make_body(
base_url="https://github.com/pandas-dev/pandas/compare/",
commit_range="aaa...bbb",
benchmarks=_regression_frame(),
sha="abc123",
)
# bench.bar has empty params; severity should be on the benchmark line.
msg = (
" - [ ] [bench.bar](https://pandas-dev.github.io/asv-runner/#bench.bar)"
" - 30.000% (1.500s)"
)
assert msg in body


def test_make_body_shorten_collapses_param_sublist() -> None:
full = make_body(
base_url="https://github.com/pandas-dev/pandas/compare/",
commit_range="aaa...bbb",
benchmarks=_regression_frame(),
sha="abc123",
)
short = make_body(
base_url="https://github.com/pandas-dev/pandas/compare/",
commit_range="aaa...bbb",
benchmarks=_regression_frame(),
sha="abc123",
shorten=True,
)
assert len(short) < len(full)
# In shorten mode, no indented sub-bullets are emitted.
assert " - [ ]" not in short
assert "10.000% (500.000ms)" in short


def test_make_body_excludes_non_regression_rows() -> None:
df = pd.DataFrame(
{
"sha": ["abc", "abc"],
"is_regression": [True, False],
"name": ["bench.foo", "bench.bar"],
"params": ["", ""],
"abs_change": [0.5, 0.5],
"pct_change": [0.10, 0.10],
}
)
body = make_body(
base_url="https://example.com/",
commit_range="x...y",
benchmarks=df,
sha="abc",
)
assert "bench.foo" in body
assert "bench.bar" not in body
Loading
Loading