Module crashtest.run
Functions to run tests, collect results, and produce run reports.
Functions
def file_to_name(file: str) ‑> str
-
Expand source code
def file_to_name(file: str) -> str: return " ".join( re.sub( "([A-Z][a-z]+)", r" \1", re.sub("([A-Z]+)", r" \1", file.replace(".hx", "").replace("_", " ")), ).split() ).title()
def gen_id() ‑> str
-
Expand source code
def gen_id() -> str: """ Generate a unique ID for a test run. """ return datetime.datetime.now().strftime("%Y%m%d%H%M%S")
Generate a unique ID for a test run.
def gen_status(results: List[TestCase]) ‑> Tuple[str, str]
-
Expand source code
def gen_status(results: List[TestCase]) -> Tuple[str, str]: """ Generate a status message and color based on test results. Returns a tuple of (status_message, color_hex). Colors: - Green (#22C55E): All tests passed - Yellow (#EAB308): < 10% failures - Orange (#F97316): 10-20% failures - Red-Orange (#EF4444): 20-50% failures - Red (#DC2626): > 50% failures - Dark Red (#991B1B): All tests failed """ if not results: return "No Tests Run", "#6B7280" # Gray for no tests total = len(results) failed = sum(1 for case in results if case.failed) failure_rate = (failed / total) * 100 if failed == 0: return "All tests passed", "#22C55E" elif failed == total: return "All tests failed", "#991B1B" else: if failure_rate < 10: return f"Partial failure ({failure_rate:.1f}%)", "#EAB308" elif failure_rate < 20: return f"Partial failure ({failure_rate:.1f}%)", "#F97316" elif failure_rate < 50: return f"Major failures ({failure_rate:.1f}%)", "#EF4444" else: return f"Critical failures ({failure_rate:.1f}%)", "#DC2626"
Generate a status message and color based on test results. Returns a tuple of (status_message, color_hex).
Colors: - Green (#22C55E): All tests passed - Yellow (#EAB308): < 10% failures - Orange (#F97316): 10-20% failures - Red-Orange (#EF4444): 20-50% failures - Red (#DC2626): > 50% failures - Dark Red (#991B1B): All tests failed
def get_repo_info() ‑> GitInfo
-
Expand source code
def get_repo_info() -> GitInfo: """ Get the git branch and commit hash, if available. """ try: original_dir = os.getcwd() script_dir = os.path.dirname(os.path.abspath(__file__)) os.chdir(script_dir) try: branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]).strip().decode("utf-8") commit = subprocess.check_output(["git", "rev-parse", "HEAD"]).strip().decode("utf-8") dirty = subprocess.check_output(["git", "status", "--porcelain"]).strip().decode("utf-8") != "" return GitInfo( is_release=False, branch=branch, commit=commit[:8], dirty=dirty, github=f"https://github.com/N3rdL0rd/crashlink/commit/{commit}", ) finally: os.chdir(original_dir) except (subprocess.CalledProcessError, FileNotFoundError): return GitInfo(is_release=True, dirty=False)
Get the git branch and commit hash, if available.
def run() ‑> None
-
Expand source code
def run() -> None: """ Run all tests. """ print("Getting repo info...") git = get_repo_info() if git.is_release: print( "Cannot run tests from a release build (eg. installed fro PyPI). Please clone the repo and run from there." ) return # TODO: add support for autodownloading and building test samples print("Finding test cases...") files = os.listdir(os.path.join(os.path.dirname(__file__), "..", "tests", "haxe")) cases = [f for f in files if f.endswith(".hx")] for case in cases: if case.replace(".hx", ".hl") not in files: print(f"Warning: no compiled bytecode found for {case}. Skipping.") cases.remove(case) print("Running tests...") results = [] for i, case in enumerate(cases): print(f"Running {case}...") result = run_case(case, i) results.append(result) print("Generating run...") status, status_color = gen_status(results) r = Run( git=git, context=TestContext(version=globals.VERSION), cases=results, id=gen_id(), timestamp=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), status=status, status_color=status_color, ) os.makedirs(os.path.join(os.path.dirname(__file__), "runs"), exist_ok=True) save_run(r, os.path.join(os.path.dirname(__file__), "runs", f"{gen_id()}.json"))
Run all tests.
def run_case(case: str, id: int) ‑> TestCase
-
Expand source code
def run_case(case: str, id: int) -> TestCase: """ Runs a single test case, handling IR and pseudocode generation separately. """ # Attempt to read the original content try: original_content = open( os.path.join(os.path.dirname(__file__), "..", "tests", "haxe", case), "r", ).read() except Exception as e: tb_last = traceback.format_exc().splitlines()[-1] return TestCase( original=TestFile( name=case, content=escape("Failed to read original file."), ), decompiled=TestFile( name=f"{case.replace('.hx', '')} (Decompiled)", content=escape("Failed to produce pseudocode."), ), ir=TestFile( name=f"{case.replace('.hx', '')} (IR)", content=escape("Failed to produce IR."), ), failed=True, test_name=file_to_name(case), test_id=id, error=escape(f"Failed to read original file: {str(e)}\n{tb_last}"), ) # Load bytecode and create IRFunction code = None irf = None ir_content = "Failed to produce IR." pseudo_content = "Failed to produce pseudocode." ir_error = None pseudo_error = None # First try to load the bytecode try: code = Bytecode.from_path( os.path.join( os.path.dirname(__file__), "..", "tests", "haxe", case.replace(".hx", ".hl"), ) ) irf = decomp.IRFunction(code, code.get_test_main()) except Exception as e: tb_last = traceback.format_exc().splitlines()[-1] ir_error = f"Failed to load bytecode: {str(e)}\n{tb_last}" pseudo_error = f"Failed to load bytecode: {str(e)}\n{tb_last}" return TestCase( original=TestFile( name=case, content=escape(original_content), ), decompiled=TestFile( name=f"{case.replace('.hx', '')} (Decompiled)", content=escape(pseudo_content), ), ir=TestFile(name=f"{case.replace('.hx', '')} (IR)", content=escape(ir_content)), failed=True, test_name=file_to_name(case), test_id=id, error=escape(ir_error), ) # Try to generate IR try: if irf: ir_content = str(irf.block) except Exception as e: tb_last = traceback.format_exc().splitlines()[-1] ir_error = f"Failed to generate IR: {str(e)}\n{tb_last}" # Try to generate pseudocode try: if irf: pseudo_content = pseudo(irf) except Exception as e: tb_last = traceback.format_exc().splitlines()[-1] pseudo_error = f"Failed to generate pseudocode: {str(e)}\n{tb_last}" # Determine if the test failed based on errors failed = bool(ir_error and pseudo_error) # Create the error message with all available information error = None if ir_error or pseudo_error: error_parts = [] if ir_error: error_parts.append(ir_error) if pseudo_error: error_parts.append(pseudo_error) error = escape("\n".join(error_parts)) return TestCase( original=TestFile( name=case, content=escape(original_content), ), decompiled=TestFile( name=f"{case.replace('.hx', '')} (Decompiled)", content=escape(pseudo_content), ), ir=TestFile(name=f"{case.replace('.hx', '')} (IR)", content=escape(ir_content)), failed=failed, test_name=file_to_name(case), test_id=id, error=error, )
Runs a single test case, handling IR and pseudocode generation separately.