|
6 | 6 | import os
|
7 | 7 | import re
|
8 | 8 | import textwrap
|
| 9 | +from dataclasses import dataclass |
| 10 | +from pathlib import Path |
9 | 11 | from types import ModuleType
|
10 | 12 | from typing import Any, List, Optional, Set
|
11 | 13 |
|
|
22 | 24 | )
|
23 | 25 |
|
24 | 26 |
|
| 27 | +@dataclass |
| 28 | +class EIPVersionFailure: |
| 29 | + """Represents a failure in EIP version check.""" |
| 30 | + |
| 31 | + file_path: str |
| 32 | + eip_spec_name: str |
| 33 | + eip_spec_url: str |
| 34 | + referenced_version: str |
| 35 | + latest_version: str |
| 36 | + |
| 37 | + |
25 | 38 | def pytest_addoption(parser):
|
26 | 39 | """Add Github token option to pytest command line options."""
|
27 | 40 | group = parser.getgroup(
|
@@ -52,6 +65,8 @@ def pytest_configure(config):
|
52 | 65 | "eip_version_check: a test that tests the reference spec defined in an EIP test module.",
|
53 | 66 | )
|
54 | 67 |
|
| 68 | + config._eip_version_failures = [] |
| 69 | + |
55 | 70 | github_token = config.getoption("github_token") or os.environ.get("GITHUB_TOKEN")
|
56 | 71 |
|
57 | 72 | if not github_token:
|
@@ -113,7 +128,21 @@ def is_test_for_an_eip(input_string: str) -> bool:
|
113 | 128 | return False
|
114 | 129 |
|
115 | 130 |
|
116 |
| -def test_eip_spec_version(module: ModuleType, github_token: Optional[str] = None): |
| 131 | +def test_eip_spec_version( |
| 132 | + module: ModuleType, |
| 133 | + github_token: Optional[str] = None, |
| 134 | + config: Optional[pytest.Config] = None, |
| 135 | +): |
| 136 | + # Ensure config is available |
| 137 | + if config is None: |
| 138 | + # This case should ideally not happen if called via EIPSpecTestItem.runtest |
| 139 | + # or if pytest passes config correctly in other scenarios. |
| 140 | + # Fallback or error if essential. For now, assume it's provided. |
| 141 | + pytest.fail( |
| 142 | + "pytest.Config not available in test_eip_spec_version. " |
| 143 | + "This is an issue with the plugin itself." |
| 144 | + ) |
| 145 | + |
117 | 146 | """
|
118 | 147 | Test that the ReferenceSpec object as defined in the test module
|
119 | 148 | is not outdated when compared to the remote hash from
|
@@ -143,6 +172,24 @@ def test_eip_spec_version(module: ModuleType, github_token: Optional[str] = None
|
143 | 172 | f"Reference spec URL: {ref_spec.api_url()}."
|
144 | 173 | ) from e
|
145 | 174 |
|
| 175 | + if not is_up_to_date: |
| 176 | + failure_data = EIPVersionFailure( |
| 177 | + file_path=str(module.__file__), |
| 178 | + eip_spec_name=ref_spec.name(), |
| 179 | + eip_spec_url=ref_spec.api_url(), |
| 180 | + referenced_version=ref_spec.known_version(), |
| 181 | + latest_version=ref_spec.latest_version(), |
| 182 | + ) |
| 183 | + # Ensure _eip_version_failures list exists, though it should by pytest_configure |
| 184 | + if hasattr(config, "_eip_version_failures"): |
| 185 | + config._eip_version_failures.append(failure_data) |
| 186 | + else: |
| 187 | + # This case should not happen if pytest_configure runs as expected. |
| 188 | + # Log an error or handle as appropriate. |
| 189 | + print( |
| 190 | + "Warning: config._eip_version_failures not found. Failure data cannot be recorded." |
| 191 | + ) |
| 192 | + |
146 | 193 | assert is_up_to_date, message
|
147 | 194 |
|
148 | 195 |
|
@@ -189,7 +236,9 @@ def from_parent(cls, parent: Node, **kw: Any) -> "EIPSpecTestItem":
|
189 | 236 |
|
190 | 237 | def runtest(self) -> None:
|
191 | 238 | """Define the test to execute for this item."""
|
192 |
| - test_eip_spec_version(self.module, github_token=self.github_token) |
| 239 | + test_eip_spec_version( |
| 240 | + self.module, github_token=self.github_token, config=self.session.config |
| 241 | + ) |
193 | 242 |
|
194 | 243 | def reportinfo(self) -> tuple[str, int, str]:
|
195 | 244 | """
|
@@ -217,3 +266,84 @@ def pytest_collection_modifyitems(
|
217 | 266 | for item in new_test_eip_spec_version_items:
|
218 | 267 | item.add_marker("eip_version_check", append=True)
|
219 | 268 | items.extend(new_test_eip_spec_version_items)
|
| 269 | + |
| 270 | + |
| 271 | +def generate_eip_report_markdown(failures: List[EIPVersionFailure], run_id: str) -> str: |
| 272 | + """Generate a markdown report for EIP version check failures.""" |
| 273 | + summary_table_header = ( |
| 274 | + "| File Path | EIP | Referenced Version | Latest Version |\n" |
| 275 | + "|-----------|-----|--------------------|----------------|\n" |
| 276 | + ) |
| 277 | + summary_table_rows = [] |
| 278 | + for failure in failures: |
| 279 | + eip_match = re.search(r"eip-?(\d+)", failure.eip_spec_name, re.IGNORECASE) |
| 280 | + if not eip_match: |
| 281 | + eip_match = re.search(r"eip-?(\d+)", failure.file_path, re.IGNORECASE) |
| 282 | + eip_number_str = f"EIP-{eip_match.group(1)}" if eip_match else "Unknown" |
| 283 | + |
| 284 | + # Ensure the URL points to the commit history for better context |
| 285 | + eip_link_url = failure.eip_spec_url.replace("blob/", "commits/") |
| 286 | + |
| 287 | + row = ( |
| 288 | + f"| {failure.file_path} | [{eip_number_str}]({eip_link_url}) | " |
| 289 | + f"`{failure.referenced_version}` | `{failure.latest_version}` |" |
| 290 | + ) |
| 291 | + summary_table_rows.append(row) |
| 292 | + |
| 293 | + fail_details_parts = [] |
| 294 | + for failure in failures: |
| 295 | + detail = ( |
| 296 | + f"File: `{failure.file_path}`\n" |
| 297 | + f"Spec Name: `{failure.eip_spec_name}`\n" |
| 298 | + f"Spec URL: {failure.eip_spec_url}\n" |
| 299 | + f"Referenced Version: `{failure.referenced_version}`\n" |
| 300 | + f"Latest Version: `{failure.latest_version}`\n" |
| 301 | + "---\n" |
| 302 | + ) |
| 303 | + fail_details_parts.append(detail) |
| 304 | + |
| 305 | + report_template = textwrap.dedent( |
| 306 | + """\ |
| 307 | + # EIP Version Check Report (Run ID: {run_id}) |
| 308 | +
|
| 309 | + ## Summary of Outdated EIP References |
| 310 | +
|
| 311 | + {summary_table_header}{summary_table_rows_str} |
| 312 | +
|
| 313 | + ## Outdated EIP References Details |
| 314 | +
|
| 315 | + {fail_details_str} |
| 316 | + """ |
| 317 | + ) |
| 318 | + |
| 319 | + return report_template.format( |
| 320 | + run_id=run_id, |
| 321 | + summary_table_header=summary_table_header, |
| 322 | + summary_table_rows_str="\n".join(summary_table_rows), |
| 323 | + fail_details_str="\n".join(fail_details_parts), |
| 324 | + ) |
| 325 | + |
| 326 | + |
| 327 | +def pytest_sessionfinish(session): |
| 328 | + """ |
| 329 | + Call after the whole test session finishes. |
| 330 | + Generate a report if there are any EIP version failures. |
| 331 | + """ |
| 332 | + failures = session.config._eip_version_failures |
| 333 | + if failures: |
| 334 | + run_id = os.environ.get("GITHUB_RUN_ID", "local-run") |
| 335 | + report_content = generate_eip_report_markdown(failures, run_id) |
| 336 | + report_file_path_str = "./reports/outdated_eips.md" |
| 337 | + |
| 338 | + report_file_path = Path(report_file_path_str) |
| 339 | + report_file_path.parent.mkdir(parents=True, exist_ok=True) |
| 340 | + |
| 341 | + with open(report_file_path, "w") as f: |
| 342 | + f.write(report_content) |
| 343 | + |
| 344 | + print( |
| 345 | + f"\nEIP Version Check: {len(failures)} outdated EIP references found. " |
| 346 | + f"Report generated at {report_file_path_str}" |
| 347 | + ) |
| 348 | + else: |
| 349 | + print("\nEIP Version Check: No outdated EIP references found.") |
0 commit comments