use ide_run::{RunConfig, detect}; use std::fs; use std::path::Path; use tempfile::TempDir; fn project(files: &[(&str, &str)]) -> TempDir { let dir = tempfile::tempdir().unwrap(); for (path, content) in files { let p = dir.path().join(path); fs::create_dir_all(p.parent().unwrap()).unwrap(); fs::write(p, content).unwrap(); } dir } fn find<'a>(configs: &'a [RunConfig], id: &str) -> &'a RunConfig { configs .iter() .find(|c| c.id == id) .unwrap_or_else(|| panic!("missing {id}; have {:?}", configs.iter().map(|c| &c.id).collect::>())) } fn ids(configs: &[RunConfig]) -> Vec<&str> { configs.iter().map(|c| c.id.as_str()).collect() } fn cmdline(c: &RunConfig) -> String { std::iter::once(c.command.as_str()).chain(c.args.iter().map(String::as_str)).collect::>().join(" ") } #[test] fn empty_project_detects_nothing() { let dir = tempfile::tempdir().unwrap(); assert!(detect(dir.path()).is_empty()); assert!(detect(Path::new("/definitely/not/here")).is_empty()); } #[test] fn npm_scripts_with_lockfile_package_manager() { let dir = project(&[ ("package.json", r#"{"scripts":{"dev":"vite","test":"vitest","build":"vite build","test:e2e":"playwright"}}"#), ("pnpm-lock.yaml", ""), ]); let c = detect(dir.path()); assert_eq!(ids(&c), ["npm:build", "npm:dev", "npm:test", "npm:test:e2e"]); let dev = find(&c, "npm:dev"); assert_eq!(cmdline(dev), "pnpm run dev"); assert!(dev.detected && !dev.is_test); assert_eq!(dev.kind, "npm"); assert!(find(&c, "npm:test").is_test); assert!(find(&c, "npm:test:e2e").is_test); } #[test] fn package_manager_selection() { for (lock, pm) in [("yarn.lock", "yarn"), ("bun.lockb", "bun"), ("bun.lock", "bun"), ("package-lock.json", "npm")] { let dir = project(&[("package.json", r#"{"scripts":{"start":"node ."}}"#), (lock, "")]); assert_eq!(find(&detect(dir.path()), "npm:start").command, pm, "{lock}"); } let dir = project(&[("package.json", r#"{"packageManager":"yarn@4.1.0","scripts":{"start":"x"}}"#)]); assert_eq!(find(&detect(dir.path()), "npm:start").command, "yarn"); } #[test] fn npm_workspace_packages() { let dir = project(&[ ("package.json", r#"{"workspaces":["packages/*"],"scripts":{"lint":"eslint"}}"#), ("packages/web/package.json", r#"{"name":"@acme/web","scripts":{"dev":"vite"}}"#), ("packages/api/package.json", r#"{"scripts":{"test":"jest"}}"#), ("packages/notapkg/readme.md", ""), ]); let c = detect(dir.path()); let web = find(&c, "npm:@acme/web:dev"); assert_eq!(web.cwd.as_deref(), Some(Path::new("packages/web"))); assert_eq!(cmdline(web), "npm run dev"); let api = find(&c, "npm:packages/api:test"); assert!(api.is_test); let dir = project(&[ ("package.json", "{}"), ("pnpm-lock.yaml", ""), ("pnpm-workspace.yaml", "packages:\n - 'apps/*'\n - \"libs/core\"\n"), ("apps/site/package.json", r#"{"name":"site","scripts":{"build":"astro build"}}"#), ("libs/core/package.json", r#"{"name":"core","scripts":{"test":"vitest"}}"#), ]); let c = detect(dir.path()); assert_eq!(cmdline(find(&c, "npm:site:build")), "pnpm run build"); assert!(find(&c, "npm:core:test").is_test); } #[test] fn cargo_single_package() { let dir = project(&[("Cargo.toml", "[package]\nname = \"app\"\nversion = \"0.1.0\"\n"), ("src/main.rs", "fn main() {}")]); let c = detect(dir.path()); assert_eq!(ids(&c), ["cargo:run:app", "cargo:test", "cargo:build"]); let run = find(&c, "cargo:run:app"); assert_eq!(cmdline(run), "cargo run"); assert_eq!(run.debug_adapter.as_deref(), Some("codelldb")); assert!(find(&c, "cargo:test").is_test); assert_eq!(cmdline(find(&c, "cargo:test")), "cargo test"); } #[test] fn cargo_multiple_bins() { let dir = project(&[ ( "Cargo.toml", "[package]\nname = \"tool\"\nversion = \"0.1.0\"\n\n[[bin]]\nname = \"special\"\npath = \"tools/special.rs\"\n", ), ("src/main.rs", ""), ("src/bin/extra.rs", ""), ("src/bin/multi/main.rs", ""), ]); let c = detect(dir.path()); assert_eq!(cmdline(find(&c, "cargo:run:tool")), "cargo run --bin tool"); assert_eq!(cmdline(find(&c, "cargo:run:special")), "cargo run --bin special"); assert_eq!(cmdline(find(&c, "cargo:run:extra")), "cargo run --bin extra"); assert_eq!(cmdline(find(&c, "cargo:run:multi")), "cargo run --bin multi"); } #[test] fn cargo_workspace() { let dir = project(&[ ("Cargo.toml", "[workspace]\nmembers = [\"crates/*\", \"cli\"]\nexclude = [\"crates/skip\"]\n"), ("crates/core/Cargo.toml", "[package]\nname = \"core\"\n"), ("crates/core/src/lib.rs", ""), ("crates/skip/Cargo.toml", "[package]\nname = \"skip\"\n"), ("crates/skip/src/main.rs", ""), ("cli/Cargo.toml", "[package]\nname = \"openidea-cli\"\n"), ("cli/src/main.rs", ""), ]); let c = detect(dir.path()); assert_eq!(ids(&c), ["cargo:run:openidea-cli", "cargo:test", "cargo:build"]); assert_eq!(cmdline(&c[0]), "cargo run -p openidea-cli --bin openidea-cli"); assert_eq!(cmdline(find(&c, "cargo:test")), "cargo test --workspace"); } #[test] fn gradle_projects() { let dir = project(&[ ("build.gradle.kts", "plugins {\n application\n id(\"org.springframework.boot\") version \"3.2.0\"\n}\n"), ("gradlew", "#!/bin/sh"), ]); let c = detect(dir.path()); assert_eq!(ids(&c), ["gradle:build", "gradle:test", "gradle:run", "gradle:bootRun"]); if cfg!(unix) { assert_eq!(cmdline(find(&c, "gradle:bootRun")), "./gradlew bootRun"); } assert!(find(&c, "gradle:test").is_test); let dir = project(&[("build.gradle", "plugins { id 'java' }\n")]); let c = detect(dir.path()); assert_eq!(ids(&c), ["gradle:build", "gradle:test"]); assert_eq!(c[0].command, "gradle"); let dir = project(&[("build.gradle", "apply plugin: 'application'\n")]); assert!(detect(dir.path()).iter().any(|c| c.id == "gradle:run")); // Multi-module: Spring Boot applied in a subproject. let dir = project(&[ ("settings.gradle", "include 'app'"), ("build.gradle", ""), ("app/build.gradle", "plugins { id 'org.springframework.boot' version '3.2.0' }"), ]); assert!(detect(dir.path()).iter().any(|c| c.id == "gradle:bootRun")); } #[test] fn maven_projects() { let pom = "org.springframework.boot\ spring-boot-maven-plugin"; let dir = project(&[("pom.xml", pom), ("mvnw", "#!/bin/sh")]); let c = detect(dir.path()); assert_eq!(ids(&c), ["maven:package", "maven:test", "maven:spring-boot:run"]); if cfg!(unix) { assert_eq!(cmdline(find(&c, "maven:test")), "./mvnw test"); } assert!(find(&c, "maven:test").is_test); let dir = project(&[("pom.xml", "")]); let c = detect(dir.path()); assert_eq!(ids(&c), ["maven:package", "maven:test"]); assert_eq!(c[0].command, "mvn"); } #[test] fn go_projects() { let dir = project(&[ ("go.mod", "module example.com/x\n"), ("main.go", "// comment\npackage main\n\nfunc main() {}\n"), ("main_test.go", "package main\n"), ("cmd/server/main.go", "package main\n"), ("cmd/lib/lib.go", "package lib\n"), ]); let c = detect(dir.path()); assert_eq!(ids(&c), ["go:run", "go:run:server", "go:test"]); assert_eq!(cmdline(find(&c, "go:run:server")), "go run ./cmd/server"); assert_eq!(cmdline(find(&c, "go:test")), "go test ./..."); assert!(find(&c, "go:test").is_test); assert_eq!(find(&c, "go:run").debug_adapter.as_deref(), Some("delve")); let dir = project(&[("go.mod", "module x\n"), ("lib.go", "package x\n")]); assert_eq!(ids(&detect(dir.path())), ["go:test"]); } #[test] fn python_projects() { let dir = project(&[ ("pyproject.toml", "[tool.pytest.ini_options]\naddopts = \"-q\"\n"), ("uv.lock", ""), ("src/mypkg/__init__.py", ""), ("src/mypkg/__main__.py", ""), ("manage.py", ""), ]); let c = detect(dir.path()); assert_eq!(ids(&c), ["django:runserver", "python:module:mypkg", "pytest"]); let pytest = find(&c, "pytest"); assert_eq!(cmdline(pytest), "uv run pytest"); assert!(pytest.is_test); assert_eq!(pytest.debug_adapter.as_deref(), Some("debugpy")); assert_eq!(cmdline(find(&c, "python:module:mypkg")), "uv run python -m mypkg"); assert_eq!(cmdline(find(&c, "django:runserver")), "uv run python manage.py runserver"); let dir = project(&[("pytest.ini", "[pytest]\n"), ("poetry.lock", "")]); assert_eq!(cmdline(find(&detect(dir.path()), "pytest")), "poetry run pytest"); let dir = project(&[("setup.cfg", "[tool:pytest]\ntestpaths = tests\n")]); assert_eq!(cmdline(find(&detect(dir.path()), "pytest")), "pytest"); let dir = project(&[("requirements.txt", "requests\n"), ("app/__main__.py", "")]); let c = detect(dir.path()); assert_eq!(ids(&c), ["python:module:app"]); } #[cfg(unix)] #[test] fn python_virtualenv() { let dir = project(&[("tox.ini", ""), (".venv/bin/python", ""), (".venv/bin/pytest", ""), ("manage.py", "")]); let c = detect(dir.path()); assert_eq!(cmdline(find(&c, "pytest")), ".venv/bin/pytest"); assert_eq!(cmdline(find(&c, "django:runserver")), ".venv/bin/python manage.py runserver"); } #[cfg(unix)] #[test] fn bare_virtualenv_marks_a_python_project() { // No packaging files: the venv alone says which interpreter / pytest to use. let dir = project(&[(".venv/bin/python", ""), (".venv/bin/pytest", ""), ("worker/test_w.py", "")]); assert_eq!(cmdline(find(&detect(dir.path()), "pytest")), ".venv/bin/pytest"); let dir = project(&[(".venv/bin/python", "")]); assert!(detect(dir.path()).is_empty()); } #[test] fn makefile_targets() { let makefile = "\ CC := gcc VERSION = 1.0:beta .PHONY: all test clean export PATH := $(PATH) all: build test build: main.o \tgcc -o app main.o %.o: %.c \t$(CC) -c $< test:: \t./run-tests clean lint: ; rm -rf out $(OUT)/x: y out/file: z define TEMPLATE inner: x endef # comment: no "; let dir = project(&[("Makefile", makefile)]); let c = detect(dir.path()); assert_eq!(ids(&c), ["make:all", "make:build", "make:test", "make:clean", "make:lint"]); assert_eq!(cmdline(find(&c, "make:build")), "make build"); assert!(find(&c, "make:test").is_test); } #[test] fn deno_tasks() { let dir = project(&[( "deno.jsonc", "{\n // tasks\n \"tasks\": {\n \"dev\": \"deno run -A main.ts\", /* inline */\n \"test\": \"deno test\",\n },\n \"url\": \"https://x.dev//y\"\n}\n", )]); let c = detect(dir.path()); assert_eq!(ids(&c), ["deno:dev", "deno:test"]); assert_eq!(cmdline(&c[0]), "deno task dev"); assert!(c[1].is_test); } #[test] fn just_recipes() { let justfile = "\ set shell := [\"bash\", \"-c\"] alias b := build version := \"1.0\" export RUST_LOG := \"info\" # Build it build target='debug': cargo build @test *args: build cargo test {{args}} _hidden: echo [private] secret: echo serve port=\"8080\": (build \"release\") ./serve "; let dir = project(&[("justfile", justfile)]); let c = detect(dir.path()); assert_eq!(ids(&c), ["just:build", "just:test", "just:serve"]); assert_eq!(cmdline(&c[1]), "just test"); assert!(c[1].is_test); } #[test] fn ordering_is_stable_across_ecosystems() { let dir = project(&[ ("Makefile", "all:\n"), ("package.json", r#"{"scripts":{"dev":"x"}}"#), ("Cargo.toml", "[package]\nname = \"a\"\n"), ("src/main.rs", ""), ("go.mod", "module a\n"), ]); let first = ids(&detect(dir.path())).into_iter().map(str::to_owned).collect::>(); assert_eq!(first, ["npm:dev", "cargo:run:a", "cargo:test", "cargo:build", "go:test", "make:all"]); for _ in 0..3 { assert_eq!(detect(dir.path()).iter().map(|c| c.id.clone()).collect::>(), first); } assert!(detect(dir.path()).iter().all(|c| c.detected)); } #[test] fn nested_packages_in_polyglot_repo() { let dir = project(&[ ("Makefile", "all:\n\techo\n"), ("web/package.json", r#"{"scripts":{"dev":"vite","test":"vitest"}}"#), ("web/pnpm-lock.yaml", ""), ("api/go.mod", "module x\n"), ("api/main.go", "package main\nfunc main() {}\n"), ("node_modules/x/package.json", r#"{"scripts":{"no":"x"}}"#), ]); let c = detect(dir.path()); let dev = find(&c, "npm:web:dev"); assert_eq!(cmdline(dev), "pnpm run dev"); assert_eq!(dev.cwd.as_deref(), Some(Path::new("web"))); assert_eq!(dev.name, "web: pnpm run dev"); assert!(find(&c, "npm:web:test").is_test); let run = find(&c, "go:run:api"); assert_eq!(run.cwd.as_deref(), Some(Path::new("api"))); assert_eq!(run.name, "api: go run ."); let test = find(&c, "go:test:api"); assert!(test.is_test); assert_eq!(cmdline(test), "go test ./..."); assert!(!ids(&c).iter().any(|id| id.contains("no"))); } #[test] fn root_package_disables_nested_scan() { let dir = project(&[ ("package.json", r#"{"scripts":{"build":"tsc"}}"#), ("tools/package.json", r#"{"scripts":{"gen":"x"}}"#), ("go.mod", "module x\n"), ("svc/go.mod", "module y\n"), ]); let c = detect(dir.path()); assert!(!ids(&c).contains(&"npm:tools:gen")); assert!(ids(&c).contains(&"go:test")); assert!(!ids(&c).contains(&"go:test:svc")); }