add silent and machine readable formats to testing tool
Some checks failed
Continuous Integration / Code Formatting (push) Successful in 31s
Continuous Integration / Code Quality Check (push) Successful in 28s
Continuous Integration / Test Execution (push) Failing after 16s
Continuous Integration / CI Summary (push) Failing after 4s

This commit is contained in:
2025-09-29 12:04:09 +04:00
parent 26991ce61a
commit ad7a2575da
2 changed files with 366 additions and 228 deletions

View File

@@ -84,7 +84,16 @@ jobs:
id: format id: format
run: | run: |
echo "🎨 Running GDScript formatting..." 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 - name: Check for formatting changes
id: check-changes id: check-changes
@@ -156,15 +165,15 @@ jobs:
id: lint id: lint
run: | run: |
echo "🔍 Running GDScript linting..." 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 - name: Upload linting results
if: failure() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: lint-results name: lint-results
path: | path: |
**/*.gd lint_results.yaml
retention-days: 7 retention-days: 7
test: test:
@@ -201,15 +210,15 @@ jobs:
id: test id: test
run: | run: |
echo "🧪 Running GDScript tests..." 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 - name: Upload test results
if: failure() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: test-results name: test-results
path: | path: |
tests/**/*.gd test_results.yaml
retention-days: 7 retention-days: 7
summary: summary:

View File

@@ -9,6 +9,8 @@ Usage examples:
python tools/run_development.py # Run all steps python tools/run_development.py # Run all steps
python tools/run_development.py --lint # Only linting python tools/run_development.py --lint # Only linting
python tools/run_development.py --validate # Only file validation 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 python tools/run_development.py --steps lint test # Custom workflow
Features: Features:
@@ -16,6 +18,7 @@ Features:
- Test execution - Test execution
- YAML, TOML, and JSON file validation (respects .gitignore) - YAML, TOML, and JSON file validation (respects .gitignore)
- Colored output and comprehensive error reporting - Colored output and comprehensive error reporting
- Machine-readable YAML output for CI/CD integration
NOTE: Handles "successful but noisy" linter output such as NOTE: Handles "successful but noisy" linter output such as
"Success: no problems found" - treats these as clean instead of warnings. "Success: no problems found" - treats these as clean instead of warnings.
@@ -75,27 +78,60 @@ class Colors:
return f"{color}{text}{Colors.RESET}" 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.""" """Print a formatted header."""
separator = Colors.colorize("=" * 48, Colors.CYAN) if not silent:
colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE) separator = Colors.colorize("=" * 48, Colors.CYAN)
print(separator) colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE)
print(colored_title) print(separator)
print(separator) print(colored_title)
print() 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.""" """Print a formatted summary."""
separator = Colors.colorize("=" * 48, Colors.CYAN) if not silent:
colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE) separator = Colors.colorize("=" * 48, Colors.CYAN)
print(separator) colored_title = Colors.colorize(title, Colors.BOLD + Colors.WHITE)
print(colored_title) print(separator)
print(separator) print(colored_title)
for key, value in stats.items(): print(separator)
colored_key = Colors.colorize(key, Colors.BLUE) for key, value in stats.items():
colored_value = Colors.colorize(str(value), Colors.BOLD + Colors.WHITE) colored_key = Colors.colorize(key, Colors.BLUE)
print(f"{colored_key}: {colored_value}") 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: 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}") 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.""" """Print command result."""
if success: if success:
if not output: if not output:
message = "✅ Clean" if not silent:
colored_message = Colors.colorize(message, Colors.GREEN) message = "✅ Clean"
colored_message = Colors.colorize(message, Colors.GREEN)
print(f" {colored_message}")
else: else:
message = "⚠️ WARNINGS found:" message = "⚠️ WARNINGS found:"
colored_message = Colors.colorize(message, Colors.YELLOW) colored_message = Colors.colorize(message, Colors.YELLOW)
print(f" {colored_message}") print(f" {colored_message}")
if output:
# Indent and color the output # Indent and color the output
for line in output.split('\n'): for line in output.split('\n'):
if line.strip(): if line.strip():
@@ -283,28 +320,33 @@ def _is_successful_linter_output(output: str) -> bool:
return False 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.""" """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) gd_files = get_gd_files(project_root)
count_msg = f"Found {len(gd_files)} GDScript files to lint." if not silent and not yaml_output:
colored_count = Colors.colorize(count_msg, Colors.BLUE) count_msg = f"Found {len(gd_files)} GDScript files to lint."
print(f"{colored_count}\n") colored_count = Colors.colorize(count_msg, Colors.BLUE)
print(f"{colored_count}\n")
clean_files = warning_files = error_files = 0 clean_files = warning_files = error_files = 0
failed_paths = [] failed_paths = []
for gd_file in gd_files: for gd_file in gd_files:
relative_path = gd_file.relative_to(project_root) relative_path = gd_file.relative_to(project_root)
file_msg = f"📄 Linting: {relative_path.name}" if not silent and not yaml_output:
colored_file = Colors.colorize(file_msg, Colors.CYAN) file_msg = f"📄 Linting: {relative_path.name}"
print(colored_file) colored_file = Colors.colorize(file_msg, Colors.CYAN)
print(colored_file)
if should_skip_file(gd_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 clean_files += 1
print() if not silent and not yaml_output:
print()
continue continue
try: 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 output is "no problems" (or similar), treat as clean.
if _is_successful_linter_output(output): if _is_successful_linter_output(output):
clean_files += 1 clean_files += 1
print_result(True, "") if not yaml_output:
print_result(True, "", silent)
else: else:
warning_files += 1 warning_files += 1
print_result(True, output) if not yaml_output:
print_result(True, output, silent)
else: else:
error_files += 1 error_files += 1
failed_paths.append(str(relative_path)) failed_paths.append(str(relative_path))
print_result(False, output) if not yaml_output:
print_result(False, output, silent)
except FileNotFoundError: except FileNotFoundError:
print(" ❌ ERROR: gdlint not found") if not silent and not yaml_output:
print(" ❌ ERROR: gdlint not found")
return False, {} return False, {}
except Exception as e: except Exception as e:
print(f" ❌ ERROR: {e}") if not silent and not yaml_output:
print(f" ❌ ERROR: {e}")
error_files += 1 error_files += 1
failed_paths.append(str(relative_path)) failed_paths.append(str(relative_path))
print() if not silent and not yaml_output:
print()
# Summary # Summary
stats = { stats = {
@@ -341,22 +389,31 @@ def run_lint(project_root: Path) -> Tuple[bool, Dict]:
"Files with warnings": warning_files, "Files with warnings": warning_files,
"Files with errors": error_files "Files with errors": error_files
} }
print_summary("Linting Summary", stats)
success = error_files == 0 success = error_files == 0
print()
if not success: if yaml_output:
msg = "❌ Linting FAILED - Please fix the errors above" output_yaml_results("lint", {**stats, "failed_paths": failed_paths}, success)
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: else:
msg = "✅ All GDScript files passed linting!" print_summary("Linting Summary", stats, silent)
colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) if not silent:
print(colored_msg) 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} 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}" 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.""" """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 # Get all validation files
validation_files = get_validation_files(project_root) validation_files = get_validation_files(project_root)
total_files = sum(len(files) for files in validation_files.values()) total_files = sum(len(files) for files in validation_files.values())
if total_files == 0: if total_files == 0:
msg = "No YAML, TOML, or JSON files found to validate." if not silent:
colored_msg = Colors.colorize(msg, Colors.YELLOW) msg = "No YAML, TOML, or JSON files found to validate."
print(colored_msg) colored_msg = Colors.colorize(msg, Colors.YELLOW)
print(colored_msg)
return True, {"Total files": 0, "Valid files": 0, "Invalid files": 0} return True, {"Total files": 0, "Valid files": 0, "Invalid files": 0}
count_msg = f"Found {total_files} files to validate:" if not silent and not yaml_output:
colored_count = Colors.colorize(count_msg, Colors.BLUE) count_msg = f"Found {total_files} files to validate:"
print(colored_count) colored_count = Colors.colorize(count_msg, Colors.BLUE)
print(colored_count)
for file_type, files in validation_files.items(): for file_type, files in validation_files.items():
if files: if files:
type_msg = f" {file_type.upper()}: {len(files)} files" type_msg = f" {file_type.upper()}: {len(files)} files"
colored_type = Colors.colorize(type_msg, Colors.CYAN) colored_type = Colors.colorize(type_msg, Colors.CYAN)
print(colored_type) print(colored_type)
print() print()
valid_files = invalid_files = 0 valid_files = invalid_files = 0
failed_paths = [] failed_paths = []
@@ -445,27 +505,32 @@ def run_validate(project_root: Path) -> Tuple[bool, Dict]:
continue continue
validator = validators[file_type] validator = validators[file_type]
type_header = f"🔍 Validating {file_type.upper()} files" if not silent and not yaml_output:
colored_header = Colors.colorize(type_header, Colors.MAGENTA + Colors.BOLD) type_header = f"🔍 Validating {file_type.upper()} files"
print(colored_header) colored_header = Colors.colorize(type_header, Colors.MAGENTA + Colors.BOLD)
print(colored_header)
for file_path in files: for file_path in files:
relative_path = file_path.relative_to(project_root) relative_path = file_path.relative_to(project_root)
file_msg = f"📄 Validating: {relative_path}" if not silent and not yaml_output:
colored_file = Colors.colorize(file_msg, Colors.CYAN) file_msg = f"📄 Validating: {relative_path}"
print(colored_file) colored_file = Colors.colorize(file_msg, Colors.CYAN)
print(colored_file)
is_valid, error_msg = validator(file_path) is_valid, error_msg = validator(file_path)
if is_valid: if is_valid:
valid_files += 1 valid_files += 1
print_result(True, "") if not yaml_output:
print_result(True, "", silent)
else: else:
invalid_files += 1 invalid_files += 1
failed_paths.append(str(relative_path)) 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 # Summary
stats = { stats = {
@@ -473,73 +538,92 @@ def run_validate(project_root: Path) -> Tuple[bool, Dict]:
"Valid files": valid_files, "Valid files": valid_files,
"Invalid files": invalid_files "Invalid files": invalid_files
} }
print_summary("Validation Summary", stats)
success = invalid_files == 0 success = invalid_files == 0
print()
if not success: if yaml_output:
msg = "❌ File validation FAILED - Please fix the syntax errors above" output_yaml_results("validate", {**stats, "failed_paths": failed_paths}, success)
colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD)
print(colored_msg)
else: else:
msg = "✅ All files passed validation!" if not silent:
colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) print_summary("Validation Summary", stats)
print(colored_msg) 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} 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.""" """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) gd_files = get_gd_files(project_root)
count_msg = f"Found {len(gd_files)} GDScript files to format." if not silent and not yaml_output:
colored_count = Colors.colorize(count_msg, Colors.BLUE) count_msg = f"Found {len(gd_files)} GDScript files to format."
print(f"{colored_count}\n") colored_count = Colors.colorize(count_msg, Colors.BLUE)
print(f"{colored_count}\n")
formatted_files = failed_files = 0 formatted_files = failed_files = 0
failed_paths = [] failed_paths = []
for gd_file in gd_files: for gd_file in gd_files:
relative_path = gd_file.relative_to(project_root) relative_path = gd_file.relative_to(project_root)
file_msg = f"🎯 Formatting: {relative_path.name}" if not silent and not yaml_output:
colored_file = Colors.colorize(file_msg, Colors.CYAN) file_msg = f"🎯 Formatting: {relative_path.name}"
print(colored_file) colored_file = Colors.colorize(file_msg, Colors.CYAN)
print(colored_file)
if should_skip_file(gd_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 formatted_files += 1
print() if not silent and not yaml_output:
print()
continue continue
try: try:
result = run_command(["gdformat", str(gd_file)], project_root) result = run_command(["gdformat", str(gd_file)], project_root)
if result.returncode == 0: if result.returncode == 0:
success_msg = "✅ Success" if not silent and not yaml_output:
colored_success = Colors.colorize(success_msg, Colors.GREEN) success_msg = "✅ Success"
print(f" {colored_success}") colored_success = Colors.colorize(success_msg, Colors.GREEN)
print(f" {colored_success}")
formatted_files += 1 formatted_files += 1
else: else:
fail_msg = f"❌ FAILED: {relative_path}" if not silent and not yaml_output:
colored_fail = Colors.colorize(fail_msg, Colors.RED) fail_msg = f"❌ FAILED: {relative_path}"
print(f" {colored_fail}") colored_fail = Colors.colorize(fail_msg, Colors.RED)
output = (result.stdout + result.stderr).strip() print(f" {colored_fail}")
if output: output = (result.stdout + result.stderr).strip()
colored_output = Colors.colorize(output, Colors.RED) if output:
print(f" {colored_output}") colored_output = Colors.colorize(output, Colors.RED)
print(f" {colored_output}")
failed_files += 1 failed_files += 1
failed_paths.append(str(relative_path)) failed_paths.append(str(relative_path))
except FileNotFoundError: except FileNotFoundError:
print(" ❌ ERROR: gdformat not found") if not silent and not yaml_output:
print(" ❌ ERROR: gdformat not found")
return False, {} return False, {}
except Exception as e: except Exception as e:
print(f" ❌ ERROR: {e}") if not silent and not yaml_output:
print(f" ❌ ERROR: {e}")
failed_files += 1 failed_files += 1
failed_paths.append(str(relative_path)) failed_paths.append(str(relative_path))
print() if not silent and not yaml_output:
print()
# Summary # Summary
stats = { stats = {
@@ -547,18 +631,27 @@ def run_format(project_root: Path) -> Tuple[bool, Dict]:
"Successfully formatted": formatted_files, "Successfully formatted": formatted_files,
"Failed": failed_files "Failed": failed_files
} }
print_summary("Formatting Summary", stats)
success = failed_files == 0 success = failed_files == 0
print()
if not success: if yaml_output:
msg = "⚠️ WARNING: Some files failed to format" output_yaml_results("format", {**stats, "failed_paths": failed_paths}, success)
colored_msg = Colors.colorize(msg, Colors.YELLOW + Colors.BOLD)
print(colored_msg)
else: else:
msg = "✅ All GDScript files formatted successfully!" print_summary("Formatting Summary", stats, silent)
colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD) if not silent:
print(colored_msg) 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} 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 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.""" """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) test_files = discover_test_files(project_root)
scan_msg = "🔍 Scanning for test files in tests\\ directory..." if not silent and not yaml_output:
colored_scan = Colors.colorize(scan_msg, Colors.BLUE) scan_msg = "🔍 Scanning for test files in tests\\ directory..."
print(colored_scan) colored_scan = Colors.colorize(scan_msg, Colors.BLUE)
print(colored_scan)
discover_msg = "\n📋 Discovered test files:" discover_msg = "\n📋 Discovered test files:"
colored_discover = Colors.colorize(discover_msg, Colors.CYAN) colored_discover = Colors.colorize(discover_msg, Colors.CYAN)
print(colored_discover) print(colored_discover)
for test_file, prefix in test_files: for test_file, prefix in test_files:
test_name = format_test_name(test_file.stem) test_name = format_test_name(test_file.stem)
file_info = f" {prefix}{test_name}: {test_file}" file_info = f" {prefix}{test_name}: {test_file}"
colored_file_info = Colors.colorize(file_info, Colors.MAGENTA) colored_file_info = Colors.colorize(file_info, Colors.MAGENTA)
print(colored_file_info) print(colored_file_info)
start_msg = "\n🚀 Starting test execution...\n" start_msg = "\n🚀 Starting test execution...\n"
colored_start = Colors.colorize(start_msg, Colors.BLUE + Colors.BOLD) colored_start = Colors.colorize(start_msg, Colors.BLUE + Colors.BOLD)
print(colored_start) print(colored_start)
total_tests = failed_tests = 0 total_tests = failed_tests = 0
test_results = [] test_results = []
@@ -612,13 +707,14 @@ def run_tests(project_root: Path) -> Tuple[bool, Dict]:
test_name = format_test_name(test_file.stem) test_name = format_test_name(test_file.stem)
full_test_name = f"{prefix}{test_name}" full_test_name = f"{prefix}{test_name}"
header_msg = f"=== {full_test_name} ===" if not silent and not yaml_output:
colored_header = Colors.colorize(header_msg, Colors.CYAN + Colors.BOLD) header_msg = f"=== {full_test_name} ==="
print(f"\n{colored_header}") colored_header = Colors.colorize(header_msg, Colors.CYAN + Colors.BOLD)
print(f"\n{colored_header}")
running_msg = f"🎯 Running: {test_file}" running_msg = f"🎯 Running: {test_file}"
colored_running = Colors.colorize(running_msg, Colors.BLUE) colored_running = Colors.colorize(running_msg, Colors.BLUE)
print(colored_running) print(colored_running)
try: try:
result = run_command( result = run_command(
@@ -628,14 +724,18 @@ def run_tests(project_root: Path) -> Tuple[bool, Dict]:
) )
if result.returncode == 0: if result.returncode == 0:
pass_msg = f"✅ PASSED: {full_test_name}" if not silent and not yaml_output:
colored_pass = Colors.colorize(pass_msg, Colors.GREEN + Colors.BOLD) pass_msg = f"✅ PASSED: {full_test_name}"
print(colored_pass) colored_pass = Colors.colorize(pass_msg, Colors.GREEN + Colors.BOLD)
print(colored_pass)
test_results.append((full_test_name, True, "")) test_results.append((full_test_name, True, ""))
else: else:
fail_msg = f"❌ FAILED: {full_test_name}" if not silent and not yaml_output:
colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) fail_msg = f"❌ FAILED: {full_test_name}"
print(colored_fail) 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 failed_tests += 1
error_msg = (result.stderr + result.stdout).strip() or "Unknown error" error_msg = (result.stderr + result.stdout).strip() or "Unknown error"
test_results.append((full_test_name, False, error_msg)) 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 total_tests += 1
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
timeout_msg = f"⏰ FAILED: {full_test_name} (TIMEOUT)" if not silent and not yaml_output:
colored_timeout = Colors.colorize(timeout_msg, Colors.RED + Colors.BOLD) timeout_msg = f"⏰ FAILED: {full_test_name} (TIMEOUT)"
print(colored_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 failed_tests += 1
test_results.append((full_test_name, False, "Test timed out")) test_results.append((full_test_name, False, "Test timed out"))
total_tests += 1 total_tests += 1
except FileNotFoundError: except FileNotFoundError:
error_msg = "❌ ERROR: Godot not found" if not yaml_output and not silent:
colored_error = Colors.colorize(error_msg, Colors.RED + Colors.BOLD) error_msg = "❌ ERROR: Godot not found"
print(colored_error) colored_error = Colors.colorize(error_msg, Colors.RED + Colors.BOLD)
print(colored_error)
return False, {} return False, {}
except Exception as e: except Exception as e:
exc_msg = f"💥 FAILED: {full_test_name} (ERROR: {e})" if not silent and not yaml_output:
colored_exc = Colors.colorize(exc_msg, Colors.RED + Colors.BOLD) exc_msg = f"💥 FAILED: {full_test_name} (ERROR: {e})"
print(colored_exc) 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 failed_tests += 1
test_results.append((full_test_name, False, str(e))) test_results.append((full_test_name, False, str(e)))
total_tests += 1 total_tests += 1
print() if not silent and not yaml_output:
print()
# Summary # Summary
passed_tests = total_tests - failed_tests passed_tests = total_tests - failed_tests
@@ -671,23 +779,28 @@ def run_tests(project_root: Path) -> Tuple[bool, Dict]:
"Tests Passed": passed_tests, "Tests Passed": passed_tests,
"Tests Failed": failed_tests "Tests Failed": failed_tests
} }
print_summary("Test Execution Summary", stats)
success = failed_tests == 0 success = failed_tests == 0
print()
if success: if yaml_output:
msg = "🎉 ALL TESTS PASSED!" output_yaml_results("test", {**stats, "results": test_results}, success)
colored_msg = Colors.colorize(msg, Colors.GREEN + Colors.BOLD)
print(colored_msg)
else: else:
msg = f"💥 {failed_tests} TEST(S) FAILED" print_summary("Test Execution Summary", stats, silent)
colored_msg = Colors.colorize(msg, Colors.RED + Colors.BOLD) if not silent:
print(colored_msg) 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} 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. Execute development workflow steps in sequence.
@@ -700,97 +813,106 @@ def run_workflow(project_root: Path, steps: List[str]) -> bool:
Returns: Returns:
bool: True if all steps completed successfully, False if any failed 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 = { workflow_steps = {
"lint": ("🔍 Code linting (gdlint)", run_lint), "lint": ("🔍 Code linting (gdlint)", lambda root: run_lint(root, silent, yaml_output)),
"format": ("🎨 Code formatting (gdformat)", run_format), "format": ("🎨 Code formatting (gdformat)", lambda root: run_format(root, silent, yaml_output)),
"test": ("🧪 Test execution (godot tests)", run_tests), "test": ("🧪 Test execution (godot tests)", lambda root: run_tests(root, silent, yaml_output)),
"validate": ("📋 File format validation (yaml/toml/json)", run_validate) "validate": ("📋 File format validation (yaml/toml/json)", lambda root: run_validate(root, silent, yaml_output))
} }
intro_msg = "🚀 This script will run the development workflow:" if not silent:
colored_intro = Colors.colorize(intro_msg, Colors.BLUE + Colors.BOLD) intro_msg = "🚀 This script will run the development workflow:"
print(colored_intro) colored_intro = Colors.colorize(intro_msg, Colors.BLUE + Colors.BOLD)
print(colored_intro)
for i, step in enumerate(steps, 1): for i, step in enumerate(steps, 1):
step_name = workflow_steps[step][0] step_name = workflow_steps[step][0]
step_msg = f"{i}. {step_name}" step_msg = f"{i}. {step_name}"
colored_step = Colors.colorize(step_msg, Colors.CYAN) colored_step = Colors.colorize(step_msg, Colors.CYAN)
print(colored_step) print(colored_step)
print() print()
start_time = time.time() start_time = time.time()
results = {} results = {}
for step in steps: for step in steps:
step_name, step_func = workflow_steps[step] step_name, step_func = workflow_steps[step]
separator = Colors.colorize("-" * 48, Colors.MAGENTA) if not silent:
print(separator) separator = Colors.colorize("-" * 48, Colors.MAGENTA)
print(separator)
running_msg = f"⚡ Running {step_name}" running_msg = f"⚡ Running {step_name}"
colored_running = Colors.colorize(running_msg, Colors.BLUE + Colors.BOLD) colored_running = Colors.colorize(running_msg, Colors.BLUE + Colors.BOLD)
print(colored_running) print(colored_running)
print(separator) print(separator)
success, step_results = step_func(project_root) success, step_results = step_func(project_root)
results[step] = step_results results[step] = step_results
if not success: if not silent:
fail_msg = f"{step.upper()} FAILED - Continuing with remaining steps" if not success:
colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) fail_msg = f"{step.upper()} FAILED - Continuing with remaining steps"
print(f"\n{colored_fail}") 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" warning_msg = "⚠️ Issues found, but continuing to provide complete feedback"
colored_warning = Colors.colorize(warning_msg, Colors.YELLOW) colored_warning = Colors.colorize(warning_msg, Colors.YELLOW)
print(colored_warning) print(colored_warning)
status_msg = f"{step_name} completed {'successfully' if success else 'with issues'}" 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) colored_status = Colors.colorize(status_msg, Colors.GREEN if success else Colors.YELLOW)
print(colored_status) print(colored_status)
print() print()
# Final summary # Final summary
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
print_header("📊 Workflow Summary")
if not silent:
print_header("📊 Workflow Summary")
all_success = True all_success = True
any_failures = False any_failures = False
for step in steps: for step in steps:
step_success = results[step].get("Tests Failed", results[step].get("Failed", 0)) == 0 step_success = results[step].get("Tests Failed", results[step].get("Failed", 0)) == 0
status_emoji = "" if step_success else "" if not silent:
status_text = "PASSED" if step_success else "FAILED" status_emoji = "" if step_success else ""
status_color = Colors.GREEN if step_success else Colors.RED 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, "📋") step_emoji = {"lint": "🔍", "format": "🎨", "test": "🧪", "validate": "📋"}.get(step, "📋")
colored_status = Colors.colorize(f"{status_text}", status_color + Colors.BOLD) colored_status = Colors.colorize(f"{status_text}", status_color + Colors.BOLD)
print(f"{step_emoji} {step.capitalize()}: {status_emoji} {colored_status}") print(f"{step_emoji} {step.capitalize()}: {status_emoji} {colored_status}")
if not step_success: if not step_success:
any_failures = True any_failures = True
all_success = False all_success = False
print() if not silent:
if all_success: print()
success_msg = "🎉 ALL WORKFLOW STEPS COMPLETED SUCCESSFULLY!" if all_success:
colored_success = Colors.colorize(success_msg, Colors.GREEN + Colors.BOLD) success_msg = "🎉 ALL WORKFLOW STEPS COMPLETED SUCCESSFULLY!"
print(colored_success) colored_success = Colors.colorize(success_msg, Colors.GREEN + Colors.BOLD)
print(colored_success)
commit_msg = "🚀 Your code is ready for commit." commit_msg = "🚀 Your code is ready for commit."
colored_commit = Colors.colorize(commit_msg, Colors.CYAN) colored_commit = Colors.colorize(commit_msg, Colors.CYAN)
print(colored_commit) print(colored_commit)
else: else:
fail_msg = "❌ WORKFLOW COMPLETED WITH FAILURES" fail_msg = "❌ WORKFLOW COMPLETED WITH FAILURES"
colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD) colored_fail = Colors.colorize(fail_msg, Colors.RED + Colors.BOLD)
print(colored_fail) print(colored_fail)
review_msg = "🔧 Please fix the issues above before committing." review_msg = "🔧 Please fix the issues above before committing."
colored_review = Colors.colorize(review_msg, Colors.RED) colored_review = Colors.colorize(review_msg, Colors.RED)
print(colored_review) 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 return all_success
@@ -803,6 +925,8 @@ def main():
parser.add_argument("--format", action="store_true", help="Run formatting") parser.add_argument("--format", action="store_true", help="Run formatting")
parser.add_argument("--test", action="store_true", help="Run tests") 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("--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() args = parser.parse_args()
project_root = Path(__file__).parent.parent project_root = Path(__file__).parent.parent
@@ -821,10 +945,15 @@ def main():
# Run workflow or individual step # Run workflow or individual step
if len(steps) == 1: 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) success, _ = step_funcs[steps[0]](project_root)
else: else:
success = run_workflow(project_root, steps) success = run_workflow(project_root, steps, args.silent, args.yaml)
sys.exit(0 if success else 1) sys.exit(0 if success else 1)