From ad7a2575dadcb0111d239a1a2e6e59756667d9c2 Mon Sep 17 00:00:00 2001 From: Vladimir nett00n Budylnikov Date: Mon, 29 Sep 2025 12:04:09 +0400 Subject: [PATCH] add silent and machine readable formats to testing tool --- .gitea/workflows/ci.yml | 23 +- tools/run_development.py | 571 ++++++++++++++++++++++++--------------- 2 files changed, 366 insertions(+), 228 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 44b0b5f..8b005c2 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -84,7 +84,16 @@ jobs: id: format run: | echo "๐ŸŽจ Running GDScript formatting..." - python tools/run_development.py --format + python tools/run_development.py --format --silent --yaml > format_results.yaml + + - name: Upload formatting results + if: always() + uses: actions/upload-artifact@v3 + with: + name: format-results + path: | + format_results.yaml + retention-days: 7 - name: Check for formatting changes id: check-changes @@ -156,15 +165,15 @@ jobs: id: lint run: | echo "๐Ÿ” Running GDScript linting..." - python tools/run_development.py --lint + python tools/run_development.py --lint --silent --yaml > lint_results.yaml - name: Upload linting results - if: failure() + if: always() uses: actions/upload-artifact@v3 with: name: lint-results path: | - **/*.gd + lint_results.yaml retention-days: 7 test: @@ -201,15 +210,15 @@ jobs: id: test run: | echo "๐Ÿงช Running GDScript tests..." - python tools/run_development.py --test + python tools/run_development.py --test --silent --yaml > test_results.yaml - name: Upload test results - if: failure() + if: always() uses: actions/upload-artifact@v3 with: name: test-results path: | - tests/**/*.gd + test_results.yaml retention-days: 7 summary: diff --git a/tools/run_development.py b/tools/run_development.py index 5ff5f74..78aad09 100644 --- a/tools/run_development.py +++ b/tools/run_development.py @@ -9,6 +9,8 @@ Usage examples: python tools/run_development.py # Run all steps python tools/run_development.py --lint # Only linting python tools/run_development.py --validate # Only file validation + python tools/run_development.py --validate --silent # Silent validation (only errors) + python tools/run_development.py --test --yaml # Test results in YAML format python tools/run_development.py --steps lint test # Custom workflow Features: @@ -16,6 +18,7 @@ Features: - Test execution - YAML, TOML, and JSON file validation (respects .gitignore) - Colored output and comprehensive error reporting +- Machine-readable YAML output for CI/CD integration NOTE: Handles "successful but noisy" linter output such as "Success: no problems found" - treats these as clean instead of warnings. @@ -75,27 +78,60 @@ class Colors: return f"{color}{text}{Colors.RESET}" -def print_header(title: str) -> None: +def print_header(title: str, silent: bool = False) -> None: """Print a formatted header.""" - separator = Colors.colorize("=" * 48, Colors.CYAN) - colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE) - print(separator) - print(colored_title) - print(separator) - print() + if not silent: + separator = Colors.colorize("=" * 48, Colors.CYAN) + colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE) + print(separator) + print(colored_title) + print(separator) + print() -def print_summary(title: str, stats: Dict[str, int]) -> None: +def print_summary(title: str, stats: Dict[str, int], silent: bool = False) -> None: """Print a formatted summary.""" - separator = Colors.colorize("=" * 48, Colors.CYAN) - colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE) - print(separator) - print(colored_title) - print(separator) - for key, value in stats.items(): - colored_key = Colors.colorize(key, Colors.BLUE) - colored_value = Colors.colorize(str(value), Colors.BOLD + Colors.WHITE) - print(f"{colored_key}: {colored_value}") + if not silent: + separator = Colors.colorize("=" * 48, Colors.CYAN) + colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE) + print(separator) + print(colored_title) + print(separator) + for key, value in stats.items(): + colored_key = Colors.colorize(key, Colors.BLUE) + colored_value = Colors.colorize(str(value), Colors.BOLD + Colors.WHITE) + print(f"{colored_key}: {colored_value}") + + +def output_yaml_results(step_name: str, results: Dict, success: bool) -> None: + """Output results in YAML format.""" + if yaml is None: + print("# YAML output requires PyYAML. Install with: pip install PyYAML") + return + + # Convert results to YAML-friendly format + yaml_data = { + "step": step_name, + "success": success, + "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), + "statistics": {}, + "failed_items": [] + } + + # Extract statistics + for key, value in results.items(): + if key != "failed_paths" and key != "results": + yaml_data["statistics"][key.lower().replace(" ", "_")] = value + + # Extract failed items + if "failed_paths" in results: + yaml_data["failed_items"] = results["failed_paths"] + elif "results" in results: + # For test results, extract failed tests + failed_tests = [result[0] for result in results["results"] if not result[1]] + yaml_data["failed_items"] = failed_tests + + print(yaml.dump(yaml_data, default_flow_style=False, sort_keys=False)) def run_command(cmd: List[str], cwd: Path, timeout: int = 30) -> subprocess.CompletedProcess: @@ -157,17 +193,18 @@ def print_skip_message(tool: str) -> None: print(f" {colored_message}") -def print_result(success: bool, output: str = "") -> None: +def print_result(success: bool, output: str = "", silent: bool = False) -> None: """Print command result.""" if success: if not output: - message = "โœ… Clean" - colored_message = Colors.colorize(message, Colors.GREEN) + if not silent: + message = "โœ… Clean" + colored_message = Colors.colorize(message, Colors.GREEN) + print(f" {colored_message}") else: message = "โš ๏ธ WARNINGS found:" colored_message = Colors.colorize(message, Colors.YELLOW) - print(f" {colored_message}") - if output: + print(f" {colored_message}") # Indent and color the output for line in output.split('\n'): if line.strip(): @@ -283,28 +320,33 @@ def _is_successful_linter_output(output: str) -> bool: return False -def run_lint(project_root: Path) -> Tuple[bool, Dict]: +def run_lint(project_root: Path, silent: bool = False, yaml_output: bool = False) -> Tuple[bool, Dict]: """Run gdlint on all GDScript files.""" - print_header("๐Ÿ” GDScript Linter") + if not yaml_output: + print_header("๐Ÿ” GDScript Linter", silent) gd_files = get_gd_files(project_root) - count_msg = f"Found {len(gd_files)} GDScript files to lint." - colored_count = Colors.colorize(count_msg, Colors.BLUE) - print(f"{colored_count}\n") + if not silent and not yaml_output: + count_msg = f"Found {len(gd_files)} GDScript files to lint." + colored_count = Colors.colorize(count_msg, Colors.BLUE) + print(f"{colored_count}\n") clean_files = warning_files = error_files = 0 failed_paths = [] for gd_file in gd_files: relative_path = gd_file.relative_to(project_root) - file_msg = f"๐Ÿ“„ Linting: {relative_path.name}" - colored_file = Colors.colorize(file_msg, Colors.CYAN) - print(colored_file) + if not silent and not yaml_output: + file_msg = f"๐Ÿ“„ Linting: {relative_path.name}" + colored_file = Colors.colorize(file_msg, Colors.CYAN) + print(colored_file) if should_skip_file(gd_file): - print_skip_message("gdlint") + if not silent and not yaml_output: + print_skip_message("gdlint") clean_files += 1 - print() + if not silent and not yaml_output: + print() continue try: @@ -315,24 +357,30 @@ def run_lint(project_root: Path) -> Tuple[bool, Dict]: # If output is "no problems" (or similar), treat as clean. if _is_successful_linter_output(output): clean_files += 1 - print_result(True, "") + if not yaml_output: + print_result(True, "", silent) else: warning_files += 1 - print_result(True, output) + if not yaml_output: + print_result(True, output, silent) else: error_files += 1 failed_paths.append(str(relative_path)) - print_result(False, output) + if not yaml_output: + print_result(False, output, silent) except FileNotFoundError: - print(" โŒ ERROR: gdlint not found") + if not silent and not yaml_output: + print(" โŒ ERROR: gdlint not found") return False, {} except Exception as e: - print(f" โŒ ERROR: {e}") + if not silent and not yaml_output: + print(f" โŒ ERROR: {e}") error_files += 1 failed_paths.append(str(relative_path)) - print() + if not silent and not yaml_output: + print() # Summary stats = { @@ -341,22 +389,31 @@ def run_lint(project_root: Path) -> Tuple[bool, Dict]: "Files with warnings": warning_files, "Files with errors": error_files } - print_summary("Linting Summary", stats) success = error_files == 0 - print() - if not success: - msg = "โŒ Linting FAILED - Please fix the errors above" - colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD) - print(colored_msg) - elif warning_files > 0: - msg = "โš ๏ธ Linting PASSED with warnings - Consider fixing them" - colored_msg = Colors.colorize(msg, Colors.YELLOW + Colors.BOLD) - print(colored_msg) + + if yaml_output: + output_yaml_results("lint", {**stats, "failed_paths": failed_paths}, success) else: - msg = "โœ… All GDScript files passed linting!" - colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) - print(colored_msg) + print_summary("Linting Summary", stats, silent) + if not silent: + print() + if not success: + msg = "โŒ Linting FAILED - Please fix the errors above" + colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD) + print(colored_msg) + elif warning_files > 0: + msg = "โš ๏ธ Linting PASSED with warnings - Consider fixing them" + colored_msg = Colors.colorize(msg, Colors.YELLOW + Colors.BOLD) + print(colored_msg) + else: + msg = "โœ… All GDScript files passed linting!" + colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) + print(colored_msg) + elif not success: + # In silent mode, still show failed files + for failed_path in failed_paths: + print(f"โŒ {failed_path}") return success, {**stats, "failed_paths": failed_paths} @@ -403,31 +460,34 @@ def validate_json_file(file_path: Path) -> Tuple[bool, str]: return False, f"Error reading file: {e}" -def run_validate(project_root: Path) -> Tuple[bool, Dict]: +def run_validate(project_root: Path, silent: bool = False, yaml_output: bool = False) -> Tuple[bool, Dict]: """Run validation on YAML, TOML, and JSON files.""" - print_header("๐Ÿ“‹ File Format Validation") + if not silent and not yaml_output: + print_header("๐Ÿ“‹ File Format Validation") # Get all validation files validation_files = get_validation_files(project_root) total_files = sum(len(files) for files in validation_files.values()) if total_files == 0: - msg = "No YAML, TOML, or JSON files found to validate." - colored_msg = Colors.colorize(msg, Colors.YELLOW) - print(colored_msg) + if not silent: + msg = "No YAML, TOML, or JSON files found to validate." + colored_msg = Colors.colorize(msg, Colors.YELLOW) + print(colored_msg) return True, {"Total files": 0, "Valid files": 0, "Invalid files": 0} - count_msg = f"Found {total_files} files to validate:" - colored_count = Colors.colorize(count_msg, Colors.BLUE) - print(colored_count) + if not silent and not yaml_output: + count_msg = f"Found {total_files} files to validate:" + colored_count = Colors.colorize(count_msg, Colors.BLUE) + print(colored_count) - for file_type, files in validation_files.items(): - if files: - type_msg = f" {file_type.upper()}: {len(files)} files" - colored_type = Colors.colorize(type_msg, Colors.CYAN) - print(colored_type) + for file_type, files in validation_files.items(): + if files: + type_msg = f" {file_type.upper()}: {len(files)} files" + colored_type = Colors.colorize(type_msg, Colors.CYAN) + print(colored_type) - print() + print() valid_files = invalid_files = 0 failed_paths = [] @@ -445,27 +505,32 @@ def run_validate(project_root: Path) -> Tuple[bool, Dict]: continue validator = validators[file_type] - type_header = f"๐Ÿ” Validating {file_type.upper()} files" - colored_header = Colors.colorize(type_header, Colors.MAGENTA + Colors.BOLD) - print(colored_header) + if not silent and not yaml_output: + type_header = f"๐Ÿ” Validating {file_type.upper()} files" + colored_header = Colors.colorize(type_header, Colors.MAGENTA + Colors.BOLD) + print(colored_header) for file_path in files: relative_path = file_path.relative_to(project_root) - file_msg = f"๐Ÿ“„ Validating: {relative_path}" - colored_file = Colors.colorize(file_msg, Colors.CYAN) - print(colored_file) + if not silent and not yaml_output: + file_msg = f"๐Ÿ“„ Validating: {relative_path}" + colored_file = Colors.colorize(file_msg, Colors.CYAN) + print(colored_file) is_valid, error_msg = validator(file_path) if is_valid: valid_files += 1 - print_result(True, "") + if not yaml_output: + print_result(True, "", silent) else: invalid_files += 1 failed_paths.append(str(relative_path)) - print_result(False, error_msg) + if not yaml_output: + print_result(False, error_msg, silent) - print() + if not silent and not yaml_output: + print() # Summary stats = { @@ -473,73 +538,92 @@ def run_validate(project_root: Path) -> Tuple[bool, Dict]: "Valid files": valid_files, "Invalid files": invalid_files } - print_summary("Validation Summary", stats) success = invalid_files == 0 - print() - if not success: - msg = "โŒ File validation FAILED - Please fix the syntax errors above" - colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD) - print(colored_msg) + + if yaml_output: + output_yaml_results("validate", {**stats, "failed_paths": failed_paths}, success) else: - msg = "โœ… All files passed validation!" - colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) - print(colored_msg) + if not silent: + print_summary("Validation Summary", stats) + print() + if not success: + msg = "โŒ File validation FAILED - Please fix the syntax errors above" + colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD) + print(colored_msg) + else: + msg = "โœ… All files passed validation!" + colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) + print(colored_msg) + elif not success: + # In silent mode, still show errors + for failed_path in failed_paths: + print(f"โŒ {failed_path}") return success, {**stats, "failed_paths": failed_paths} -def run_format(project_root: Path) -> Tuple[bool, Dict]: +def run_format(project_root: Path, silent: bool = False, yaml_output: bool = False) -> Tuple[bool, Dict]: """Run gdformat on all GDScript files.""" - print_header("๐ŸŽจ GDScript Formatter") + if not yaml_output: + print_header("๐ŸŽจ GDScript Formatter", silent) gd_files = get_gd_files(project_root) - count_msg = f"Found {len(gd_files)} GDScript files to format." - colored_count = Colors.colorize(count_msg, Colors.BLUE) - print(f"{colored_count}\n") + if not silent and not yaml_output: + count_msg = f"Found {len(gd_files)} GDScript files to format." + colored_count = Colors.colorize(count_msg, Colors.BLUE) + print(f"{colored_count}\n") formatted_files = failed_files = 0 failed_paths = [] for gd_file in gd_files: relative_path = gd_file.relative_to(project_root) - file_msg = f"๐ŸŽฏ Formatting: {relative_path.name}" - colored_file = Colors.colorize(file_msg, Colors.CYAN) - print(colored_file) + if not silent and not yaml_output: + file_msg = f"๐ŸŽฏ Formatting: {relative_path.name}" + colored_file = Colors.colorize(file_msg, Colors.CYAN) + print(colored_file) if should_skip_file(gd_file): - print_skip_message("gdformat") + if not silent and not yaml_output: + print_skip_message("gdformat") formatted_files += 1 - print() + if not silent and not yaml_output: + print() continue try: result = run_command(["gdformat", str(gd_file)], project_root) if result.returncode == 0: - success_msg = "โœ… Success" - colored_success = Colors.colorize(success_msg, Colors.GREEN) - print(f" {colored_success}") + if not silent and not yaml_output: + success_msg = "โœ… Success" + colored_success = Colors.colorize(success_msg, Colors.GREEN) + print(f" {colored_success}") formatted_files += 1 else: - fail_msg = f"โŒ FAILED: {relative_path}" - colored_fail = Colors.colorize(fail_msg, Colors.RED) - print(f" {colored_fail}") - output = (result.stdout + result.stderr).strip() - if output: - colored_output = Colors.colorize(output, Colors.RED) - print(f" {colored_output}") + if not silent and not yaml_output: + fail_msg = f"โŒ FAILED: {relative_path}" + colored_fail = Colors.colorize(fail_msg, Colors.RED) + print(f" {colored_fail}") + output = (result.stdout + result.stderr).strip() + if output: + colored_output = Colors.colorize(output, Colors.RED) + print(f" {colored_output}") failed_files += 1 failed_paths.append(str(relative_path)) except FileNotFoundError: - print(" โŒ ERROR: gdformat not found") + if not silent and not yaml_output: + print(" โŒ ERROR: gdformat not found") return False, {} except Exception as e: - print(f" โŒ ERROR: {e}") + if not silent and not yaml_output: + print(f" โŒ ERROR: {e}") failed_files += 1 failed_paths.append(str(relative_path)) - print() + if not silent and not yaml_output: + print() # Summary stats = { @@ -547,18 +631,27 @@ def run_format(project_root: Path) -> Tuple[bool, Dict]: "Successfully formatted": formatted_files, "Failed": failed_files } - print_summary("Formatting Summary", stats) success = failed_files == 0 - print() - if not success: - msg = "โš ๏ธ WARNING: Some files failed to format" - colored_msg = Colors.colorize(msg, Colors.YELLOW + Colors.BOLD) - print(colored_msg) + + if yaml_output: + output_yaml_results("format", {**stats, "failed_paths": failed_paths}, success) else: - msg = "โœ… All GDScript files formatted successfully!" - colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) - print(colored_msg) + print_summary("Formatting Summary", stats, silent) + if not silent: + print() + if not success: + msg = "โš ๏ธ WARNING: Some files failed to format" + colored_msg = Colors.colorize(msg, Colors.YELLOW + Colors.BOLD) + print(colored_msg) + else: + msg = "โœ… All GDScript files formatted successfully!" + colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) + print(colored_msg) + elif not success: + # In silent mode, still show failed files + for failed_path in failed_paths: + print(f"โŒ {failed_path}") return success, {**stats, "failed_paths": failed_paths} @@ -581,29 +674,31 @@ def discover_test_files(project_root: Path) -> List[Tuple[Path, str]]: return test_files -def run_tests(project_root: Path) -> Tuple[bool, Dict]: +def run_tests(project_root: Path, silent: bool = False, yaml_output: bool = False) -> Tuple[bool, Dict]: """Run Godot tests.""" - print_header("๐Ÿงช GDScript Test Runner") + if not yaml_output: + print_header("๐Ÿงช GDScript Test Runner", silent) test_files = discover_test_files(project_root) - scan_msg = "๐Ÿ” Scanning for test files in tests\\ directory..." - colored_scan = Colors.colorize(scan_msg, Colors.BLUE) - print(colored_scan) + if not silent and not yaml_output: + scan_msg = "๐Ÿ” Scanning for test files in tests\\ directory..." + colored_scan = Colors.colorize(scan_msg, Colors.BLUE) + print(colored_scan) - discover_msg = "\n๐Ÿ“‹ Discovered test files:" - colored_discover = Colors.colorize(discover_msg, Colors.CYAN) - print(colored_discover) + discover_msg = "\n๐Ÿ“‹ Discovered test files:" + colored_discover = Colors.colorize(discover_msg, Colors.CYAN) + print(colored_discover) - for test_file, prefix in test_files: - test_name = format_test_name(test_file.stem) - file_info = f" {prefix}{test_name}: {test_file}" - colored_file_info = Colors.colorize(file_info, Colors.MAGENTA) - print(colored_file_info) + for test_file, prefix in test_files: + test_name = format_test_name(test_file.stem) + file_info = f" {prefix}{test_name}: {test_file}" + colored_file_info = Colors.colorize(file_info, Colors.MAGENTA) + print(colored_file_info) - start_msg = "\n๐Ÿš€ Starting test execution...\n" - colored_start = Colors.colorize(start_msg, Colors.BLUE + Colors.BOLD) - print(colored_start) + start_msg = "\n๐Ÿš€ Starting test execution...\n" + colored_start = Colors.colorize(start_msg, Colors.BLUE + Colors.BOLD) + print(colored_start) total_tests = failed_tests = 0 test_results = [] @@ -612,13 +707,14 @@ def run_tests(project_root: Path) -> Tuple[bool, Dict]: test_name = format_test_name(test_file.stem) full_test_name = f"{prefix}{test_name}" - header_msg = f"=== {full_test_name} ===" - colored_header = Colors.colorize(header_msg, Colors.CYAN + Colors.BOLD) - print(f"\n{colored_header}") + if not silent and not yaml_output: + header_msg = f"=== {full_test_name} ===" + colored_header = Colors.colorize(header_msg, Colors.CYAN + Colors.BOLD) + print(f"\n{colored_header}") - running_msg = f"๐ŸŽฏ Running: {test_file}" - colored_running = Colors.colorize(running_msg, Colors.BLUE) - print(colored_running) + running_msg = f"๐ŸŽฏ Running: {test_file}" + colored_running = Colors.colorize(running_msg, Colors.BLUE) + print(colored_running) try: result = run_command( @@ -628,14 +724,18 @@ def run_tests(project_root: Path) -> Tuple[bool, Dict]: ) if result.returncode == 0: - pass_msg = f"โœ… PASSED: {full_test_name}" - colored_pass = Colors.colorize(pass_msg, Colors.GREEN + Colors.BOLD) - print(colored_pass) + if not silent and not yaml_output: + pass_msg = f"โœ… PASSED: {full_test_name}" + colored_pass = Colors.colorize(pass_msg, Colors.GREEN + Colors.BOLD) + print(colored_pass) test_results.append((full_test_name, True, "")) else: - fail_msg = f"โŒ FAILED: {full_test_name}" - colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) - print(colored_fail) + if not silent and not yaml_output: + fail_msg = f"โŒ FAILED: {full_test_name}" + colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) + print(colored_fail) + elif silent and not yaml_output: + print(f"โŒ {full_test_name}") failed_tests += 1 error_msg = (result.stderr + result.stdout).strip() or "Unknown error" test_results.append((full_test_name, False, error_msg)) @@ -643,26 +743,34 @@ def run_tests(project_root: Path) -> Tuple[bool, Dict]: total_tests += 1 except subprocess.TimeoutExpired: - timeout_msg = f"โฐ FAILED: {full_test_name} (TIMEOUT)" - colored_timeout = Colors.colorize(timeout_msg, Colors.RED + Colors.BOLD) - print(colored_timeout) + if not silent and not yaml_output: + timeout_msg = f"โฐ FAILED: {full_test_name} (TIMEOUT)" + colored_timeout = Colors.colorize(timeout_msg, Colors.RED + Colors.BOLD) + print(colored_timeout) + elif silent and not yaml_output: + print(f"โฐ {full_test_name} (TIMEOUT)") failed_tests += 1 test_results.append((full_test_name, False, "Test timed out")) total_tests += 1 except FileNotFoundError: - error_msg = "โŒ ERROR: Godot not found" - colored_error = Colors.colorize(error_msg, Colors.RED + Colors.BOLD) - print(colored_error) + if not yaml_output and not silent: + error_msg = "โŒ ERROR: Godot not found" + colored_error = Colors.colorize(error_msg, Colors.RED + Colors.BOLD) + print(colored_error) return False, {} except Exception as e: - exc_msg = f"๐Ÿ’ฅ FAILED: {full_test_name} (ERROR: {e})" - colored_exc = Colors.colorize(exc_msg, Colors.RED + Colors.BOLD) - print(colored_exc) + if not silent and not yaml_output: + exc_msg = f"๐Ÿ’ฅ FAILED: {full_test_name} (ERROR: {e})" + colored_exc = Colors.colorize(exc_msg, Colors.RED + Colors.BOLD) + print(colored_exc) + elif silent and not yaml_output: + print(f"๐Ÿ’ฅ {full_test_name} (ERROR: {e})") failed_tests += 1 test_results.append((full_test_name, False, str(e))) total_tests += 1 - print() + if not silent and not yaml_output: + print() # Summary passed_tests = total_tests - failed_tests @@ -671,23 +779,28 @@ def run_tests(project_root: Path) -> Tuple[bool, Dict]: "Tests Passed": passed_tests, "Tests Failed": failed_tests } - print_summary("Test Execution Summary", stats) success = failed_tests == 0 - print() - if success: - msg = "๐ŸŽ‰ ALL TESTS PASSED!" - colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) - print(colored_msg) + + if yaml_output: + output_yaml_results("test", {**stats, "results": test_results}, success) else: - msg = f"๐Ÿ’ฅ {failed_tests} TEST(S) FAILED" - colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD) - print(colored_msg) + print_summary("Test Execution Summary", stats, silent) + if not silent: + print() + if success: + msg = "๐ŸŽ‰ ALL TESTS PASSED!" + colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) + print(colored_msg) + else: + msg = f"๐Ÿ’ฅ {failed_tests} TEST(S) FAILED" + colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD) + print(colored_msg) return success, {**stats, "results": test_results} -def run_workflow(project_root: Path, steps: List[str]) -> bool: +def run_workflow(project_root: Path, steps: List[str], silent: bool = False, yaml_output: bool = False) -> bool: """ Execute development workflow steps in sequence. @@ -700,97 +813,106 @@ def run_workflow(project_root: Path, steps: List[str]) -> bool: Returns: bool: True if all steps completed successfully, False if any failed """ - print_header("๐Ÿ”„ Development Workflow Runner") + if not silent: + print_header("๐Ÿ”„ Development Workflow Runner") workflow_steps = { - "lint": ("๐Ÿ” Code linting (gdlint)", run_lint), - "format": ("๐ŸŽจ Code formatting (gdformat)", run_format), - "test": ("๐Ÿงช Test execution (godot tests)", run_tests), - "validate": ("๐Ÿ“‹ File format validation (yaml/toml/json)", run_validate) + "lint": ("๐Ÿ” Code linting (gdlint)", lambda root: run_lint(root, silent, yaml_output)), + "format": ("๐ŸŽจ Code formatting (gdformat)", lambda root: run_format(root, silent, yaml_output)), + "test": ("๐Ÿงช Test execution (godot tests)", lambda root: run_tests(root, silent, yaml_output)), + "validate": ("๐Ÿ“‹ File format validation (yaml/toml/json)", lambda root: run_validate(root, silent, yaml_output)) } - intro_msg = "๐Ÿš€ This script will run the development workflow:" - colored_intro = Colors.colorize(intro_msg, Colors.BLUE + Colors.BOLD) - print(colored_intro) + if not silent: + intro_msg = "๐Ÿš€ This script will run the development workflow:" + colored_intro = Colors.colorize(intro_msg, Colors.BLUE + Colors.BOLD) + print(colored_intro) - for i, step in enumerate(steps, 1): - step_name = workflow_steps[step][0] - step_msg = f"{i}. {step_name}" - colored_step = Colors.colorize(step_msg, Colors.CYAN) - print(colored_step) - print() + for i, step in enumerate(steps, 1): + step_name = workflow_steps[step][0] + step_msg = f"{i}. {step_name}" + colored_step = Colors.colorize(step_msg, Colors.CYAN) + print(colored_step) + print() start_time = time.time() results = {} for step in steps: step_name, step_func = workflow_steps[step] - separator = Colors.colorize("-" * 48, Colors.MAGENTA) - print(separator) + if not silent: + separator = Colors.colorize("-" * 48, Colors.MAGENTA) + print(separator) - running_msg = f"โšก Running {step_name}" - colored_running = Colors.colorize(running_msg, Colors.BLUE + Colors.BOLD) - print(colored_running) - print(separator) + running_msg = f"โšก Running {step_name}" + colored_running = Colors.colorize(running_msg, Colors.BLUE + Colors.BOLD) + print(colored_running) + print(separator) success, step_results = step_func(project_root) results[step] = step_results - if not success: - fail_msg = f"โŒ {step.upper()} FAILED - Continuing with remaining steps" - colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) - print(f"\n{colored_fail}") + if not silent: + if not success: + fail_msg = f"โŒ {step.upper()} FAILED - Continuing with remaining steps" + colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) + print(f"\n{colored_fail}") - warning_msg = "โš ๏ธ Issues found, but continuing to provide complete feedback" - colored_warning = Colors.colorize(warning_msg, Colors.YELLOW) - print(colored_warning) + warning_msg = "โš ๏ธ Issues found, but continuing to provide complete feedback" + colored_warning = Colors.colorize(warning_msg, Colors.YELLOW) + print(colored_warning) - status_msg = f"โœ… {step_name} completed {'successfully' if success else 'with issues'}" - colored_status = Colors.colorize(status_msg, Colors.GREEN if success else Colors.YELLOW) - print(colored_status) - print() + status_msg = f"โœ… {step_name} completed {'successfully' if success else 'with issues'}" + colored_status = Colors.colorize(status_msg, Colors.GREEN if success else Colors.YELLOW) + print(colored_status) + print() # Final summary elapsed_time = time.time() - start_time - print_header("๐Ÿ“Š Workflow Summary") + + if not silent: + print_header("๐Ÿ“Š Workflow Summary") all_success = True any_failures = False for step in steps: step_success = results[step].get("Tests Failed", results[step].get("Failed", 0)) == 0 - status_emoji = "โœ…" if step_success else "โŒ" - status_text = "PASSED" if step_success else "FAILED" - status_color = Colors.GREEN if step_success else Colors.RED + if not silent: + status_emoji = "โœ…" if step_success else "โŒ" + status_text = "PASSED" if step_success else "FAILED" + status_color = Colors.GREEN if step_success else Colors.RED - step_emoji = {"lint": "๐Ÿ”", "format": "๐ŸŽจ", "test": "๐Ÿงช", "validate": "๐Ÿ“‹"}.get(step, "๐Ÿ“‹") - colored_status = Colors.colorize(f"{status_text}", status_color + Colors.BOLD) - print(f"{step_emoji} {step.capitalize()}: {status_emoji} {colored_status}") + step_emoji = {"lint": "๐Ÿ”", "format": "๐ŸŽจ", "test": "๐Ÿงช", "validate": "๐Ÿ“‹"}.get(step, "๐Ÿ“‹") + colored_status = Colors.colorize(f"{status_text}", status_color + Colors.BOLD) + print(f"{step_emoji} {step.capitalize()}: {status_emoji} {colored_status}") if not step_success: any_failures = True all_success = False - print() - if all_success: - success_msg = "๐ŸŽ‰ ALL WORKFLOW STEPS COMPLETED SUCCESSFULLY!" - colored_success = Colors.colorize(success_msg, Colors.GREEN + Colors.BOLD) - print(colored_success) + if not silent: + print() + if all_success: + success_msg = "๐ŸŽ‰ ALL WORKFLOW STEPS COMPLETED SUCCESSFULLY!" + colored_success = Colors.colorize(success_msg, Colors.GREEN + Colors.BOLD) + print(colored_success) - commit_msg = "๐Ÿš€ Your code is ready for commit." - colored_commit = Colors.colorize(commit_msg, Colors.CYAN) - print(colored_commit) - else: - fail_msg = "โŒ WORKFLOW COMPLETED WITH FAILURES" - colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) - print(colored_fail) + commit_msg = "๐Ÿš€ Your code is ready for commit." + colored_commit = Colors.colorize(commit_msg, Colors.CYAN) + print(colored_commit) + else: + fail_msg = "โŒ WORKFLOW COMPLETED WITH FAILURES" + colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) + print(colored_fail) - review_msg = "๐Ÿ”ง Please fix the issues above before committing." - colored_review = Colors.colorize(review_msg, Colors.RED) - print(colored_review) + review_msg = "๐Ÿ”ง Please fix the issues above before committing." + colored_review = Colors.colorize(review_msg, Colors.RED) + print(colored_review) + + time_msg = f"โฑ๏ธ Elapsed time: {elapsed_time:.1f} seconds" + colored_time = Colors.colorize(time_msg, Colors.MAGENTA) + print(f"\n{colored_time}") - time_msg = f"โฑ๏ธ Elapsed time: {elapsed_time:.1f} seconds" - colored_time = Colors.colorize(time_msg, Colors.MAGENTA) - print(f"\n{colored_time}") return all_success @@ -803,6 +925,8 @@ def main(): parser.add_argument("--format", action="store_true", help="Run formatting") parser.add_argument("--test", action="store_true", help="Run tests") parser.add_argument("--validate", action="store_true", help="Run file format validation") + parser.add_argument("--silent", "-s", action="store_true", help="Silent mode - hide success messages, only show errors") + parser.add_argument("--yaml", action="store_true", help="Output results in machine-readable YAML format") args = parser.parse_args() project_root = Path(__file__).parent.parent @@ -821,10 +945,15 @@ def main(): # Run workflow or individual step if len(steps) == 1: - step_funcs = {"lint": run_lint, "format": run_format, "test": run_tests, "validate": run_validate} + step_funcs = { + "lint": lambda root: run_lint(root, args.silent, args.yaml), + "format": lambda root: run_format(root, args.silent, args.yaml), + "test": lambda root: run_tests(root, args.silent, args.yaml), + "validate": lambda root: run_validate(root, args.silent, args.yaml) + } success, _ = step_funcs[steps[0]](project_root) else: - success = run_workflow(project_root, steps) + success = run_workflow(project_root, steps, args.silent, args.yaml) sys.exit(0 if success else 1)