Skip to content

Commit 8751cec

Browse files
committed
Test manual workflow to send pytest JSON results to DB
1 parent 2f888f8 commit 8751cec

File tree

2 files changed

+126
-128
lines changed

2 files changed

+126
-128
lines changed

.github/workflows/nightly.yml

Lines changed: 80 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,86 @@
11
name: Nightly
22

33
on:
4-
schedule:
5-
- cron: "0 1 * * *"
64
workflow_dispatch:
7-
pull_request:
8-
paths:
9-
- '.github/workflows/nightly.yml'
10-
11-
concurrency:
12-
group: ${{ github.workflow }}-${{ github.ref }}
13-
cancel-in-progress: true
14-
15-
permissions:
16-
contents: read
17-
packages: write
5+
inputs:
6+
source-repo:
7+
description: "ORG/REPO"
8+
required: true
9+
type: string
10+
source-github-run-id:
11+
description: "Repo GitHub Run ID"
12+
required: true #TODO
13+
type: string
14+
source-artifact-name:
15+
description: "Run Artifact Name"
16+
required: true #TODO
17+
type: string
18+
rocm-version:
19+
required: true
20+
type: string
21+
ubuntu-version:
22+
required: true
23+
type: string
24+
rocm-build-num:
25+
required: false
26+
type: string
27+
default: "0"
28+
github-sha:
29+
required: false
30+
type: string
31+
default: ToT
32+
runner-label:
33+
required: false
34+
type: string
35+
default: "MI300"
36+
secrets:
37+
ROCM_JAX_DB_HOSTNAME:
38+
required: true
39+
ROCM_JAX_DB_USERNAME:
40+
required: true
41+
ROCM_JAX_DB_PASSWORD:
42+
required: true
43+
ROCM_JAX_DB_NAME:
44+
required: true
1845

1946
jobs:
20-
call-build-wheels:
21-
strategy:
22-
fail-fast: false
23-
matrix:
24-
rocm-version: ["7.1.1", "7.2.0"]
25-
include:
26-
- rocm-version: "7.1.1"
27-
runner-label: '["linux-x86-64-1gpu-amd"]'
28-
- rocm-version: "7.2.0"
29-
runner-label: '["linux-x86-64-1gpu-amd"]'
30-
uses: ./.github/workflows/build-wheels.yml
31-
with:
32-
python-versions: "3.11,3.12"
33-
rocm-version: ${{ matrix.rocm-version }}
34-
rocm-build-job: ${{ matrix.rocm-build-job }}
35-
rocm-build-num: ${{ matrix.rocm-build-num }}
36-
runner-label: ${{ matrix.runner-label }}
37-
secrets:
38-
rbe_ci_cert: ${{ secrets.RBE_CI_CERT }}
39-
rbe_ci_key: ${{ secrets.RBE_CI_KEY }}
40-
call-build-docker:
41-
needs: call-build-wheels
42-
strategy:
43-
fail-fast: false
44-
matrix:
45-
rocm-version: ["7.1.1", "7.2.0"]
46-
include:
47-
- rocm-version: "7.1.1"
48-
runner-label: '["linux-x86-64-1gpu-amd"]'
49-
- rocm-version: "7.2.0"
50-
runner-label: '["linux-x86-64-1gpu-amd"]'
51-
uses: ./.github/workflows/build-docker.yml
52-
with:
53-
rocm-version: ${{ matrix.rocm-version }}
54-
rocm-build-job: ${{ matrix.rocm-build-job }}
55-
rocm-build-num: ${{ matrix.rocm-build-num }}
56-
runner-label: ${{ matrix.runner-label }}
57-
extra-cr-tag: "nightly"
58-
call-test-and-upload:
59-
needs: call-build-docker
60-
strategy:
61-
fail-fast: false
62-
matrix:
63-
test-command:
64-
- "python jax_rocm_plugin/build/rocm/run_single_gpu.py -c -s"
65-
- "python jax_rocm_plugin/build/rocm/run_multi_gpu.py -c -s"
66-
rocm-version: ["7.1.1", "7.2.0"]
67-
ubuntu-version: ["22", "24"]
68-
include:
69-
- test-command: "python jax_rocm_plugin/build/rocm/run_single_gpu.py -c -s"
70-
runner-label: '["linux-x86-64-1gpu-amd"]'
71-
test-id: "single"
72-
- test-command: "python jax_rocm_plugin/build/rocm/run_multi_gpu.py -c -s"
73-
runner-label: '["linux-x86-64-4gpu-amd"]'
74-
test-id: "multi"
75-
uses: ./.github/workflows/test-and-upload.yml
76-
with:
77-
rocm-version: ${{ matrix.rocm-version }}
78-
ubuntu-version: ${{ matrix.ubuntu-version }}
79-
rocm-build-num: ${{ matrix.rocm-build-num || '0' }}
80-
github-sha: ${{ github.sha }}
81-
github-run-id: ${{ github.run_id }}
82-
runner-label: ${{ matrix.runner-label }}
83-
test-command: ${{ matrix.test-command }}
84-
test-id: ${{ matrix.test-id }}
85-
secrets:
86-
ROCM_JAX_DB_HOSTNAME: ${{ secrets.ROCM_JAX_DB_HOSTNAME }}
87-
ROCM_JAX_DB_USERNAME: ${{ secrets.ROCM_JAX_DB_USERNAME }}
88-
ROCM_JAX_DB_PASSWORD: ${{ secrets.ROCM_JAX_DB_PASSWORD }}
89-
ROCM_JAX_DB_NAME: ${{ secrets.ROCM_JAX_DB_NAME }}
47+
upload-to-db:
48+
runs-on: mysqldb
49+
steps:
50+
- name: Checkout source
51+
uses: actions/checkout@v4
52+
- name: Download logs artifact from source run
53+
env:
54+
GH_TOKEN: ${{ secrets.SECRET_TOKEN }}
55+
run: |
56+
gh run download "${{ inputs.source-github-run-id }}" \
57+
-R "${{ inputs.source-repo }}" \
58+
-n "${{ inputs.source-artifact-name }}" \
59+
-D "logs-${{ inputs.source-github-run-id }}"
60+
- name: Upload logs to MySQL database
61+
env:
62+
ROCM_JAX_DB_HOSTNAME: ${{ secrets.ROCM_JAX_DB_HOSTNAME }}
63+
ROCM_JAX_DB_USERNAME: ${{ secrets.ROCM_JAX_DB_USERNAME }}
64+
ROCM_JAX_DB_PASSWORD: ${{ secrets.ROCM_JAX_DB_PASSWORD }}
65+
ROCM_JAX_DB_NAME: ${{ secrets.ROCM_JAX_DB_NAME }}
66+
ROCM_VERSION: ${{ inputs.rocm-version }}
67+
UBUNTU_VERSION: ${{ inputs.ubuntu-version }}
68+
LOGS_DIR: logs-${{ inputs.source-github-run-id }}
69+
GITHUB_SHA: ${{ inputs.github-sha }}
70+
GITHUB_RUN_ID: ${{ inputs.github-run-id }}
71+
ROCM_BUILD_NUM: ${{ inputs.rocm-build-num }}
72+
run: |
73+
python3 -m venv venv
74+
source venv/bin/activate
75+
pip install --upgrade pip
76+
pip install mysql-connector-python
77+
78+
python ci/upload_test_to_db.py \
79+
--logs_dir "$LOGS_DIR" \
80+
--run-tag "ci-run" \
81+
--runner-label "MI300" \
82+
--ubuntu-version "${UBUNTU_VERSION}" \
83+
--rocm-version "${ROCM_VERSION//.}" \
84+
--commit-sha "$GITHUB_SHA" \
85+
--build-num "$ROCM_BUILD_NUM" \
86+
--github-run-id "$GITHUB_RUN_ID"

ci/upload_test_to_db.py

Lines changed: 46 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
"""
33
Upload pytest results to MySQL.
44
Tables:
5-
- ci_runs: one row per run
6-
- ci_tests: one row per unique test
7-
- ci_results: one row per test per run
5+
- ci_runs_dup: one row per run
6+
- ci_tests_dup: one row per unique test
7+
- ci_results_dup: one row per test per run
88
"""
99

1010
from __future__ import annotations
@@ -72,37 +72,41 @@ def nodeid_parts(nodeid: str) -> Tuple[str, str, str]:
7272
return f, c, t
7373

7474

75-
def list_test_jsons(logs_dir: Path) -> List[Path]:
76-
"""Return per-test JSON files under logs_dir; skip aux files."""
77-
return sorted(
75+
def find_single_report_json(logs_dir: Path) -> Path:
76+
jsons = [
7877
p
7978
for p in logs_dir.iterdir()
8079
if p.is_file()
8180
and p.suffix.lower() == ".json"
82-
and p.name not in SKIP_FILES
8381
and not p.name.endswith("last_running.json")
84-
)
85-
86-
87-
def load_from_per_test_jsons(files: Iterable[Path]) -> Tuple[datetime, List[dict]]:
88-
"""Load tests and set run created_at to the latest per-file 'created'.
89-
90-
Each per-test JSON file must have a 'created' timestamp. We use the maximum
91-
of these values as the run's created_at (i.e., when the run finished).
92-
"""
93-
tests: List[dict] = []
94-
created_values: List[float] = []
95-
96-
for path in files:
97-
with path.open("r", encoding="utf-8") as fh:
98-
data = json.load(fh)
99-
if "created" not in data:
100-
raise ValueError(f"Missing 'created' in {path.name}")
101-
created_values.append(float(data["created"]))
102-
tests.extend(data.get("tests", []))
103-
104-
created_at = datetime.fromtimestamp(max(created_values))
105-
return created_at, tests
82+
]
83+
if not jsons:
84+
print(f"No JSON report found in {logs_dir}")
85+
raise SystemExit(2)
86+
if len(jsons) != 1:
87+
print(
88+
f"Expected exactly ONE JSON report, found {len(jsons)}: "
89+
+ ", ".join(p.name for p in jsons)
90+
)
91+
raise SystemExit(2)
92+
return jsons[0]
93+
94+
95+
def load_from_single_json(path: Path) -> Tuple[datetime, List[dict]]:
96+
with path.open("r", encoding="utf-8") as fh:
97+
data = json.load(fh)
98+
if isinstance(data, dict):
99+
tests = data.get("tests", [])
100+
created = data.get("created")
101+
if created is not None:
102+
created_at = datetime.fromtimestamp(float(created))
103+
else:
104+
created_at = datetime.fromtimestamp(path.stat().st_mtime)
105+
return created_at, tests
106+
if isinstance(data, list):
107+
created_at = datetime.fromtimestamp(path.stat().st_mtime)
108+
return created_at, data
109+
raise ValueError(f"Unexpected JSON structure in {path.name}")
106110

107111

108112
def extract_result_fields(
@@ -263,10 +267,10 @@ def connect():
263267

264268

265269
def insert_run(cur, created_at: datetime, meta: dict) -> int:
266-
"""Insert one row into ci_runs and return run_id. Idempotence is not enforced here."""
270+
"""Insert one row into ci_runs_dup and return run_id. Idempotence is not enforced here."""
267271
cur.execute(
268272
"""
269-
INSERT INTO ci_runs (
273+
INSERT INTO ci_runs_dup (
270274
created_at, commit_sha, runner_label, ubuntu_version,
271275
rocm_version, build_num, github_run_id, run_tag, logs_path
272276
) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s)
@@ -287,11 +291,11 @@ def insert_run(cur, created_at: datetime, meta: dict) -> int:
287291

288292

289293
def sync_tests_and_get_ids(cur, tests: List[dict]) -> Dict[Tuple[str, str, str], int]:
290-
"""Ensure all tests exist in ci_tests and return an ID mapping.
294+
"""Ensure all tests exist in ci_tests_dup and return an ID mapping.
291295
292296
Uses a TEMPORARY TABLE for efficiency with large runs:
293297
1) Bulk insert unique (filename, classname, test_name) into a temp table.
294-
2) INSERT any missing rows into ci_tests in one set operation.
298+
2) INSERT any missing rows into ci_tests_dup in one set operation.
295299
3) SELECT back (file, class, test) -> id mapping in one query.
296300
"""
297301
uniq = {nodeid_parts(t["nodeid"]) for t in tests}
@@ -315,10 +319,10 @@ def sync_tests_and_get_ids(cur, tests: List[dict]) -> Dict[Tuple[str, str, str],
315319

316320
cur.execute(
317321
"""
318-
INSERT INTO ci_tests (filename, classname, test_name)
322+
INSERT INTO ci_tests_dup (filename, classname, test_name)
319323
SELECT s.filename, s.classname, s.test_name
320324
FROM tmp_tests s
321-
LEFT JOIN ci_tests t
325+
LEFT JOIN ci_tests_dup t
322326
ON t.filename = s.filename
323327
AND t.classname = s.classname
324328
AND t.test_name = s.test_name
@@ -330,7 +334,7 @@ def sync_tests_and_get_ids(cur, tests: List[dict]) -> Dict[Tuple[str, str, str],
330334
"""
331335
SELECT t.id, s.filename, s.classname, s.test_name
332336
FROM tmp_tests s
333-
JOIN ci_tests t
337+
JOIN ci_tests_dup t
334338
ON t.filename = s.filename
335339
AND t.classname = s.classname
336340
AND t.test_name = s.test_name
@@ -356,7 +360,7 @@ def batch_insert_results(
356360
return
357361

358362
sql = """
359-
INSERT INTO ci_results
363+
INSERT INTO ci_results_dup
360364
(run_id, test_id, outcome, duration, longrepr, message, skip_label)
361365
VALUES (%s,%s,%s,%s,%s,%s,%s)
362366
ON DUPLICATE KEY UPDATE
@@ -388,16 +392,13 @@ def upload_pytest_results( # pylint: disable=too-many-arguments, too-many-local
388392
389393
Flow:
390394
1) Parse JSONs and gather tests.
391-
2) Insert a ci_runs row and get run_id.
395+
2) Insert a ci_runs_dup row and get run_id.
392396
3) Ensure all tests exist; get (file,class,test) -> test_id map.
393-
4) Bulk insert/update ci_results for this run.
397+
4) Bulk insert/update ci_results_dup for this run.
394398
"""
395-
files = list_test_jsons(logs_dir)
396-
if not files:
397-
print(f"No JSON files under {logs_dir}")
398-
raise SystemExit(2) # input error
399-
400-
created_at, tests = load_from_per_test_jsons(files)
399+
report = find_single_report_json(logs_dir)
400+
created_at, tests = load_from_single_json(report)
401+
401402
if not tests:
402403
print("No tests found in JSONs")
403404
raise SystemExit(2) # input error

0 commit comments

Comments
 (0)