mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 16:08:05 +00:00
Run and Debug passed the configuration working directory to java_resolve_launch as the module, so changing the working directory also changed which jdtls project supplied the classpath and failed with "references non-existing project". Java application configurations now store their module at detection and creation, keep it when the working directory changes, and clear it when the main class changes. Older saved configurations resolve it from the main class source file. The Kotlin debugger check also uses the module directory. Fixes #435
265 lines
11 KiB
Rust
265 lines
11 KiB
Rust
use ide_run::{RunConfig, load_user_configs, save_user_configs, user_configs_path};
|
|
use std::collections::BTreeMap;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
|
|
fn sample() -> Vec<RunConfig> {
|
|
vec![
|
|
RunConfig {
|
|
id: "user:server".into(),
|
|
name: "Server \"prod\"".into(),
|
|
kind: "shell".into(),
|
|
command: "cargo".into(),
|
|
args: vec!["run".into(), "--release".into()],
|
|
cwd: Some(PathBuf::from("server").into()),
|
|
env: BTreeMap::from([("RUST_LOG".into(), "debug".into()), ("A B".into(), "x=y".into())]),
|
|
detected: false,
|
|
is_test: false,
|
|
debug_adapter: Some("codelldb".into()),
|
|
allow_parallel: true,
|
|
extras: Default::default(),
|
|
},
|
|
RunConfig {
|
|
id: "user:tests".into(),
|
|
name: "Tests".into(),
|
|
kind: "pytest".into(),
|
|
command: "pytest -q tests/".into(),
|
|
args: vec![],
|
|
cwd: None,
|
|
env: BTreeMap::new(),
|
|
detected: false,
|
|
is_test: true,
|
|
debug_adapter: None,
|
|
allow_parallel: false,
|
|
extras: Default::default(),
|
|
},
|
|
]
|
|
}
|
|
|
|
#[test]
|
|
fn missing_file_is_empty() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
assert!(load_user_configs(dir.path()).unwrap().is_empty());
|
|
// Saving nothing does not create the file.
|
|
save_user_configs(dir.path(), &[]).unwrap();
|
|
assert!(!user_configs_path(dir.path()).exists());
|
|
}
|
|
|
|
#[test]
|
|
fn round_trip() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let configs = sample();
|
|
save_user_configs(dir.path(), &configs).unwrap();
|
|
assert!(dir.path().join(".openidea/run.toml").is_file());
|
|
assert_eq!(load_user_configs(dir.path()).unwrap(), configs);
|
|
|
|
// Saving a detected config persists it as a user config.
|
|
let mut detected = configs[0].clone();
|
|
detected.detected = true;
|
|
save_user_configs(dir.path(), &[detected]).unwrap();
|
|
let loaded = load_user_configs(dir.path()).unwrap();
|
|
assert_eq!(loaded.len(), 1);
|
|
assert!(!loaded[0].detected);
|
|
}
|
|
|
|
#[test]
|
|
fn preserves_unrelated_content() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = user_configs_path(dir.path());
|
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
fs::write(
|
|
&path,
|
|
"# OpenIDEA run configurations\ndefault = \"user:server\"\n\n[[configurations]]\nid = \"old\"\ncommand = \"x\"\n\n[ui]\nshow-output = true\n",
|
|
)
|
|
.unwrap();
|
|
save_user_configs(dir.path(), &sample()).unwrap();
|
|
let text = fs::read_to_string(&path).unwrap();
|
|
assert!(text.contains("# OpenIDEA run configurations"), "{text}");
|
|
assert!(text.contains("default = \"user:server\""), "{text}");
|
|
assert!(text.contains("show-output = true"), "{text}");
|
|
assert!(!text.contains("\"old\""), "{text}");
|
|
assert_eq!(load_user_configs(dir.path()).unwrap(), sample());
|
|
|
|
save_user_configs(dir.path(), &[]).unwrap();
|
|
assert!(load_user_configs(dir.path()).unwrap().is_empty());
|
|
assert!(fs::read_to_string(&path).unwrap().contains("show-output"));
|
|
}
|
|
|
|
#[test]
|
|
fn lenient_hand_written_file() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = user_configs_path(dir.path());
|
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
fs::write(
|
|
&path,
|
|
"[[configurations]]\nname = \"Dev Server\"\ncommand = \"npm run dev\"\nisTest = false\n\n[[configurations]]\ncommand = \"make test\"\nis_test = true\ndebugAdapter = \"lldb\"\n",
|
|
)
|
|
.unwrap();
|
|
let c = load_user_configs(dir.path()).unwrap();
|
|
assert_eq!(c.len(), 2);
|
|
assert_eq!(c[0].id, "user:dev-server");
|
|
assert_eq!(c[0].kind, "shell");
|
|
assert_eq!(c[1].name, "make test");
|
|
assert!(c[1].is_test);
|
|
assert_eq!(c[1].debug_adapter.as_deref(), Some("lldb"));
|
|
}
|
|
|
|
#[test]
|
|
fn invalid_file_is_reported() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = user_configs_path(dir.path());
|
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
fs::write(&path, "[[configurations]\n").unwrap();
|
|
let err = load_user_configs(dir.path()).unwrap_err();
|
|
assert_eq!(err.kind, ide_core::error::ErrorKind::InvalidInput);
|
|
assert!(err.message.contains("run.toml"));
|
|
}
|
|
|
|
#[test]
|
|
fn extras_round_trip_and_local_file() {
|
|
use ide_run::{BeforeLaunchTask, load_templates, save_templates};
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let mut shared = sample().remove(0);
|
|
shared.extras.folder = Some("Servers".into());
|
|
shared.extras.params = BTreeMap::from([("mode".into(), "command".into())]);
|
|
shared.extras.include_system_env = false;
|
|
shared.extras.env_files = vec![".env".into(), "local.env".into()];
|
|
shared.extras.stdin_file = Some("in.txt".into());
|
|
shared.extras.output_file = Some("out.log".into());
|
|
shared.extras.before_launch = vec![
|
|
BeforeLaunchTask { kind: "build".into(), target: String::new() },
|
|
BeforeLaunchTask { kind: "run".into(), target: "user:migrate".into() },
|
|
];
|
|
shared.extras.show_on_stderr = true;
|
|
shared.extras.open_tool_window = Some(false);
|
|
shared.extras.focus_tool_window = true;
|
|
shared.extras.emulate_terminal = true;
|
|
shared.extras.stop_command = Some("docker stop server".into());
|
|
let mut local = sample().remove(1);
|
|
local.extras.local = true;
|
|
let mut compound = sample().remove(1);
|
|
compound.id = "user:all".into();
|
|
compound.name = "All".into();
|
|
compound.kind = "compound".into();
|
|
compound.command = String::new();
|
|
compound.extras.compound = vec!["user:server".into(), "user:tests".into()];
|
|
let configs = vec![shared, compound, local];
|
|
save_user_configs(dir.path(), &configs).unwrap();
|
|
assert_eq!(load_user_configs(dir.path()).unwrap(), configs);
|
|
|
|
let project = fs::read_to_string(user_configs_path(dir.path())).unwrap();
|
|
assert!(!project.contains("\"user:tests\"\n"), "{project}");
|
|
let private = fs::read_to_string(dir.path().join(".openidea/run.local.toml")).unwrap();
|
|
assert!(private.contains("id = \"user:tests\""), "{private}");
|
|
let ignore = fs::read_to_string(dir.path().join(".openidea/.gitignore")).unwrap();
|
|
assert!(ignore.lines().any(|l| l == "/run.local.toml"), "{ignore}");
|
|
|
|
// Making it a project file moves it back.
|
|
let mut moved = configs.clone();
|
|
moved[2].extras.local = false;
|
|
save_user_configs(dir.path(), &moved).unwrap();
|
|
assert_eq!(load_user_configs(dir.path()).unwrap(), moved);
|
|
assert!(!fs::read_to_string(dir.path().join(".openidea/run.local.toml")).unwrap().contains("user:tests"));
|
|
|
|
// Templates share run.toml without disturbing configurations.
|
|
let mut template = sample().remove(1);
|
|
template.id = "template:npm".into();
|
|
template.kind = "npm".into();
|
|
save_templates(dir.path(), std::slice::from_ref(&template)).unwrap();
|
|
assert_eq!(load_templates(dir.path()).unwrap(), vec![template]);
|
|
assert_eq!(load_user_configs(dir.path()).unwrap(), moved);
|
|
}
|
|
|
|
#[test]
|
|
fn jvm_options_persist_in_the_project_file() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let vm = "-Ddemo.flag=on --add-opens java.base/java.lang=ALL-UNNAMED '-Dname=a b'";
|
|
let args =
|
|
["-Ddemo.flag=on", "--add-opens", "java.base/java.lang=ALL-UNNAMED", "-Dname=a b", "-cp", "${classpath}"];
|
|
let mut app = sample().remove(0);
|
|
app.id = "user:demo".into();
|
|
app.kind = "spring-boot".into();
|
|
app.command = "java".into();
|
|
app.args =
|
|
args.into_iter().chain(["a.DemoApplication", "--spring.profiles.active=dev"]).map(String::from).collect();
|
|
app.extras.params = BTreeMap::from([
|
|
("mainClass".into(), "a.DemoApplication".into()),
|
|
("vmOptions".into(), vm.into()),
|
|
("activeProfiles".into(), "dev".into()),
|
|
("classpath".into(), "${classpath}".into()),
|
|
]);
|
|
save_user_configs(dir.path(), std::slice::from_ref(&app)).unwrap();
|
|
let text = fs::read_to_string(user_configs_path(dir.path())).unwrap();
|
|
assert!(text.contains(&format!("vmOptions = \"{vm}\"")), "{text}");
|
|
assert_eq!(load_user_configs(dir.path()).unwrap(), vec![app.clone()]);
|
|
|
|
// A file written before the form had these fields loads with its command line only.
|
|
fs::write(
|
|
user_configs_path(dir.path()),
|
|
"[[configurations]]\nid = \"user:old\"\nname = \"Old\"\nkind = \"spring-boot\"\ncommand = \"java\"\n\
|
|
args = [\"-Xmx1g\", \"-cp\", \"${classpath}\", \"a.App\"]\n",
|
|
)
|
|
.unwrap();
|
|
let old = load_user_configs(dir.path()).unwrap();
|
|
assert_eq!(old[0].args, ["-Xmx1g", "-cp", "${classpath}", "a.App"]);
|
|
assert!(old[0].extras.params.is_empty());
|
|
|
|
// Templates keep default VM options for new configurations.
|
|
let mut template = app;
|
|
template.id = "template:spring-boot".into();
|
|
template.extras.params = BTreeMap::from([("vmOptions".into(), "-Xmx512m".into())]);
|
|
ide_run::save_templates(dir.path(), std::slice::from_ref(&template)).unwrap();
|
|
assert_eq!(ide_run::load_templates(dir.path()).unwrap()[0].extras.params["vmOptions"], "-Xmx512m");
|
|
}
|
|
|
|
#[test]
|
|
fn suppression_round_trip_and_resaving() {
|
|
use ide_run::{load_suppressed_config_ids, save_user_configs_with_removals};
|
|
let dir = tempfile::tempdir().unwrap();
|
|
assert!(load_suppressed_config_ids(dir.path()).unwrap().is_empty());
|
|
let configs = sample();
|
|
save_user_configs(dir.path(), &configs).unwrap();
|
|
// Old files without the field still load.
|
|
assert!(load_suppressed_config_ids(dir.path()).unwrap().is_empty());
|
|
let ids = vec!["npm:dev".to_owned(), "spring-boot:App".to_owned()];
|
|
save_user_configs_with_removals(dir.path(), &configs, &ids).unwrap();
|
|
assert_eq!(load_suppressed_config_ids(dir.path()).unwrap(), ids);
|
|
assert_eq!(load_user_configs(dir.path()).unwrap(), configs);
|
|
save_user_configs(dir.path(), &[]).unwrap();
|
|
assert_eq!(load_suppressed_config_ids(dir.path()).unwrap(), ids);
|
|
let mut restored = configs[0].clone();
|
|
restored.id = ids[0].clone();
|
|
restored.extras.local = true;
|
|
save_user_configs(dir.path(), &[restored]).unwrap();
|
|
assert_eq!(load_suppressed_config_ids(dir.path()).unwrap(), vec![ids[1].clone()]);
|
|
assert!(fs::read_to_string(dir.path().join(".openidea/.gitignore")).unwrap().contains("/run.local.toml"));
|
|
}
|
|
|
|
#[test]
|
|
fn java_module_is_optional_and_independent_of_working_directory() {
|
|
let dir = tempfile::tempdir().unwrap();
|
|
let path = user_configs_path(dir.path());
|
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
fs::write(
|
|
&path,
|
|
r#"[[configurations]]
|
|
id = "user:main"
|
|
name = "Main"
|
|
kind = "application"
|
|
command = "java"
|
|
args = ["-cp", "${classpath}", "a.Main"]
|
|
cwd = "sibling"
|
|
"#,
|
|
)
|
|
.unwrap();
|
|
let mut configs = load_user_configs(dir.path()).unwrap();
|
|
assert_eq!(configs[0].extras.module, None);
|
|
for module in ["app", ""] {
|
|
configs[0].extras.module = Some(module.into());
|
|
save_user_configs(dir.path(), &configs).unwrap();
|
|
let loaded = load_user_configs(dir.path()).unwrap();
|
|
assert_eq!(loaded, configs);
|
|
assert_eq!(loaded[0].cwd.as_deref(), Some(std::path::Path::new("sibling")));
|
|
}
|
|
}
|