diff --git a/.github/workflows/parse_logs.py b/.github/workflows/parse_logs.py index 545beaa4167..c0674aeac0b 100644 --- a/.github/workflows/parse_logs.py +++ b/.github/workflows/parse_logs.py @@ -1,57 +1,102 @@ # type: ignore import argparse -import itertools +import functools +import json import pathlib import textwrap +from dataclasses import dataclass -parser = argparse.ArgumentParser() -parser.add_argument("filepaths", nargs="+", type=pathlib.Path) -args = parser.parse_args() +from pytest import CollectReport, TestReport -filepaths = sorted(p for p in args.filepaths if p.is_file()) +@dataclass +class SessionStart: + pytest_version: str + outcome: str = "status" -def extract_short_test_summary_info(lines): - up_to_start_of_section = itertools.dropwhile( - lambda l: "=== short test summary info ===" not in l, - lines, - ) - up_to_section_content = itertools.islice(up_to_start_of_section, 1, None) - section_content = itertools.takewhile( - lambda l: l.startswith("FAILED") or l.startswith("ERROR"), up_to_section_content - ) - content = "\n".join(section_content) + @classmethod + def _from_json(cls, json): + json_ = json.copy() + json_.pop("$report_type") + return cls(**json_) - return content +@dataclass +class SessionFinish: + exitstatus: str + outcome: str = "status" -def format_log_message(path): - py_version = path.name.split("-")[1] - summary = f"Python {py_version} Test Summary Info" - with open(path) as f: - data = extract_short_test_summary_info(line.rstrip() for line in f) - message = ( - textwrap.dedent( - """\ -
{summary} + @classmethod + def _from_json(cls, json): + json_ = json.copy() + json_.pop("$report_type") + return cls(**json_) - ``` - {data} - ``` -
- """ - ) - .rstrip() - .format(summary=summary, data=data) - ) +def parse_record(record): + report_types = { + "TestReport": TestReport, + "CollectReport": CollectReport, + "SessionStart": SessionStart, + "SessionFinish": SessionFinish, + } + cls = report_types.get(record["$report_type"]) + if cls is None: + raise ValueError(f"unknown report type: {record['$report_type']}") + return cls._from_json(record) + + +@functools.singledispatch +def format_summary(report): + return f"{report.nodeid}: {report}" + + +@format_summary.register +def _(report: TestReport): + message = report.longrepr.chain[0][1].message + return f"{report.nodeid}: {message}" + + +@format_summary.register +def _(report: CollectReport): + message = report.longrepr.split("\n")[-1].removeprefix("E").lstrip() + return f"{report.nodeid}: {message}" + + +def format_report(reports, py_version): + newline = "\n" + summaries = newline.join(format_summary(r) for r in reports) + message = textwrap.dedent( + """\ +
Python {py_version} Test Summary + + ``` + {summaries} + ``` + +
+ """ + ).format(summaries=summaries, py_version=py_version) return message -print("Parsing logs ...") -message = "\n\n".join(format_log_message(path) for path in filepaths) +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("filepath", type=pathlib.Path) + args = parser.parse_args() + + py_version = args.filepath.stem.split("-")[1] + + print("Parsing logs ...") + + lines = args.filepath.read_text().splitlines() + reports = [parse_record(json.loads(line)) for line in lines] + + failed = [report for report in reports if report.outcome == "failed"] + + message = format_report(failed, py_version=py_version) -output_file = pathlib.Path("pytest-logs.txt") -print(f"Writing output file to: {output_file.absolute()}") -output_file.write_text(message) + output_file = pathlib.Path("pytest-logs.txt") + print(f"Writing output file to: {output_file.absolute()}") + output_file.write_text(message) diff --git a/.github/workflows/upstream-dev-ci.yaml b/.github/workflows/upstream-dev-ci.yaml index 8dac95b53c0..51ea9720f5b 100644 --- a/.github/workflows/upstream-dev-ci.yaml +++ b/.github/workflows/upstream-dev-ci.yaml @@ -62,6 +62,7 @@ jobs: environment-name: xarray-tests extra-specs: | python=${{ matrix.python-version }} + pytest-reportlog - name: Install upstream versions run: | bash ci/install-upstream-wheels.sh @@ -80,10 +81,11 @@ jobs: if: success() id: status run: | - set -euo pipefail - python -m pytest --timeout=60 -rf | tee output-${{ matrix.python-version }}-log || ( + python -m pytest --timeout=60 -rf \ + --report-log output-${{ matrix.python-version }}-log.jsonl \ + || ( echo '::set-output name=ARTIFACTS_AVAILABLE::true' && false - ) + ) - name: Upload artifacts if: | failure() @@ -92,8 +94,8 @@ jobs: && github.repository == 'pydata/xarray' uses: actions/upload-artifact@v3 with: - name: output-${{ matrix.python-version }}-log - path: output-${{ matrix.python-version }}-log + name: output-${{ matrix.python-version }}-log.jsonl + path: output-${{ matrix.python-version }}-log.jsonl retention-days: 5 report: @@ -119,10 +121,14 @@ jobs: run: | rsync -a /tmp/workspace/logs/output-*/ ./logs ls -R ./logs + - name: install dependencies + run: | + python -m pip install pytest - name: Parse logs run: | shopt -s globstar - python .github/workflows/parse_logs.py logs/**/*-log + python .github/workflows/parse_logs.py logs/**/*-log* + cat pytest-logs.txt - name: Report failures uses: actions/github-script@v6 with: