From bb03226702b3df4e5644babd5af1d39c13ed7bb6 Mon Sep 17 00:00:00 2001 From: Christian Pieczewski Date: Fri, 8 May 2026 19:28:50 +0200 Subject: [PATCH 1/3] feat: Support multi-module Java projects in tasks Previously, Zed's Java tasks for running and testing applications did not correctly support multi-module Maven or Gradle projects. When executing a task from a file within a submodule, the commands would attempt to run at the root level, potentially leading to incorrect execution or failures. This change introduces logic to dynamically determine the nearest Maven `pom.xml` or Gradle `build.gradle`/`settings.gradle` file relative to the currently active file. It then modifies the Maven and Gradle commands to target the specific module. For Maven, this involves adding the `-pl ` and `-am` flags. For Gradle, it prepends the module path (e.g., `:module-name:`) to the `run` or `test` command. This ensures that tasks are executed within the context of the correct module, improving the developer experience for multi-module Java projects. --- languages/java/tasks.json | 8 +- tests/task_verification_test.rs | 222 ++++++++++++++++++++++++++++++++ 2 files changed, 226 insertions(+), 4 deletions(-) create mode 100644 tests/task_verification_test.rs diff --git a/languages/java/tasks.json b/languages/java/tasks.json index ac2bfe8..b3b4b6f 100644 --- a/languages/java/tasks.json +++ b/languages/java/tasks.json @@ -1,7 +1,7 @@ [ { "label": "Run $ZED_CUSTOM_java_class_name", - "command": "pkg=\"${ZED_CUSTOM_java_package_name:}\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean compile exec:java -Dexec.mainClass=\"$c\"; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;", + "command": "pkg=\"$ZED_CUSTOM_java_package_name\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test-compile exec:java -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; else $CMD clean test-compile -pl \"$m\" -am && $CMD exec:java -pl \"$m\" -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;", "use_new_terminal": false, "reveal": "always", "tags": ["java-main"], @@ -14,7 +14,7 @@ }, { "label": "$ZED_CUSTOM_java_class_name.${ZED_CUSTOM_java_outer_class_name:}.$ZED_CUSTOM_java_method_name", - "command": "package=\"$ZED_CUSTOM_java_package_name\"; outer=\"${ZED_CUSTOM_java_outer_class_name:}\"; inner=\"$ZED_CUSTOM_java_class_name\"; method=\"$ZED_CUSTOM_java_method_name\"; sep=\"$\"; if [ -n \"$outer\" ]; then c=\"$outer$sep$inner\"; else c=\"$inner\"; fi; if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean test -Dtest=\"$package.$c#$method\"; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD test --tests \"$package.$c.$method\"; else >&2 echo 'No build tool found'; exit 1; fi;", + "command": "package=\"$ZED_CUSTOM_java_package_name\"; outer=\"${ZED_CUSTOM_java_outer_class_name:}\"; inner=\"$ZED_CUSTOM_java_class_name\"; method=\"$ZED_CUSTOM_java_method_name\"; sep=\"$\"; if [ -n \"$outer\" ]; then c=\"$outer$sep$inner\"; else c=\"$inner\"; fi; f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test -Dtest=\"$package.$c#$method\"; else $CMD clean test-compile -pl \"$m\" -am && $CMD test -pl \"$m\" -Dtest=\"$package.$c#$method\"; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:test --tests \"$package.$c.$method\"; else >&2 echo 'No build tool found'; exit 1; fi;", "use_new_terminal": false, "reveal": "always", "tags": ["java-test-method", "java-test-method-nested"], @@ -27,7 +27,7 @@ }, { "label": "Test class $ZED_CUSTOM_java_class_name", - "command": "package=\"$ZED_CUSTOM_java_package_name\"; outer=\"${ZED_CUSTOM_java_outer_class_name:}\"; inner=\"$ZED_CUSTOM_java_class_name\"; sep=\"$\"; if [ -n \"$outer\" ]; then c=\"$outer$sep$inner\"; else c=\"$inner\"; fi; if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean test -Dtest=\"$package.$c\"; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD test --tests \"$package.$c\"; else >&2 echo 'No build tool found'; exit 1; fi;", + "command": "package=\"$ZED_CUSTOM_java_package_name\"; outer=\"${ZED_CUSTOM_java_outer_class_name:}\"; inner=\"$ZED_CUSTOM_java_class_name\"; sep=\"$\"; if [ -n \"$outer\" ]; then c=\"$outer$sep$inner\"; else c=\"$inner\"; fi; f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test -Dtest=\"$package.$c\"; else $CMD clean test-compile -pl \"$m\" -am && $CMD test -pl \"$m\" -Dtest=\"$package.$c\"; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:test --tests \"$package.$c\"; else >&2 echo 'No build tool found'; exit 1; fi;", "use_new_terminal": false, "reveal": "always", "tags": ["java-test-class", "java-test-class-nested"], @@ -40,7 +40,7 @@ }, { "label": "Run tests", - "command": "if [ -f pom.xml ]; then [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; $CMD clean test; elif [ -f build.gradle ] || [ -f build.gradle.kts ]; then [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD test; else >&2 echo 'No build tool found'; exit 1; fi;", + "command": "f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test; else $CMD clean test-compile -pl \"$m\" -am && $CMD test -pl \"$m\"; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:test; else >&2 echo 'No build tool found'; exit 1; fi;", "use_new_terminal": false, "reveal": "always", "shell": { diff --git a/tests/task_verification_test.rs b/tests/task_verification_test.rs new file mode 100644 index 0000000..1f8d79b --- /dev/null +++ b/tests/task_verification_test.rs @@ -0,0 +1,222 @@ +use serde_json::Value; +use std::fs; +use std::path::{Path, PathBuf}; + +fn setup_mock_project( + temp_dir: &Path, + project_type: &str, + is_multi: bool, +) -> (PathBuf, PathBuf) { + let module_dir = if is_multi { + temp_dir.join("module-a") + } else { + temp_dir.to_path_buf() + }; + + let package_dir = if project_type == "maven" && is_multi { + module_dir.join("src/test/java/com/example") + } else { + module_dir.join("src/main/java/com/example") + }; + + fs::create_dir_all(&package_dir).unwrap(); + + let bin_dir = temp_dir.join("bin"); + fs::create_dir_all(&bin_dir).unwrap(); + + if project_type == "maven" { + fs::File::create(temp_dir.join("pom.xml")).unwrap(); + if is_multi { + fs::File::create(module_dir.join("pom.xml")).unwrap(); + } + let mvn_mock = bin_dir.join("mvn"); + fs::write(&mvn_mock, "#!/bin/sh\necho \"MVN_CALLED: $@\"").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&mvn_mock, fs::Permissions::from_mode(0o755)).unwrap(); + } + } else { + fs::File::create(temp_dir.join("settings.gradle")).unwrap(); + if is_multi { + fs::File::create(module_dir.join("build.gradle")).unwrap(); + } + let gradle_mock = bin_dir.join("gradle"); + fs::write(&gradle_mock, "#!/bin/sh\necho \"GRADLE_CALLED: $@\"").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&gradle_mock, fs::Permissions::from_mode(0o755)).unwrap(); + } + } + + let zed_file = package_dir.join("Main.java"); + fs::File::create(&zed_file).unwrap(); + + (zed_file, bin_dir) +} + +#[test] +fn test_maven_multi_module_command_logic() { + use std::process::Command as StdCommand; + + let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); + let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); + let mut run_command = tasks[0]["command"].as_str().expect("Command is not a string").to_string(); + + run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_maven_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", true); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("MVN_CALLED: clean test-compile -pl module-a -am"), "Should build submodule with dependencies. Got: {}", stdout); + assert!(stdout.contains("MVN_CALLED: exec:java -pl module-a"), "Should run only the submodule. Got: {}", stdout); + assert!(stdout.contains("-Dexec.classpathScope=test"), "Should use test classpath scope. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + +#[test] +fn test_maven_multi_module_test_method_logic() { + use std::process::Command as StdCommand; + + let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); + let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); + let mut test_command = tasks[1]["command"].as_str().expect("Command is not a string").to_string(); + + test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_maven_method_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", true); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&test_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("ZED_CUSTOM_java_method_name", "shouldPersist") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("MVN_CALLED: clean test-compile -pl module-a -am"), "Should build submodule with dependencies. Got: {}", stdout); + assert!(stdout.contains("MVN_CALLED: test -pl module-a -Dtest=com.example.Main#shouldPersist"), "Should run only the submodule test. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + +#[test] +fn test_gradle_multi_module_command_logic() { + use std::process::Command as StdCommand; + + let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); + let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); + let mut run_command = tasks[0]["command"].as_str().expect("Command is not a string").to_string(); + + run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", true); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("GRADLE_CALLED: :module-a:run"), "Should run with correct module path. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + +#[test] +fn test_gradle_multi_module_test_method_logic() { + use std::process::Command as StdCommand; + + let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); + let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); + let mut test_command = tasks[1]["command"].as_str().expect("Command is not a string").to_string(); + + test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_method_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", true); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&test_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("ZED_CUSTOM_java_method_name", "shouldPersist") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("GRADLE_CALLED: :module-a:test --tests com.example.Main.shouldPersist"), "Should run submodule test with correct path. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} From c259befa837fcdee4b6801db121943c00f93f4bd Mon Sep 17 00:00:00 2001 From: Christian Pieczewski Date: Mon, 11 May 2026 10:01:03 +0200 Subject: [PATCH 2/3] feat: Add comprehensive tests for Java task commands Introduces a suite of integration tests for Java task commands across various project configurations and build tools. This includes: - Added to tasks in to allow programmatic identification and testing of commands. - Refactored to support flexible module paths. - Introduced to retrieve task commands robustly by their tags. - Added new tests for Maven projects covering single, multi-module, and nested module execution, and nested class test methods. - Added new tests for Gradle projects covering single, multi-module, and nested module execution, and running all tests. --- languages/java/tasks.json | 2 + tests/task_verification_test.rs | 329 ++++++++++++++++++++++++++++++-- 2 files changed, 311 insertions(+), 20 deletions(-) diff --git a/languages/java/tasks.json b/languages/java/tasks.json index b3b4b6f..cf319ee 100644 --- a/languages/java/tasks.json +++ b/languages/java/tasks.json @@ -43,6 +43,7 @@ "command": "f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test; else $CMD clean test-compile -pl \"$m\" -am && $CMD test -pl \"$m\"; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:test; else >&2 echo 'No build tool found'; exit 1; fi;", "use_new_terminal": false, "reveal": "always", + "tags": ["java-test-all"], "shell": { "with_arguments": { "program": "/bin/sh", @@ -55,6 +56,7 @@ "command": "cache_dir=\"\"; if [ -n \"$XDG_CACHE_HOME\" ]; then cache_dir=\"$XDG_CACHE_HOME\"; elif [ \"$(uname)\" = \"Darwin\" ]; then cache_dir=\"$HOME/Library/Caches\"; else cache_dir=\"$HOME/.cache\"; fi; found=$(find \"$cache_dir\" -maxdepth 1 -type d -name 'jdtls-*' 2>/dev/null); if [ -n \"$found\" ]; then echo \"$found\" | xargs rm -rf && echo 'JDTLS cache cleared. Restart the language server'; else echo 'No JDTLS cache found'; fi", "use_new_terminal": false, "reveal": "always", + "tags": ["java-clear-cache"], "shell": { "with_arguments": { "program": "/bin/sh", diff --git a/tests/task_verification_test.rs b/tests/task_verification_test.rs index 1f8d79b..74d02eb 100644 --- a/tests/task_verification_test.rs +++ b/tests/task_verification_test.rs @@ -5,14 +5,17 @@ use std::path::{Path, PathBuf}; fn setup_mock_project( temp_dir: &Path, project_type: &str, - is_multi: bool, + module_path: Option<&str>, ) -> (PathBuf, PathBuf) { - let module_dir = if is_multi { - temp_dir.join("module-a") + let module_dir = if let Some(path) = module_path { + let d = temp_dir.join(path); + fs::create_dir_all(&d).unwrap(); + d } else { temp_dir.to_path_buf() }; + let is_multi = module_path.is_some(); let package_dir = if project_type == "maven" && is_multi { module_dir.join("src/test/java/com/example") } else { @@ -36,7 +39,7 @@ fn setup_mock_project( use std::os::unix::fs::PermissionsExt; fs::set_permissions(&mvn_mock, fs::Permissions::from_mode(0o755)).unwrap(); } - } else { + } else if project_type == "gradle" { fs::File::create(temp_dir.join("settings.gradle")).unwrap(); if is_multi { fs::File::create(module_dir.join("build.gradle")).unwrap(); @@ -56,13 +59,26 @@ fn setup_mock_project( (zed_file, bin_dir) } +fn get_task_command_by_tag(tag: &str) -> String { + let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); + let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); + let tasks_array = tasks.as_array().expect("tasks.json is not an array"); + + for task in tasks_array { + if let Some(tags) = task["tags"].as_array() { + if tags.iter().any(|t| t.as_str() == Some(tag)) { + return task["command"].as_str().expect("Command is not a string").to_string(); + } + } + } + panic!("Task with tag '{}' not found", tag); +} + #[test] fn test_maven_multi_module_command_logic() { use std::process::Command as StdCommand; - let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); - let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); - let mut run_command = tasks[0]["command"].as_str().expect("Command is not a string").to_string(); + let mut run_command = get_task_command_by_tag("java-main"); run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); @@ -72,7 +88,7 @@ fn test_maven_multi_module_command_logic() { } fs::create_dir_all(&temp_dir).unwrap(); - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", true); + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("module-a")); let old_path = std::env::var("PATH").unwrap_or_default(); let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); @@ -98,13 +114,89 @@ fn test_maven_multi_module_command_logic() { fs::remove_dir_all(&temp_dir).unwrap(); } +#[test] +fn test_maven_single_module_command_logic() { + use std::process::Command as StdCommand; + + let mut run_command = get_task_command_by_tag("java-main"); + + run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_maven_single_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", None); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("MVN_CALLED: clean test-compile exec:java -Dexec.mainClass=com.example.Main"), "Should run as single module. Got: {}", stdout); + assert!(stdout.contains("-Dexec.classpathScope=test"), "Should use test classpath scope. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + +#[test] +fn test_maven_nested_module_command_logic() { + use std::process::Command as StdCommand; + + let mut run_command = get_task_command_by_tag("java-main"); + + run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_maven_nested_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("nested/module-b")); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("MVN_CALLED: clean test-compile -pl nested/module-b -am"), "Should build nested submodule with dependencies. Got: {}", stdout); + assert!(stdout.contains("MVN_CALLED: exec:java -pl nested/module-b"), "Should run only the nested submodule. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + #[test] fn test_maven_multi_module_test_method_logic() { use std::process::Command as StdCommand; - let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); - let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); - let mut test_command = tasks[1]["command"].as_str().expect("Command is not a string").to_string(); + let mut test_command = get_task_command_by_tag("java-test-method"); test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); @@ -114,7 +206,7 @@ fn test_maven_multi_module_test_method_logic() { } fs::create_dir_all(&temp_dir).unwrap(); - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", true); + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("module-a")); let old_path = std::env::var("PATH").unwrap_or_default(); let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); @@ -144,9 +236,7 @@ fn test_maven_multi_module_test_method_logic() { fn test_gradle_multi_module_command_logic() { use std::process::Command as StdCommand; - let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); - let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); - let mut run_command = tasks[0]["command"].as_str().expect("Command is not a string").to_string(); + let mut run_command = get_task_command_by_tag("java-main"); run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); @@ -156,7 +246,7 @@ fn test_gradle_multi_module_command_logic() { } fs::create_dir_all(&temp_dir).unwrap(); - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", true); + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("module-a")); let old_path = std::env::var("PATH").unwrap_or_default(); let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); @@ -180,13 +270,87 @@ fn test_gradle_multi_module_command_logic() { fs::remove_dir_all(&temp_dir).unwrap(); } +#[test] +fn test_gradle_single_module_command_logic() { + use std::process::Command as StdCommand; + + let mut run_command = get_task_command_by_tag("java-main"); + + run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_single_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", None); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("GRADLE_CALLED: :run"), "Should run as single module. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + +#[test] +fn test_gradle_nested_module_command_logic() { + use std::process::Command as StdCommand; + + let mut run_command = get_task_command_by_tag("java-main"); + + run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_nested_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("nested/module-b")); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("GRADLE_CALLED: :nested:module-b:run"), "Should run with correct nested module path. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + #[test] fn test_gradle_multi_module_test_method_logic() { use std::process::Command as StdCommand; - let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); - let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); - let mut test_command = tasks[1]["command"].as_str().expect("Command is not a string").to_string(); + let mut test_command = get_task_command_by_tag("java-test-method"); test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); @@ -196,7 +360,7 @@ fn test_gradle_multi_module_test_method_logic() { } fs::create_dir_all(&temp_dir).unwrap(); - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", true); + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("module-a")); let old_path = std::env::var("PATH").unwrap_or_default(); let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); @@ -220,3 +384,128 @@ fn test_gradle_multi_module_test_method_logic() { fs::remove_dir_all(&temp_dir).unwrap(); } + +#[test] +fn test_no_build_tool_command_logic() { + use std::process::Command as StdCommand; + + let mut run_command = get_task_command_by_tag("java-main"); + + run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_no_build_tool_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "none", None); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + // Mock javac and java + let javac_mock = bin_dir.join("javac"); + fs::write(&javac_mock, "#!/bin/sh\necho \"JAVAC_CALLED: $@\"").unwrap(); + let java_mock = bin_dir.join("java"); + fs::write(&java_mock, "#!/bin/sh\necho \"JAVA_CALLED: $@\"").unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&javac_mock, fs::Permissions::from_mode(0o755)).unwrap(); + fs::set_permissions(&java_mock, fs::Permissions::from_mode(0o755)).unwrap(); + } + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Main") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("JAVAC_CALLED: -d bin ./src/main/java/com/example/Main.java"), "Should compile with javac. Got: {}", stdout); + assert!(stdout.contains("JAVA_CALLED: -cp bin com.example.Main"), "Should run with java. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + +#[test] +fn test_maven_nested_class_test_method_logic() { + use std::process::Command as StdCommand; + + let mut test_command = get_task_command_by_tag("java-test-method"); + + test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_maven_nested_class_logic_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("module-a")); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&test_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", "com.example") + .env("ZED_CUSTOM_java_class_name", "Inner") + .env("ZED_CUSTOM_java_outer_class_name", "Outer") + .env("ZED_CUSTOM_java_method_name", "testMe") + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("MVN_CALLED: test -pl module-a -Dtest=com.example.Outer$Inner#testMe"), "Should run nested class test method correctly. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} + +#[test] +fn test_gradle_run_all_tests_logic() { + use std::process::Command as StdCommand; + + let run_tests_command = get_task_command_by_tag("java-test-all"); + + let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_run_tests_integration"); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + + let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("module-a")); + + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + + let output = StdCommand::new("sh") + .arg("-c") + .arg(&run_tests_command) + .env("ZED_FILE", zed_file.to_string_lossy().to_string()) + .env("PWD", temp_dir.to_string_lossy().to_string()) + .env("PATH", new_path) + .current_dir(&temp_dir) + .output() + .expect("Failed to execute shell command"); + + let stdout = String::from_utf8_lossy(&output.stdout); + + assert!(stdout.contains("GRADLE_CALLED: :module-a:test"), "Should run all tests in submodule. Got: {}", stdout); + + fs::remove_dir_all(&temp_dir).unwrap(); +} From cd1759b327bb7fae41809ffe6a39f1afaeb4c4f7 Mon Sep 17 00:00:00 2001 From: Christian Pieczewski Date: Tue, 12 May 2026 21:15:40 +0200 Subject: [PATCH 3/3] set correct maven scope for java tasks Update the Java task runner to detect if the current file is inside a test directory ('src/test/'). Depending on the file location, Maven will now dynamically select 'test-compile' and 'test' classpath scope or 'compile' and 'runtime' scope. This ensures that main classes are executed with the correct dependencies and build targets. Additionally, the Rust test suite has been heavily refactored. It now utilizes a builder pattern (TestProject and TaskRunner) to simplify setup, improve readability, and add test coverage for both runtime and test execution paths across Maven and Gradle. --- languages/java/tasks.json | 2 +- tests/task_verification_test.rs | 761 +++++++++++++++----------------- 2 files changed, 367 insertions(+), 396 deletions(-) diff --git a/languages/java/tasks.json b/languages/java/tasks.json index cf319ee..bf98f71 100644 --- a/languages/java/tasks.json +++ b/languages/java/tasks.json @@ -1,7 +1,7 @@ [ { "label": "Run $ZED_CUSTOM_java_class_name", - "command": "pkg=\"$ZED_CUSTOM_java_package_name\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; if [ \"$m\" = \".\" ]; then $CMD clean test-compile exec:java -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; else $CMD clean test-compile -pl \"$m\" -am && $CMD exec:java -pl \"$m\" -Dexec.mainClass=\"$c\" -Dexec.classpathScope=test; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;", + "command": "pkg=\"$ZED_CUSTOM_java_package_name\"; cls=\"$ZED_CUSTOM_java_class_name\"; if [ -n \"$pkg\" ]; then c=\"$pkg.$cls\"; else c=\"$cls\"; fi; f=\"$ZED_FILE\"; p=\"$PWD\"; d=$(dirname \"${f#$p/}\"); if [ -f pom.xml ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/pom.xml\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; [ -f ./mvnw ] && CMD=\"./mvnw\" || CMD=\"mvn\"; case \"$f\" in *\"/src/test/\"*) COMPILE_GOAL=\"test-compile\"; CLASSPATH_SCOPE=\"test\";; *) COMPILE_GOAL=\"compile\"; CLASSPATH_SCOPE=\"runtime\";; esac; if [ \"$m\" = \".\" ]; then $CMD clean $COMPILE_GOAL exec:java -Dexec.mainClass=\"$c\" -Dexec.classpathScope=$CLASSPATH_SCOPE; else $CMD clean $COMPILE_GOAL -pl \"$m\" -am && $CMD exec:java -pl \"$m\" -Dexec.mainClass=\"$c\" -Dexec.classpathScope=$CLASSPATH_SCOPE; fi; elif [ -f build.gradle ] || [ -f build.gradle.kts ] || [ -f settings.gradle ] || [ -f settings.gradle.kts ]; then m=\".\"; md=\"$d\"; while [ \"$md\" != \".\" ] && [ \"$md\" != \"/\" ]; do if [ -f \"$md/build.gradle\" ] || [ -f \"$md/build.gradle.kts\" ]; then m=\"$md\"; break; fi; md=$(dirname \"$md\"); done; if [ \"$m\" = \".\" ]; then mp=\"\"; else mp=\":$(echo \"$m\" | tr '/' ':')\"; fi; [ -f ./gradlew ] && CMD=\"./gradlew\" || CMD=\"gradle\"; $CMD ${mp}:run -PmainClass=\"$c\"; else find . -name '*.java' -not -path './bin/*' -not -path './target/*' -not -path './build/*' -print0 | xargs -0 javac -d bin && java -cp bin \"$c\"; fi;", "use_new_terminal": false, "reveal": "always", "tags": ["java-main"], diff --git a/tests/task_verification_test.rs b/tests/task_verification_test.rs index 74d02eb..60d0f0f 100644 --- a/tests/task_verification_test.rs +++ b/tests/task_verification_test.rs @@ -6,7 +6,7 @@ fn setup_mock_project( temp_dir: &Path, project_type: &str, module_path: Option<&str>, -) -> (PathBuf, PathBuf) { +) -> (PathBuf, PathBuf, PathBuf) { let module_dir = if let Some(path) = module_path { let d = temp_dir.join(path); fs::create_dir_all(&d).unwrap(); @@ -14,23 +14,20 @@ fn setup_mock_project( } else { temp_dir.to_path_buf() }; - - let is_multi = module_path.is_some(); - let package_dir = if project_type == "maven" && is_multi { - module_dir.join("src/test/java/com/example") - } else { - module_dir.join("src/main/java/com/example") - }; - - fs::create_dir_all(&package_dir).unwrap(); + + let main_package_dir = module_dir.join("src/main/java/com/example"); + let test_package_dir = module_dir.join("src/test/java/com/example"); + + fs::create_dir_all(&main_package_dir).unwrap(); + fs::create_dir_all(&test_package_dir).unwrap(); let bin_dir = temp_dir.join("bin"); fs::create_dir_all(&bin_dir).unwrap(); if project_type == "maven" { fs::File::create(temp_dir.join("pom.xml")).unwrap(); - if is_multi { - fs::File::create(module_dir.join("pom.xml")).unwrap(); + if let Some(path) = module_path { + fs::File::create(temp_dir.join(path).join("pom.xml")).unwrap(); } let mvn_mock = bin_dir.join("mvn"); fs::write(&mvn_mock, "#!/bin/sh\necho \"MVN_CALLED: $@\"").unwrap(); @@ -41,8 +38,8 @@ fn setup_mock_project( } } else if project_type == "gradle" { fs::File::create(temp_dir.join("settings.gradle")).unwrap(); - if is_multi { - fs::File::create(module_dir.join("build.gradle")).unwrap(); + if let Some(path) = module_path { + fs::File::create(temp_dir.join(path).join("build.gradle")).unwrap(); } let gradle_mock = bin_dir.join("gradle"); fs::write(&gradle_mock, "#!/bin/sh\necho \"GRADLE_CALLED: $@\"").unwrap(); @@ -53,12 +50,13 @@ fn setup_mock_project( } } - let zed_file = package_dir.join("Main.java"); + let zed_file = main_package_dir.join("Main.java"); + let zed_test_file = test_package_dir.join("MainTest.java"); fs::File::create(&zed_file).unwrap(); + fs::File::create(&zed_test_file).unwrap(); - (zed_file, bin_dir) + (zed_file, zed_test_file, bin_dir) } - fn get_task_command_by_tag(tag: &str) -> String { let tasks_json = fs::read_to_string("languages/java/tasks.json").expect("Failed to read tasks.json"); let tasks: Value = serde_json::from_str(&tasks_json).expect("Failed to parse tasks.json"); @@ -74,438 +72,411 @@ fn get_task_command_by_tag(tag: &str) -> String { panic!("Task with tag '{}' not found", tag); } -#[test] -fn test_maven_multi_module_command_logic() { - use std::process::Command as StdCommand; +struct TestProject { + temp_dir: PathBuf, + bin_dir: PathBuf, + zed_file: PathBuf, + zed_test_file: PathBuf, + new_path: String, +} - let mut run_command = get_task_command_by_tag("java-main"); +impl TestProject { + fn new(name: &str, project_type: &str, module_path: Option<&str>) -> Self { + let temp_dir = std::env::temp_dir().join(name); + if temp_dir.exists() { + fs::remove_dir_all(&temp_dir).unwrap(); + } + fs::create_dir_all(&temp_dir).unwrap(); + let (zed_file, zed_test_file, bin_dir) = setup_mock_project(&temp_dir, project_type, module_path); + let old_path = std::env::var("PATH").unwrap_or_default(); + let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); + Self { + temp_dir, + bin_dir, + zed_file, + zed_test_file, + new_path, + } + } - run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + fn task(&self, tag: &str) -> TaskRunner { + let command = get_task_command_by_tag(tag) + .replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + TaskRunner { + project: self, + command, + zed_file: self.zed_file.clone(), + package: "com.example".to_string(), + class: "Main".to_string(), + extra_env: Vec::new(), + } + } - let temp_dir = std::env::temp_dir().join("zed_java_test_maven_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); + fn mock_bin(&self, name: &str, content: &str) { + let bin_path = self.bin_dir.join(name); + fs::write(&bin_path, content).unwrap(); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + fs::set_permissions(&bin_path, fs::Permissions::from_mode(0o755)).unwrap(); + } } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("module-a")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("MVN_CALLED: clean test-compile -pl module-a -am"), "Should build submodule with dependencies. Got: {}", stdout); - assert!(stdout.contains("MVN_CALLED: exec:java -pl module-a"), "Should run only the submodule. Got: {}", stdout); - assert!(stdout.contains("-Dexec.classpathScope=test"), "Should use test classpath scope. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); } -#[test] -fn test_maven_single_module_command_logic() { - use std::process::Command as StdCommand; +impl Drop for TestProject { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.temp_dir); + } +} +struct TaskRunner<'a> { + project: &'a TestProject, + command: String, + zed_file: PathBuf, + package: String, + class: String, + extra_env: Vec<(&'static str, String)>, +} - let mut run_command = get_task_command_by_tag("java-main"); +impl<'a> TaskRunner<'a> { + fn zed_file(mut self, path: PathBuf) -> Self { + self.zed_file = path; + self + } + fn class(mut self, c: &str) -> Self { + self.class = c.to_string(); + self + } + fn method(mut self, m: &str) -> Self { + self.extra_env.push(("ZED_CUSTOM_java_method_name", m.to_string())); + self + } + fn outer_class(mut self, o: &str) -> Self { + self.extra_env.push(("ZED_CUSTOM_java_outer_class_name", o.to_string())); + self + } - run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + fn run(self) -> String { + let mut cmd = std::process::Command::new("sh"); + cmd.arg("-c") + .arg(&self.command) + .env("ZED_FILE", self.zed_file.to_string_lossy().to_string()) + .env("PWD", self.project.temp_dir.to_string_lossy().to_string()) + .env("ZED_CUSTOM_java_package_name", &self.package) + .env("ZED_CUSTOM_java_class_name", &self.class) + .env("PATH", &self.project.new_path) + .current_dir(&self.project.temp_dir); + + for (k, v) in self.extra_env { + cmd.env(k, v); + } - let temp_dir = std::env::temp_dir().join("zed_java_test_maven_single_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); + let output = cmd.output().expect("Failed to execute shell command"); + String::from_utf8_lossy(&output.stdout).to_string() } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", None); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("MVN_CALLED: clean test-compile exec:java -Dexec.mainClass=com.example.Main"), "Should run as single module. Got: {}", stdout); - assert!(stdout.contains("-Dexec.classpathScope=test"), "Should use test classpath scope. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); } -#[test] -fn test_maven_nested_module_command_logic() { - use std::process::Command as StdCommand; +// --- Maven Tests --- - let mut run_command = get_task_command_by_tag("java-main"); +#[test] +fn test_maven_single_module_command_logic() { + let project = TestProject::new("maven_single", "maven", None); + + let stdout = project.task("java-main").run(); + let stdout_test = project + .task("java-main") + .zed_file(project.zed_test_file.clone()) + .run(); + + assert!( + stdout.contains("MVN_CALLED: clean compile exec:java -Dexec.mainClass=com.example.Main"), + "Should run as single module. Got: {}", + stdout + ); + assert!( + stdout.contains("-Dexec.classpathScope=runtime"), + "Should use runtime classpath scope. Got: {}", + stdout + ); + assert!( + stdout_test.contains("MVN_CALLED: clean test-compile exec:java -Dexec.mainClass=com.example.Main"), + "Should run as single module. Got: {}", + stdout_test + ); + assert!( + stdout_test.contains("-Dexec.classpathScope=test"), + "Should use test classpath scope. Got: {}", + stdout_test + ); +} - run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); +#[test] +fn test_maven_multi_module_command_logic() { + let project = TestProject::new("maven_multi", "maven", Some("module-a")); + + let stdout = project.task("java-main").run(); + let stdout_test = project + .task("java-main") + .zed_file(project.zed_test_file.clone()) + .run(); + + assert!( + stdout.contains("MVN_CALLED: clean compile -pl module-a -am"), + "Should build submodule with dependencies. Got: {}", + stdout + ); + assert!( + stdout.contains("MVN_CALLED: exec:java -pl module-a"), + "Should run only the submodule. Got: {}", + stdout + ); + assert!( + stdout.contains("-Dexec.classpathScope=runtime"), + "Should use test classpath scope. Got: {}", + stdout + ); + + assert!( + stdout_test.contains("MVN_CALLED: clean test-compile -pl module-a -am"), + "Should build submodule with dependencies. Got: {}", + stdout_test + ); + assert!( + stdout_test.contains("MVN_CALLED: exec:java -pl module-a"), + "Should run only the submodule. Got: {}", + stdout_test + ); + assert!( + stdout_test.contains("-Dexec.classpathScope=test"), + "Should use test classpath scope. Got: {}", + stdout_test + ); +} - let temp_dir = std::env::temp_dir().join("zed_java_test_maven_nested_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("nested/module-b")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("MVN_CALLED: clean test-compile -pl nested/module-b -am"), "Should build nested submodule with dependencies. Got: {}", stdout); - assert!(stdout.contains("MVN_CALLED: exec:java -pl nested/module-b"), "Should run only the nested submodule. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); +#[test] +fn test_maven_nested_module_command_logic() { + let project = TestProject::new("maven_nested", "maven", Some("nested/module-b")); + + let stdout = project + .task("java-main") + .zed_file(project.zed_test_file.clone()) + .run(); + + assert!( + stdout.contains("MVN_CALLED: clean test-compile -pl nested/module-b -am"), + "Should build nested submodule with dependencies. Got: {}", + stdout + ); + assert!( + stdout.contains("MVN_CALLED: exec:java -pl nested/module-b"), + "Should run only the nested submodule. Got: {}", + stdout + ); } #[test] fn test_maven_multi_module_test_method_logic() { - use std::process::Command as StdCommand; - - let mut test_command = get_task_command_by_tag("java-test-method"); + let project = TestProject::new("maven_method", "maven", Some("module-a")); + + let stdout = project + .task("java-test-method") + .method("shouldPersist") + .run(); + + assert!( + stdout.contains("MVN_CALLED: clean test-compile -pl module-a -am"), + "Should build submodule with dependencies. Got: {}", + stdout + ); + assert!( + stdout.contains("MVN_CALLED: test -pl module-a -Dtest=com.example.Main#shouldPersist"), + "Should run only the submodule test. Got: {}", + stdout + ); +} - test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); +#[test] +fn test_maven_nested_class_test_method_logic() { + let project = TestProject::new("maven_nested_class", "maven", Some("module-a")); + + let stdout = project + .task("java-test-method") + .class("Inner") + .outer_class("Outer") + .method("testMe") + .run(); + + assert!( + stdout.contains("MVN_CALLED: test -pl module-a -Dtest=com.example.Outer$Inner#testMe"), + "Should run nested class test method correctly. Got: {}", + stdout + ); +} - let temp_dir = std::env::temp_dir().join("zed_java_test_maven_method_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("module-a")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&test_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("ZED_CUSTOM_java_method_name", "shouldPersist") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("MVN_CALLED: clean test-compile -pl module-a -am"), "Should build submodule with dependencies. Got: {}", stdout); - assert!(stdout.contains("MVN_CALLED: test -pl module-a -Dtest=com.example.Main#shouldPersist"), "Should run only the submodule test. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); +#[test] +fn test_maven_run_all_tests_logic() { + let project = TestProject::new("maven_all_tests", "maven", Some("module-a")); + + let stdout = project.task("java-test-all").run(); + + assert!( + stdout.contains("MVN_CALLED: clean test-compile -pl module-a -am"), + "Should build submodule with dependencies. Got: {}", + stdout + ); + assert!( + stdout.contains("MVN_CALLED: test -pl module-a"), + "Should run all tests in submodule. Got: {}", + stdout + ); } #[test] -fn test_gradle_multi_module_command_logic() { - use std::process::Command as StdCommand; +fn test_maven_test_class_logic() { + let project = TestProject::new("maven_test_class", "maven", Some("module-a")); + + let stdout = project.task("java-test-class").run(); + + assert!( + stdout.contains("MVN_CALLED: clean test-compile -pl module-a -am"), + "Should build submodule with dependencies. Got: {}", + stdout + ); + assert!( + stdout.contains("MVN_CALLED: test -pl module-a -Dtest=com.example.Main"), + "Should run only the submodule test class. Got: {}", + stdout + ); +} - let mut run_command = get_task_command_by_tag("java-main"); +// --- Gradle Tests --- - run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); +#[test] +fn test_gradle_single_module_command_logic() { + let project = TestProject::new("gradle_single", "gradle", None); - let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("module-a")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("GRADLE_CALLED: :module-a:run"), "Should run with correct module path. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); + let stdout = project.task("java-main").run(); + + assert!( + stdout.contains("GRADLE_CALLED: :run"), + "Should run as single module. Got: {}", + stdout + ); } #[test] -fn test_gradle_single_module_command_logic() { - use std::process::Command as StdCommand; - - let mut run_command = get_task_command_by_tag("java-main"); +fn test_gradle_multi_module_command_logic() { + let project = TestProject::new("gradle_multi", "gradle", Some("module-a")); - run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + let stdout = project.task("java-main").run(); - let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_single_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", None); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("GRADLE_CALLED: :run"), "Should run as single module. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); + assert!( + stdout.contains("GRADLE_CALLED: :module-a:run"), + "Should run with correct module path. Got: {}", + stdout + ); } #[test] fn test_gradle_nested_module_command_logic() { - use std::process::Command as StdCommand; - - let mut run_command = get_task_command_by_tag("java-main"); + let project = TestProject::new("gradle_nested", "gradle", Some("nested/module-b")); - run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + let stdout = project.task("java-main").run(); - let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_nested_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("nested/module-b")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("GRADLE_CALLED: :nested:module-b:run"), "Should run with correct nested module path. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); + assert!( + stdout.contains("GRADLE_CALLED: :nested:module-b:run"), + "Should run with correct nested module path. Got: {}", + stdout + ); } #[test] fn test_gradle_multi_module_test_method_logic() { - use std::process::Command as StdCommand; - - let mut test_command = get_task_command_by_tag("java-test-method"); - - test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); - - let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_method_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("module-a")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&test_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("ZED_CUSTOM_java_method_name", "shouldPersist") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("GRADLE_CALLED: :module-a:test --tests com.example.Main.shouldPersist"), "Should run submodule test with correct path. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); + let project = TestProject::new("gradle_method", "gradle", Some("module-a")); + + let stdout = project + .task("java-test-method") + .method("shouldPersist") + .run(); + + assert!( + stdout.contains("GRADLE_CALLED: :module-a:test --tests com.example.Main.shouldPersist"), + "Should run submodule test with correct path. Got: {}", + stdout + ); } #[test] -fn test_no_build_tool_command_logic() { - use std::process::Command as StdCommand; - - let mut run_command = get_task_command_by_tag("java-main"); +fn test_gradle_run_all_tests_logic() { + let project = TestProject::new("gradle_all_tests", "gradle", Some("module-a")); - run_command = run_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + let stdout = project.task("java-test-all").run(); - let temp_dir = std::env::temp_dir().join("zed_java_test_no_build_tool_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "none", None); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - // Mock javac and java - let javac_mock = bin_dir.join("javac"); - fs::write(&javac_mock, "#!/bin/sh\necho \"JAVAC_CALLED: $@\"").unwrap(); - let java_mock = bin_dir.join("java"); - fs::write(&java_mock, "#!/bin/sh\necho \"JAVA_CALLED: $@\"").unwrap(); - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - fs::set_permissions(&javac_mock, fs::Permissions::from_mode(0o755)).unwrap(); - fs::set_permissions(&java_mock, fs::Permissions::from_mode(0o755)).unwrap(); - } - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Main") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("JAVAC_CALLED: -d bin ./src/main/java/com/example/Main.java"), "Should compile with javac. Got: {}", stdout); - assert!(stdout.contains("JAVA_CALLED: -cp bin com.example.Main"), "Should run with java. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); + assert!( + stdout.contains("GRADLE_CALLED: :module-a:test"), + "Should run all tests in submodule. Got: {}", + stdout + ); } #[test] -fn test_maven_nested_class_test_method_logic() { - use std::process::Command as StdCommand; +fn test_gradle_nested_class_test_method_logic() { + let project = TestProject::new("gradle_nested_class", "gradle", Some("module-a")); + + let stdout = project + .task("java-test-method") + .class("Inner") + .outer_class("Outer") + .method("testMe") + .run(); + + assert!( + stdout.contains("GRADLE_CALLED: :module-a:test --tests com.example.Outer$Inner.testMe"), + "Should run nested class test method correctly for Gradle. Got: {}", + stdout + ); +} - let mut test_command = get_task_command_by_tag("java-test-method"); +#[test] +fn test_gradle_test_class_logic() { + let project = TestProject::new("gradle_test_class", "gradle", Some("module-a")); - test_command = test_command.replace("${ZED_CUSTOM_java_outer_class_name:}", "${ZED_CUSTOM_java_outer_class_name:-}"); + let stdout = project.task("java-test-class").run(); - let temp_dir = std::env::temp_dir().join("zed_java_test_maven_nested_class_logic_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "maven", Some("module-a")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&test_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("ZED_CUSTOM_java_package_name", "com.example") - .env("ZED_CUSTOM_java_class_name", "Inner") - .env("ZED_CUSTOM_java_outer_class_name", "Outer") - .env("ZED_CUSTOM_java_method_name", "testMe") - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("MVN_CALLED: test -pl module-a -Dtest=com.example.Outer$Inner#testMe"), "Should run nested class test method correctly. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); + assert!( + stdout.contains("GRADLE_CALLED: :module-a:test --tests com.example.Main"), + "Should run only the submodule test class for Gradle. Got: {}", + stdout + ); } -#[test] -fn test_gradle_run_all_tests_logic() { - use std::process::Command as StdCommand; +// --- Generic Tests --- - let run_tests_command = get_task_command_by_tag("java-test-all"); - - let temp_dir = std::env::temp_dir().join("zed_java_test_gradle_run_tests_integration"); - if temp_dir.exists() { - fs::remove_dir_all(&temp_dir).unwrap(); - } - fs::create_dir_all(&temp_dir).unwrap(); - - let (zed_file, bin_dir) = setup_mock_project(&temp_dir, "gradle", Some("module-a")); - - let old_path = std::env::var("PATH").unwrap_or_default(); - let new_path = format!("{}:{}", bin_dir.to_string_lossy(), old_path); - - let output = StdCommand::new("sh") - .arg("-c") - .arg(&run_tests_command) - .env("ZED_FILE", zed_file.to_string_lossy().to_string()) - .env("PWD", temp_dir.to_string_lossy().to_string()) - .env("PATH", new_path) - .current_dir(&temp_dir) - .output() - .expect("Failed to execute shell command"); - - let stdout = String::from_utf8_lossy(&output.stdout); - - assert!(stdout.contains("GRADLE_CALLED: :module-a:test"), "Should run all tests in submodule. Got: {}", stdout); - - fs::remove_dir_all(&temp_dir).unwrap(); +#[test] +fn test_no_build_tool_command_logic() { + let project = TestProject::new("no_build_tool", "none", None); + project.mock_bin("javac", "#!/bin/sh\necho \"JAVAC_CALLED: $@\""); + project.mock_bin("java", "#!/bin/sh\necho \"JAVA_CALLED: $@\""); + + let stdout = project.task("java-main").run(); + + assert!( + stdout.contains("JAVAC_CALLED: -d bin"), + "Should compile with javac. Got: {}", + stdout + ); + assert!( + stdout.contains("./src/main/java/com/example/Main.java"), + "Should compile Main.java. Got: {}", + stdout + ); + assert!( + stdout.contains("./src/test/java/com/example/MainTest.java"), + "Should compile MainTest.java. Got: {}", + stdout + ); + assert!( + stdout.contains("JAVA_CALLED: -cp bin com.example.Main"), + "Should run with java. Got: {}", + stdout + ); }