"""Tests for the Gradle wrapper."""
import pytest
import os
from pathlib import Path
from gradle_mcp.gradle import (
GradleWrapper,
GradleTask,
GradleProject,
CompilationError,
FailedTask,
ErrorInfo,
)
class TestGradleWrapper:
"""Test suite for GradleWrapper."""
def test_is_cleaning_task_with_clean(self):
"""Test that 'clean' is recognized as a cleaning task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
assert wrapper._is_cleaning_task("clean") is True
def test_is_cleaning_task_with_clean_build(self):
"""Test that 'cleanBuild' is recognized as a cleaning task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
assert wrapper._is_cleaning_task("cleanBuild") is True
def test_is_cleaning_task_with_clean_test(self):
"""Test that 'cleanTest' is recognized as a cleaning task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
assert wrapper._is_cleaning_task("cleanTest") is True
def test_is_not_cleaning_task_with_build(self):
"""Test that 'build' is not recognized as a cleaning task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
assert wrapper._is_cleaning_task("build") is False
def test_is_not_cleaning_task_with_test(self):
"""Test that 'test' is not recognized as a cleaning task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
assert wrapper._is_cleaning_task("test") is False
def test_is_not_cleaning_task_with_assemble(self):
"""Test that 'assemble' is not recognized as a cleaning task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
assert wrapper._is_cleaning_task("assemble") is False
def test_cleaning_task_case_insensitive(self):
"""Test that cleaning task detection is case-insensitive."""
wrapper = GradleWrapper.__new__(GradleWrapper)
assert wrapper._is_cleaning_task("Clean") is True
assert wrapper._is_cleaning_task("CLEAN") is True
assert wrapper._is_cleaning_task("CLEANBUILD") is True
class TestGradleWrapperEnvConfig:
"""Test suite for environment configuration."""
def test_gradle_project_root_env_var(self):
"""Test that GRADLE_PROJECT_ROOT environment variable is respected."""
# Save original env var if it exists
original = os.getenv("GRADLE_PROJECT_ROOT")
try:
# Set custom project root
os.environ["GRADLE_PROJECT_ROOT"] = "/tmp/test-project"
# This would fail because gradlew doesn't exist, but that's expected
# We're just testing that the env var is read
with pytest.raises(FileNotFoundError):
wrapper = GradleWrapper("/tmp/test-project")
finally:
# Restore original
if original:
os.environ["GRADLE_PROJECT_ROOT"] = original
else:
os.environ.pop("GRADLE_PROJECT_ROOT", None)
def test_gradle_wrapper_custom_path(self):
"""Test that custom wrapper path can be set after initialization."""
wrapper = GradleWrapper.__new__(GradleWrapper)
wrapper.project_root = Path(".")
# Simulate setting a custom wrapper path
custom_path = Path("/custom/path/gradlew")
wrapper.wrapper_script = custom_path
assert wrapper.wrapper_script == custom_path
def test_validate_gradle_args_rejects_tests_for_non_test_task(self):
"""_validate_gradle_args should reject --tests when not running a test task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
# args with --tests should be rejected if tasks do not include a test task
with pytest.raises(ValueError):
wrapper._validate_gradle_args(["--tests", "com.example.MyTest"], tasks=["build"])
def test_validate_gradle_args_allows_tests_for_test_task(self):
"""_validate_gradle_args should allow --tests when running a test task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
# Should not raise for test tasks
wrapper._validate_gradle_args(["--tests", "com.example.MyTest"], tasks=[":module:test"])
def test_validate_gradle_args_allows_tests_with_equals_syntax(self):
"""--tests=... syntax should also be allowed for test tasks."""
wrapper = GradleWrapper.__new__(GradleWrapper)
wrapper._validate_gradle_args(["--tests=com.example.MyTest"], tasks=["test"])
def test_validate_gradle_args_allows_multiple_tests_flags(self):
"""Multiple --tests occurrences should be allowed for test tasks."""
wrapper = GradleWrapper.__new__(GradleWrapper)
# Two separate --tests flags should both be accepted
wrapper._validate_gradle_args([
"--tests",
"com.example.FirstTest",
"--tests",
"com.example.SecondTest",
], tasks=["test"])
def test_validate_gradle_args_allows_tests_with_comma_list(self):
"""--tests with a comma-separated list should be allowed for test tasks."""
wrapper = GradleWrapper.__new__(GradleWrapper)
# A single --tests with comma-separated patterns
wrapper._validate_gradle_args([
"--tests=com.example.FirstTest,com.example.SecondTest"
], tasks=[":module:test"])
class TestGradlePropertiesReading:
"""Test suite for Gradle properties reading."""
def test_read_gradle_properties(self, tmp_path):
"""Test reading gradle.properties file."""
# Create gradle.properties with test content
props_file = tmp_path / "gradle.properties"
props_file.write_text("org.gradle.jvmargs=-Xmx2g\norg.gradle.daemon=true\n")
# Create a fake gradlew script to pass initialization
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
# Create GradleWrapper and verify properties are read
wrapper = GradleWrapper(str(tmp_path))
props = wrapper.gradle_properties
assert props.get("org.gradle.jvmargs") == "-Xmx2g"
assert props.get("org.gradle.daemon") == "true"
def test_read_gradle_properties_with_comments(self, tmp_path):
"""Test that comments are ignored."""
# Create file with # comments
props_file = tmp_path / "gradle.properties"
props_file.write_text(
"# This is a comment\n"
"org.gradle.jvmargs=-Xmx2g\n"
"# Another comment\n"
"org.gradle.daemon=true\n"
" # Comment with leading spaces\n"
)
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
# Verify only key=value pairs are parsed
wrapper = GradleWrapper(str(tmp_path))
props = wrapper.gradle_properties
assert len(props) == 2
assert props.get("org.gradle.jvmargs") == "-Xmx2g"
assert props.get("org.gradle.daemon") == "true"
def test_read_gradle_properties_missing_file(self, tmp_path):
"""Test behavior when gradle.properties doesn't exist."""
# Create a fake gradlew script but no gradle.properties
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
# Create GradleWrapper with no gradle.properties
wrapper = GradleWrapper(str(tmp_path))
# Verify empty dict is returned
props = wrapper.gradle_properties
assert props == {}
def test_read_wrapper_properties(self, tmp_path):
"""Test reading gradle-wrapper.properties."""
# Create gradle/wrapper/gradle-wrapper.properties
wrapper_dir = tmp_path / "gradle" / "wrapper"
wrapper_dir.mkdir(parents=True)
wrapper_props = wrapper_dir / "gradle-wrapper.properties"
wrapper_props.write_text(
"distributionUrl=https\\://services.gradle.org/distributions/gradle-8.5-bin.zip\n"
"distributionBase=GRADLE_USER_HOME\n"
)
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
# Verify distributionUrl is read
wrapper = GradleWrapper(str(tmp_path))
props = wrapper.wrapper_properties
assert "distributionUrl" in props
assert "gradle-8.5-bin.zip" in props["distributionUrl"]
assert props.get("distributionBase") == "GRADLE_USER_HOME"
def test_read_wrapper_properties_missing_file(self, tmp_path):
"""Test behavior when wrapper properties doesn't exist."""
# Create a fake gradlew script but no wrapper properties
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
# Verify empty dict is returned
props = wrapper.wrapper_properties
assert props == {}
class TestEnvironmentHandling:
"""Test suite for environment variable handling."""
def test_build_execution_environment_includes_gradle_opts(self, tmp_path):
"""Test that JVM args from gradle.properties are added to GRADLE_OPTS."""
# Create gradle.properties with org.gradle.jvmargs
props_file = tmp_path / "gradle.properties"
props_file.write_text("org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m\n")
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
# Clear GRADLE_OPTS if present
import os
original_opts = os.environ.pop("GRADLE_OPTS", None)
try:
# Call _build_execution_environment()
env = wrapper._build_execution_environment()
# Verify GRADLE_OPTS includes the jvm args
assert "GRADLE_OPTS" in env
assert "-Xmx2g" in env["GRADLE_OPTS"]
assert "-XX:MaxMetaspaceSize=512m" in env["GRADLE_OPTS"]
finally:
# Restore original
if original_opts:
os.environ["GRADLE_OPTS"] = original_opts
def test_build_execution_environment_merges_existing_opts(self, tmp_path, monkeypatch):
"""Test that existing GRADLE_OPTS are preserved and merged."""
# Set GRADLE_OPTS in environment
monkeypatch.setenv("GRADLE_OPTS", "-Xms512m")
# Create gradle.properties with jvm args
props_file = tmp_path / "gradle.properties"
props_file.write_text("org.gradle.jvmargs=-Xmx2g\n")
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
env = wrapper._build_execution_environment()
# Verify both are in final GRADLE_OPTS
assert "-Xms512m" in env["GRADLE_OPTS"]
assert "-Xmx2g" in env["GRADLE_OPTS"]
def test_build_execution_environment_sets_gradle_user_home(self, tmp_path, monkeypatch):
"""Test that GRADLE_USER_HOME is set if not present."""
# Ensure GRADLE_USER_HOME not in env
monkeypatch.delenv("GRADLE_USER_HOME", raising=False)
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
# Call _build_execution_environment()
env = wrapper._build_execution_environment()
# Verify GRADLE_USER_HOME is set
assert "GRADLE_USER_HOME" in env
assert ".gradle" in env["GRADLE_USER_HOME"]
def test_build_execution_environment_preserves_gradle_user_home(self, tmp_path, monkeypatch):
"""Test that existing GRADLE_USER_HOME is preserved."""
# Set custom GRADLE_USER_HOME
custom_home = "/custom/gradle/home"
monkeypatch.setenv("GRADLE_USER_HOME", custom_home)
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
# Call _build_execution_environment()
env = wrapper._build_execution_environment()
# Verify original value is preserved
assert env["GRADLE_USER_HOME"] == custom_home
class TestErrorParsing:
"""Test suite for error parsing methods."""
def test_parse_compilation_errors_basic(self):
"""Test parsing basic compilation errors."""
wrapper = GradleWrapper.__new__(GradleWrapper)
output = """
> Task :composeApp:compileKotlin FAILED
e: file:///Users/test/project/src/Main.kt:45:49 Argument type mismatch: actual type is 'String', but 'Int' was expected.
e: file:///Users/test/project/src/Main.kt:45:61 No value passed for parameter 'count'.
"""
errors = wrapper._parse_compilation_errors(output)
assert len(errors) == 2
assert errors[0].file == "/Users/test/project/src/Main.kt"
assert errors[0].line == 45
assert errors[0].column == 49
assert "Argument type mismatch" in errors[0].message
assert errors[1].line == 45
assert errors[1].column == 61
assert "No value passed" in errors[1].message
def test_parse_compilation_errors_without_file_prefix(self):
"""Test parsing errors without file:// prefix."""
wrapper = GradleWrapper.__new__(GradleWrapper)
output = "e: /Users/test/src/File.kt:10:5 Unresolved reference: foo"
errors = wrapper._parse_compilation_errors(output)
assert len(errors) == 1
assert errors[0].file == "/Users/test/src/File.kt"
assert errors[0].line == 10
assert errors[0].column == 5
assert "Unresolved reference" in errors[0].message
def test_parse_compilation_errors_deduplication(self):
"""Test that duplicate errors are removed."""
wrapper = GradleWrapper.__new__(GradleWrapper)
# Simulating same error appearing from multiple targets (iOS Arm64 and iOS Simulator)
output = """
e: file:///Users/test/project/src/Test.kt:10:5 Error message
e: file:///Users/test/project/src/Test.kt:10:5 Error message
e: file:///Users/test/project/src/Test.kt:20:10 Different error
"""
errors = wrapper._parse_compilation_errors(output)
# Should have only 2 unique errors
assert len(errors) == 2
assert errors[0].line == 10
assert errors[1].line == 20
def test_parse_compilation_errors_uppercase_e(self):
"""Test parsing errors with uppercase E:."""
wrapper = GradleWrapper.__new__(GradleWrapper)
output = "E: /Users/test/src/File.java:15:3 cannot find symbol"
errors = wrapper._parse_compilation_errors(output)
assert len(errors) == 1
assert errors[0].line == 15
def test_parse_compilation_errors_empty_output(self):
"""Test parsing with no errors."""
wrapper = GradleWrapper.__new__(GradleWrapper)
output = """
> Task :app:build
BUILD SUCCESSFUL in 5s
"""
errors = wrapper._parse_compilation_errors(output)
assert len(errors) == 0
def test_parse_failed_tasks_basic(self):
"""Test parsing failed tasks."""
wrapper = GradleWrapper.__new__(GradleWrapper)
output = """
> Task :core:compileKotlin UP-TO-DATE
> Task :app:compileKotlin FAILED
> Task :lib:compileKotlin FAILED
"""
tasks = wrapper._parse_failed_tasks(output)
assert len(tasks) == 2
assert tasks[0].name == ":app:compileKotlin"
assert tasks[1].name == ":lib:compileKotlin"
def test_parse_failed_tasks_deduplication(self):
"""Test that duplicate failed tasks are removed."""
wrapper = GradleWrapper.__new__(GradleWrapper)
output = """
> Task :app:compileKotlin FAILED
> Task :app:compileKotlin FAILED
"""
tasks = wrapper._parse_failed_tasks(output)
assert len(tasks) == 1
def test_parse_failed_tasks_no_failures(self):
"""Test parsing with no failed tasks."""
wrapper = GradleWrapper.__new__(GradleWrapper)
output = """
> Task :app:compileKotlin UP-TO-DATE
> Task :app:build
BUILD SUCCESSFUL
"""
tasks = wrapper._parse_failed_tasks(output)
assert len(tasks) == 0
def test_generate_error_summary_with_errors(self):
"""Test error summary generation."""
wrapper = GradleWrapper.__new__(GradleWrapper)
failed_tasks = [
FailedTask(name=":app:compileKotlin", reason="Compilation errors"),
FailedTask(name=":lib:compileKotlin", reason="Compilation errors"),
]
compilation_errors = [
CompilationError(file="/test/File1.kt", line=10, column=5, message="error1"),
CompilationError(file="/test/File1.kt", line=20, column=5, message="error2"),
CompilationError(file="/test/File2.kt", line=5, column=1, message="error3"),
]
summary = wrapper._generate_error_summary(failed_tasks, compilation_errors)
assert "2 tasks failed" in summary
assert "3 compilation errors" in summary
assert "2 files" in summary
def test_generate_error_summary_single_task(self):
"""Test summary with single task."""
wrapper = GradleWrapper.__new__(GradleWrapper)
failed_tasks = [FailedTask(name=":app:build", reason="failed")]
compilation_errors = [
CompilationError(file="/test/File.kt", line=10, column=5, message="error"),
]
summary = wrapper._generate_error_summary(failed_tasks, compilation_errors)
assert "1 task failed" in summary
assert "1 compilation error" in summary
assert "1 file" in summary
def test_generate_error_summary_no_errors(self):
"""Test summary with no compilation errors."""
wrapper = GradleWrapper.__new__(GradleWrapper)
failed_tasks = [FailedTask(name=":app:build", reason="failed")]
compilation_errors = []
summary = wrapper._generate_error_summary(failed_tasks, compilation_errors)
assert "1 task failed" in summary
assert "compilation error" not in summary
def test_extract_structured_error_full(self):
"""Test full structured error extraction."""
wrapper = GradleWrapper.__new__(GradleWrapper)
stdout = """
> Task :composeApp:compileTestKotlinIosSimulatorArm64 FAILED
e: file:///Users/test/ProfileContainerTest.kt:45:49 Argument type mismatch: actual type is 'ProfileRepository', but 'String' was expected.
e: file:///Users/test/ProfileContainerTest.kt:45:61 No value passed for parameter 'repository'.
> Task :composeApp:compileTestKotlinIosArm64 FAILED
e: file:///Users/test/ProfileContainerTest.kt:45:49 Argument type mismatch: actual type is 'ProfileRepository', but 'String' was expected.
e: file:///Users/test/ProfileContainerTest.kt:45:61 No value passed for parameter 'repository'.
"""
stderr = "BUILD FAILED in 10s"
error_info = wrapper._extract_structured_error(stdout, stderr)
assert isinstance(error_info, ErrorInfo)
assert "2 tasks failed" in error_info.summary
# Errors should be deduplicated
assert len(error_info.compilation_errors) == 2
assert len(error_info.failed_tasks) == 2
# Verify deduplication worked
messages = [e.message for e in error_info.compilation_errors]
assert "Argument type mismatch" in messages[0]
assert "No value passed" in messages[1]
def test_extract_structured_error_no_compilation_errors(self):
"""Test structured error with no compilation errors."""
wrapper = GradleWrapper.__new__(GradleWrapper)
stdout = "> Task :app:test FAILED"
stderr = """
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:test'.
> There were failing tests. See the report at: file:///Users/test/reports/tests/test/index.html
BUILD FAILED in 30s
"""
error_info = wrapper._extract_structured_error(stdout, stderr)
assert isinstance(error_info, ErrorInfo)
assert len(error_info.failed_tasks) == 1
assert len(error_info.compilation_errors) == 0
assert "1 task failed" in error_info.summary
class TestJvmArgsParsing:
"""Test suite for JVM arguments parsing."""
def test_parse_jvm_args_heap_size(self):
"""Test parsing -Xmx and -Xms arguments."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java -Xmx2g -Xms512m -jar gradle-daemon.jar"
args = wrapper._parse_jvm_args(command_line)
assert "-Xmx2g" in args
assert "-Xms512m" in args
def test_parse_jvm_args_stack_size(self):
"""Test parsing -Xss argument."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java -Xss1m -jar gradle-daemon.jar"
args = wrapper._parse_jvm_args(command_line)
assert "-Xss1m" in args
def test_parse_jvm_args_xx_options(self):
"""Test parsing -XX: JVM options."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -jar gradle.jar"
args = wrapper._parse_jvm_args(command_line)
assert "-XX:MaxMetaspaceSize=512m" in args
assert "-XX:+HeapDumpOnOutOfMemoryError" in args
def test_parse_jvm_args_system_properties(self):
"""Test parsing -D system properties."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java -Duser.language=en -Dfile.encoding=UTF-8 -jar gradle.jar"
args = wrapper._parse_jvm_args(command_line)
assert "-Duser.language=en" in args
assert "-Dfile.encoding=UTF-8" in args
def test_parse_jvm_args_add_opens(self):
"""Test parsing --add-opens arguments."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED -jar gradle.jar"
args = wrapper._parse_jvm_args(command_line)
assert "--add-opens" in args[0]
assert len(args) == 2
def test_parse_jvm_args_add_exports(self):
"""Test parsing --add-exports arguments."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java --add-exports java.base/sun.nio.ch=ALL-UNNAMED -jar gradle.jar"
args = wrapper._parse_jvm_args(command_line)
assert "--add-exports" in args[0]
def test_parse_jvm_args_mixed(self):
"""Test parsing mixed JVM arguments."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java -Xmx2g -Xms512m -XX:MaxMetaspaceSize=256m -Dorg.gradle.daemon=true --add-opens java.base/java.util=ALL-UNNAMED -jar gradle.jar"
args = wrapper._parse_jvm_args(command_line)
assert len(args) == 5
assert "-Xmx2g" in args
assert "-Xms512m" in args
assert "-XX:MaxMetaspaceSize=256m" in args
assert "-Dorg.gradle.daemon=true" in args
def test_parse_jvm_args_empty_string(self):
"""Test parsing empty command line."""
wrapper = GradleWrapper.__new__(GradleWrapper)
args = wrapper._parse_jvm_args("")
assert args == []
def test_parse_jvm_args_none(self):
"""Test parsing None command line."""
wrapper = GradleWrapper.__new__(GradleWrapper)
args = wrapper._parse_jvm_args(None)
assert args == []
def test_parse_jvm_args_with_null_chars(self):
"""Test parsing command line with null characters (from /proc/pid/cmdline)."""
wrapper = GradleWrapper.__new__(GradleWrapper)
# Simulates Linux /proc/pid/cmdline format with null separators
command_line = "java\x00-Xmx2g\x00-Xms512m\x00-jar\x00gradle.jar"
args = wrapper._parse_jvm_args(command_line)
assert "-Xmx2g" in args
assert "-Xms512m" in args
def test_parse_jvm_args_no_jvm_args(self):
"""Test parsing command line with no JVM args."""
wrapper = GradleWrapper.__new__(GradleWrapper)
command_line = "java -jar gradle.jar build"
args = wrapper._parse_jvm_args(command_line)
assert args == []
class TestGetDaemonSpecificConfig:
"""Test suite for get_daemon_specific_config method."""
def test_get_daemon_specific_config_returns_runtime_jvm_args_key(self, tmp_path):
"""Test that get_daemon_specific_config returns runtime_jvm_args key."""
from unittest.mock import patch, MagicMock
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
# Mock subprocess.run to return a command line with JVM args
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "java -Xmx2g -Xms512m -jar gradle-daemon.jar"
with patch('subprocess.run', return_value=mock_result):
with patch('platform.system', return_value='Darwin'):
config = wrapper.get_daemon_specific_config("12345")
assert "runtime_jvm_args" in config
assert isinstance(config["runtime_jvm_args"], list)
def test_get_daemon_specific_config_includes_base_config(self, tmp_path):
"""Test that get_daemon_specific_config includes base config fields."""
from unittest.mock import patch, MagicMock
# Create gradle.properties with some values
props_file = tmp_path / "gradle.properties"
props_file.write_text("org.gradle.jvmargs=-Xmx4g\norg.gradle.daemon=true\n")
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "java -Xmx2g -jar gradle-daemon.jar"
with patch('subprocess.run', return_value=mock_result):
with patch('platform.system', return_value='Darwin'):
config = wrapper.get_daemon_specific_config("12345")
# Should have base config fields
assert "jvm_args" in config
assert "daemon_enabled" in config
assert "parallel_enabled" in config
assert "caching_enabled" in config
assert "max_workers" in config
def test_get_daemon_specific_config_extracts_jvm_args_on_macos(self, tmp_path):
"""Test JVM arg extraction on macOS."""
from unittest.mock import patch, MagicMock
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "java -Xmx4g -Xms1g -XX:MaxMetaspaceSize=512m -Dsome.prop=value -jar gradle-daemon.jar"
with patch('subprocess.run', return_value=mock_result) as mock_run:
with patch('platform.system', return_value='Darwin'):
config = wrapper.get_daemon_specific_config("12345")
# Verify ps command was called with correct args
mock_run.assert_called_once()
call_args = mock_run.call_args[0][0]
assert "ps" in call_args
assert "-p" in call_args
assert "12345" in call_args
# Verify JVM args were extracted
assert "-Xmx4g" in config["runtime_jvm_args"]
assert "-Xms1g" in config["runtime_jvm_args"]
assert "-XX:MaxMetaspaceSize=512m" in config["runtime_jvm_args"]
assert "-Dsome.prop=value" in config["runtime_jvm_args"]
def test_get_daemon_specific_config_handles_process_not_found(self, tmp_path):
"""Test handling when daemon process is not found."""
from unittest.mock import patch, MagicMock
# Create a fake gradlew script
gradlew = tmp_path / "gradlew"
gradlew.write_text("#!/bin/bash\necho 'fake gradlew'")
gradlew.chmod(0o755)
wrapper = GradleWrapper(str(tmp_path))
mock_result = MagicMock()
mock_result.returncode = 1 # Process not found
mock_result.stdout = ""
with patch('subprocess.run', return_value=mock_result):
with patch('platform.system', return_value='Darwin'):
config = wrapper.get_daemon_specific_config("99999")
# Should still return config with empty runtime_jvm_args
assert config["runtime_jvm_args"] == []