-
-
Notifications
You must be signed in to change notification settings - Fork 199
ci: reduce noise while improving debugging capabilities and subprocess management #1373
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 7 commits
0bb6d33
e141dd9
05947af
361b89c
6cae72c
c187a88
5ccaee6
e6038cd
e1cba0b
7f8d71b
7348b73
1e7edca
2826c4e
e21e9df
7bfbdcb
9208fda
c5c6c0a
7c0db5b
dad6a3b
c8335c4
983dccf
bbeb874
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,7 +55,13 @@ def run(cwd, exe, args, env=dict(os.environ), **kwargs): | |
| # older android emulators do not correctly pass down the returncode | ||
| # so we basically echo the return code, and parse it manually | ||
| is_pipe = kwargs.get("stdout") == subprocess.PIPE | ||
| kwargs["stdout"] = subprocess.PIPE | ||
| should_capture_android = not is_pipe and "stdout" not in kwargs | ||
|
|
||
| if should_capture_android: | ||
| # Capture output for potential display on failure | ||
| kwargs["stdout"] = subprocess.PIPE | ||
| kwargs["stderr"] = subprocess.STDOUT | ||
|
|
||
|
||
| child = subprocess.run( | ||
| [ | ||
| "{}/platform-tools/adb".format(os.environ["ANDROID_HOME"]), | ||
|
|
@@ -76,8 +82,35 @@ def run(cwd, exe, args, env=dict(os.environ), **kwargs): | |
| stdout = child.stdout | ||
| child.returncode = int(stdout[stdout.rfind(b"ret:") :][4:]) | ||
| child.stdout = stdout[: stdout.rfind(b"ret:")] | ||
| if not is_pipe: | ||
| sys.stdout.buffer.write(child.stdout) | ||
|
|
||
| # Only write output to stdout if not capturing or on success | ||
| if not should_capture_android or child.returncode == 0: | ||
| if not is_pipe: | ||
| sys.stdout.buffer.write(child.stdout) | ||
vaind marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| elif should_capture_android and child.returncode != 0: | ||
| # Enhanced error reporting for Android test execution failures | ||
| error_details = [] | ||
| error_details.append("=" * 60) | ||
| error_details.append("ANDROID TEST EXECUTION FAILED") | ||
| error_details.append("=" * 60) | ||
| error_details.append(f"Executable: {exe}") | ||
| error_details.append(f"Arguments: {' '.join(args)}") | ||
| error_details.append(f"Return code: {child.returncode}") | ||
|
|
||
| # Display captured output (last 50 lines to avoid too much noise) | ||
| if child.stdout: | ||
| output_text = child.stdout.decode('utf-8', errors='replace') | ||
| output_lines = output_text.strip().split('\n') | ||
| error_details.append("--- OUTPUT (last 50 lines) ---") | ||
| last_lines = output_lines[-50:] if len(output_lines) > 50 else output_lines | ||
| error_details.append('\n'.join(last_lines)) | ||
|
|
||
| error_details.append("=" * 60) | ||
|
|
||
| # Print the detailed error information | ||
| error_message = "\n".join(error_details) | ||
| print(error_message, flush=True) | ||
|
|
||
| if kwargs.get("check") and child.returncode: | ||
| raise subprocess.CalledProcessError( | ||
| child.returncode, child.args, output=child.stdout, stderr=child.stderr | ||
|
|
@@ -114,14 +147,60 @@ def run(cwd, exe, args, env=dict(os.environ), **kwargs): | |
| "--leak-check=yes", | ||
| *cmd, | ||
| ] | ||
| try: | ||
| return subprocess.run([*cmd, *args], cwd=cwd, env=env, **kwargs) | ||
| except subprocess.CalledProcessError: | ||
| raise pytest.fail.Exception( | ||
| "running command failed: {cmd} {args}".format( | ||
| cmd=" ".join(cmd), args=" ".join(args) | ||
| ) | ||
| ) from None | ||
|
|
||
| # Capture output unless explicitly requested to pipe to caller or stream to stdout | ||
| should_capture = kwargs.get("stdout") != subprocess.PIPE and "stdout" not in kwargs | ||
vaind marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| if should_capture: | ||
| # Capture both stdout and stderr for potential display on failure | ||
| kwargs_with_capture = kwargs.copy() | ||
| kwargs_with_capture["stdout"] = subprocess.PIPE | ||
| kwargs_with_capture["stderr"] = subprocess.STDOUT | ||
| kwargs_with_capture["universal_newlines"] = True | ||
|
|
||
| try: | ||
| result = subprocess.run([*cmd, *args], cwd=cwd, env=env, **kwargs_with_capture) | ||
| if result.returncode != 0 and kwargs.get("check"): | ||
| # Enhanced error reporting for test execution failures | ||
| error_details = [] | ||
| error_details.append("=" * 60) | ||
| error_details.append("TEST EXECUTION FAILED") | ||
| error_details.append("=" * 60) | ||
| error_details.append(f"Command: {' '.join(cmd + args)}") | ||
| error_details.append(f"Working directory: {cwd}") | ||
| error_details.append(f"Return code: {result.returncode}") | ||
|
|
||
| # Display captured output (last 50 lines to avoid too much noise) | ||
| if result.stdout: | ||
| output_lines = result.stdout.strip().split('\n') | ||
| error_details.append("--- OUTPUT (last 50 lines) ---") | ||
| last_lines = output_lines[-50:] if len(output_lines) > 50 else output_lines | ||
| error_details.append('\n'.join(last_lines)) | ||
|
|
||
| error_details.append("=" * 60) | ||
|
|
||
| # Print the detailed error information | ||
| error_message = "\n".join(error_details) | ||
| print(error_message, flush=True) | ||
|
|
||
| raise subprocess.CalledProcessError(result.returncode, result.args) | ||
| return result | ||
| except subprocess.CalledProcessError: | ||
| raise pytest.fail.Exception( | ||
| "running command failed: {cmd} {args}".format( | ||
| cmd=" ".join(cmd), args=" ".join(args) | ||
| ) | ||
| ) from None | ||
| else: | ||
| # Use original behavior when stdout is explicitly handled by caller | ||
| try: | ||
| return subprocess.run([*cmd, *args], cwd=cwd, env=env, **kwargs) | ||
| except subprocess.CalledProcessError: | ||
| raise pytest.fail.Exception( | ||
| "running command failed: {cmd} {args}".format( | ||
| cmd=" ".join(cmd), args=" ".join(args) | ||
| ) | ||
| ) from None | ||
|
|
||
|
|
||
| def check_output(*args, **kwargs): | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| """ | ||
| Test enhanced error reporting for cmake failures. | ||
|
|
||
| This test validates that when cmake configure or build fails, detailed error | ||
| information is captured and displayed to help with debugging. | ||
| """ | ||
|
|
||
| import subprocess | ||
| import tempfile | ||
| import os | ||
| import pytest | ||
| from .cmake import cmake | ||
|
|
||
|
|
||
| def test_cmake_error_reporting(tmp_path): | ||
| """Test that cmake failures show detailed error information.""" | ||
| # Create a temporary working directory | ||
| cwd = tmp_path / "build" | ||
| cwd.mkdir() | ||
|
|
||
| # Try to build a non-existent target, which should either: | ||
| # - Fail at configure (if dependencies missing) with "cmake configure failed" | ||
| # - Fail at build (if dependencies available) with "cmake build failed" | ||
| # Both scenarios should show enhanced error reporting | ||
vaind marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| with pytest.raises(pytest.fail.Exception, match="cmake .* failed"): | ||
| cmake(cwd, ["nonexistent_target_that_will_fail"], {}, []) | ||
|
|
||
|
|
||
| def test_cmake_successful_configure_shows_no_extra_output(tmp_path): | ||
| """Test that successful cmake operations don't show error reporting sections.""" | ||
| # This test verifies that our enhanced error reporting doesn't interfere | ||
| # with normal operation by running a simple successful case | ||
| sourcedir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) | ||
|
|
||
| cwd = tmp_path / "build" | ||
| cwd.mkdir() | ||
|
|
||
| # This should succeed without showing error reporting sections | ||
| try: | ||
| cmake( | ||
| cwd, | ||
| ["sentry_test_unit"], | ||
| {"SENTRY_BACKEND": "none", "SENTRY_TRANSPORT": "none"}, | ||
| [], | ||
| ) | ||
| # If we get here, the test passed successfully | ||
| assert True | ||
| except Exception as e: | ||
| # If it fails, make sure it's not due to our error reporting | ||
| assert "CMAKE CONFIGURE FAILED" not in str(e) | ||
| assert "CMAKE BUILD FAILED" not in str(e) | ||
| # Re-raise the original exception if it's a different kind of failure | ||
| raise | ||
|
|
||
|
|
||
| def test_enhanced_error_format(): | ||
| """Test that the error formatting function creates properly formatted output.""" | ||
| # This is a unit test for the error formatting logic we added | ||
|
|
||
| # Create mock subprocess.CalledProcessError | ||
| mock_cmd = ["cmake", "-DTEST=1", "/some/source"] | ||
| mock_cwd = "/some/build/dir" | ||
| mock_returncode = 1 | ||
| mock_stderr = "CMake Error: Invalid option TEST" | ||
| mock_stdout = "-- Configuring incomplete, errors occurred!" | ||
|
|
||
| # Format error details using the same logic as in cmake.py | ||
| error_details = [] | ||
| error_details.append("=" * 60) | ||
| error_details.append("CMAKE CONFIGURE FAILED") | ||
| error_details.append("=" * 60) | ||
| error_details.append(f"Command: {' '.join(mock_cmd)}") | ||
| error_details.append(f"Working directory: {mock_cwd}") | ||
| error_details.append(f"Return code: {mock_returncode}") | ||
|
|
||
| if mock_stderr and mock_stderr.strip(): | ||
| error_details.append("--- STDERR ---") | ||
| error_details.append(mock_stderr.strip()) | ||
| if mock_stdout and mock_stdout.strip(): | ||
| error_details.append("--- STDOUT ---") | ||
| error_details.append(mock_stdout.strip()) | ||
|
|
||
| error_details.append("=" * 60) | ||
| error_message = "\n".join(error_details) | ||
vaind marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| # Verify the formatted output contains expected sections | ||
| assert "CMAKE CONFIGURE FAILED" in error_message | ||
| assert "Command: cmake -DTEST=1 /some/source" in error_message | ||
| assert "Working directory: /some/build/dir" in error_message | ||
| assert "Return code: 1" in error_message | ||
| assert "--- STDERR ---" in error_message | ||
| assert "CMake Error: Invalid option TEST" in error_message | ||
| assert "--- STDOUT ---" in error_message | ||
| assert "-- Configuring incomplete, errors occurred!" in error_message | ||
| assert error_message.count("=" * 60) == 3 # Header, middle, and footer | ||
Uh oh!
There was an error while loading. Please reload this page.