From 3bd21947cfad975659f457a5847fcddc4177d67b Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 14:46:00 +0800 Subject: [PATCH 01/11] Add tests and docs for `rye run` --- docs/guide/commands/run.md | 4 +- docs/guide/pyproject.md | 20 +++- rye/src/cli/run.rs | 18 +++- rye/src/sync.rs | 5 + rye/tests/test_run.rs | 188 +++++++++++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 rye/tests/test_run.rs diff --git a/docs/guide/commands/run.md b/docs/guide/commands/run.md index eab03d8d47..25379cf471 100644 --- a/docs/guide/commands/run.md +++ b/docs/guide/commands/run.md @@ -1,7 +1,9 @@ # `run` Runs a command installed into this package. This either runs a script or application -made available in the virtualenv or a Rye specific script. +made available in the virtualenv or a Rye specific script. If not command is provided, it will list all available commands. + +If there is a script in the virtualenv having the same name as a Rye script, the virtualenv script will take precedence over the Rye script. For more information see [`rye.tool.scripts`](../pyproject.md#toolryescripts). diff --git a/docs/guide/pyproject.md b/docs/guide/pyproject.md index 34db616b37..0df200d10e 100644 --- a/docs/guide/pyproject.md +++ b/docs/guide/pyproject.md @@ -135,12 +135,28 @@ devserver-alt = ["flask", "run", "--app", "./hello.py", "--debug"] devserver-explicit = { cmd = "flask run --app ./hello.py --debug" } ``` +In addition to the shell's pre-existing PATH, Rye run adds `.venv/bin` to the PATH provided to scripts. Any scripts provided by locally-installed dependencies can be used without the `.venv/bin` prefix. For example, if there is a +`dev-dependencies` on pytest in your package, you should write: + +```toml +[tool.rye.scripts] +test = "pytest --lf" +``` + +instead of + +```toml +[tool.rye.scripts] +test = ".venv/bin/pytest --lf" +``` + +Scripts are not run in a shell, so shell specific interpolation is unavailable. + The following keys are possible for a script: ### `cmd` -The command to execute. This is either a `string` or an `array` of arguments. In either case -shell specific interpolation is unavailable. The command will invoke one of the tools in the +The command to execute. This is either a `string` or an `array` of arguments. The command will invoke one of the tools in the virtualenv if it's available there. ```toml diff --git a/rye/src/cli/run.rs b/rye/src/cli/run.rs index 7db917d72b..394c025827 100644 --- a/rye/src/cli/run.rs +++ b/rye/src/cli/run.rs @@ -11,7 +11,7 @@ use console::style; use crate::pyproject::{PyProject, Script}; use crate::sync::{sync, SyncOptions}; use crate::tui::redirect_to_stderr; -use crate::utils::{exec_spawn, get_venv_python_bin, success_status, IoPathContext}; +use crate::utils::{exec_spawn, get_venv_python_bin, success_status, CommandOutput, IoPathContext}; /// Runs a command installed into this package. #[derive(Parser, Debug)] @@ -26,6 +26,12 @@ pub struct Args { /// Use this pyproject.toml file #[arg(long, value_name = "PYPROJECT_TOML")] pyproject: Option, + /// Enables verbose diagnostics. + #[arg(short, long)] + verbose: bool, + /// Turns off all output. + #[arg(short, long, conflicts_with = "verbose")] + quiet: bool, } #[derive(Parser, Debug)] @@ -36,13 +42,19 @@ enum Cmd { pub fn execute(cmd: Args) -> Result<(), Error> { let _guard = redirect_to_stderr(true); + let output = CommandOutput::from_quiet_and_verbose(cmd.quiet, cmd.verbose); let pyproject = PyProject::load_or_discover(cmd.pyproject.as_deref())?; // make sure we have the minimal virtualenv. - sync(SyncOptions::python_only().pyproject(cmd.pyproject)) - .context("failed to sync ahead of run")?; + sync( + SyncOptions::python_only() + .pyproject(cmd.pyproject) + .output(output), + ) + .context("failed to sync ahead of run")?; if cmd.list || cmd.cmd.is_none() { + drop(_guard); return list_scripts(&pyproject); } let args = match cmd.cmd { diff --git a/rye/src/sync.rs b/rye/src/sync.rs index 2acf751a15..41f031cbd5 100644 --- a/rye/src/sync.rs +++ b/rye/src/sync.rs @@ -71,6 +71,11 @@ impl SyncOptions { self.pyproject = pyproject; self } + + pub fn output(mut self, output: CommandOutput) -> Self { + self.output = output; + self + } } /// Config written into the virtualenv for sync purposes. diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs new file mode 100644 index 0000000000..dd7c19591e --- /dev/null +++ b/rye/tests/test_run.rs @@ -0,0 +1,188 @@ +use crate::common::{rye_cmd_snapshot, Space}; +use std::fs; +use toml_edit::{table, value, Array}; + +mod common; + +#[test] +fn test_run_list() { + let space = Space::new(); + space.init("my-project"); + + let status = space + .rye_cmd() + .arg("add") + .arg("Flask==3.0.0") + .arg("--sync") + .status() + .unwrap(); + assert!(status.success()); + + space.edit_toml("pyproject.toml", |doc| { + let mut scripts = table(); + scripts["hello"] = value("echo hello"); + doc["tool"]["rye"]["scripts"] = scripts; + }); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("--list"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + flask + hello (echo hello) + python + python3 + python3.12 + + ----- stderr ----- + "###); +} + +#[test] +fn test_basic_run() { + let space = Space::new(); + space.init("my-project"); + + // Run a virtualenv script + let status = space + .rye_cmd() + .arg("add") + .arg("Flask==3.0.0") + .arg("--sync") + .status() + .unwrap(); + assert!(status.success()); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("flask").arg("--version"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.2 + Flask 3.0.0 + Werkzeug 3.0.1 + + ----- stderr ----- + "###); + + // Run a non-existing script + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("not_exist_script"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + error: invalid or unknown script 'not_exist_script' + "###); + + let init_script = space + .project_path() + .join("src") + .join("my_project") + .join("__init__.py"); + fs::write( + &init_script, + "def hello():\n print('Hello from my-project!')\n return 0", + ) + .unwrap(); + + let env_file = space.project_path().join("env_file"); + fs::write(&env_file, r#"HELLO="Hello from script_6!""#).unwrap(); + + // Run Rye scripts + space.edit_toml("pyproject.toml", |doc| { + let mut scripts = table(); + scripts["script_1"] = value(r#"python -c 'print("Hello from script_1!")'"#); + scripts["script_2"]["cmd"] = value(r#"python -c 'print("Hello from script_2!")'"#); + scripts["script_3"]["call"] = value("my_project:hello"); + scripts["script_4"]["chain"] = + value(Array::from_iter(["script_1", "script_2", "script_3"])); + + scripts["script_5"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); + scripts["script_5"]["env"]["HELLO"] = value("Hello from script_5!"); + + scripts["script_6"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); + scripts["script_6"]["env-file"] = value(env_file.to_string_lossy().into_owned()); + + doc["tool"]["rye"]["scripts"] = scripts; + }); + + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_1"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from script_1! + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_2"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from script_2! + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_3"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from my-project! + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_4"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from script_1! + Hello from script_2! + Hello from my-project! + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_5"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from script_5! + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_6"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from script_6! + + ----- stderr ----- + "###); +} + +#[test] +fn test_run_name_collision() { + let space = Space::new(); + space.init("my-project"); + + let status = space + .rye_cmd() + .arg("add") + .arg("Flask==3.0.0") + .arg("--sync") + .status() + .unwrap(); + assert!(status.success()); + + space.edit_toml("pyproject.toml", |doc| { + doc["tool"]["rye"]["scripts"] = table(); + doc["tool"]["rye"]["scripts"]["flask"] = + value(r#"python -c 'print("flask from rye script")'"#); + }); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("flask").arg("--version"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Python 3.12.2 + Flask 3.0.0 + Werkzeug 3.0.1 + + ----- stderr ----- + "###); +} From f4fb04e306e350c545a08cafa0f481e6c863d2d9 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:09:27 +0800 Subject: [PATCH 02/11] Add a failure chain case --- rye/tests/test_run.rs | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index dd7c19591e..78f6f5e43f 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -84,7 +84,7 @@ fn test_basic_run() { .unwrap(); let env_file = space.project_path().join("env_file"); - fs::write(&env_file, r#"HELLO="Hello from script_6!""#).unwrap(); + fs::write(&env_file, r#"HELLO="Hello from env_file!""#).unwrap(); // Run Rye scripts space.edit_toml("pyproject.toml", |doc| { @@ -92,14 +92,17 @@ fn test_basic_run() { scripts["script_1"] = value(r#"python -c 'print("Hello from script_1!")'"#); scripts["script_2"]["cmd"] = value(r#"python -c 'print("Hello from script_2!")'"#); scripts["script_3"]["call"] = value("my_project:hello"); - scripts["script_4"]["chain"] = + scripts["script_4"]["cmd"] = value(r#"python -c 'import sys; sys.exit(1)'"#); + scripts["script_5"]["chain"] = value(Array::from_iter(["script_1", "script_2", "script_3"])); + scripts["script_6"]["chain"] = + value(Array::from_iter(["script_1", "script_2", "script_3", "script_4", "script_3"])); - scripts["script_5"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); - scripts["script_5"]["env"]["HELLO"] = value("Hello from script_5!"); + scripts["script_7"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); + scripts["script_7"]["env"]["HELLO"] = value("Hello from script_7!"); - scripts["script_6"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); - scripts["script_6"]["env-file"] = value(env_file.to_string_lossy().into_owned()); + scripts["script_8"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); + scripts["script_8"]["env-file"] = value(env_file.to_string_lossy().into_owned()); doc["tool"]["rye"]["scripts"] = scripts; }); @@ -129,6 +132,13 @@ fn test_basic_run() { ----- stderr ----- "###); rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_4"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_5"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -138,19 +148,30 @@ fn test_basic_run() { ----- stderr ----- "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_5"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_6"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + Hello from script_1! + Hello from script_2! + Hello from my-project! + + ----- stderr ----- + error: script failed with exit status: 1 + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###" success: true exit_code: 0 ----- stdout ----- - Hello from script_5! + Hello from script_7! ----- stderr ----- "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_6"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_8"), @r###" success: true exit_code: 0 ----- stdout ----- - Hello from script_6! + Hello from env_file! ----- stderr ----- "###); From c95b072aac73aaaf0a58ff038172ce108a70592c Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:17:09 +0800 Subject: [PATCH 03/11] Fix windows `rye run --list` --- rye/tests/test_run.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index 78f6f5e43f..7f8d2e9df0 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -23,15 +23,32 @@ fn test_run_list() { scripts["hello"] = value("echo hello"); doc["tool"]["rye"]["scripts"] = scripts; }); + + #[cfg(not(windows))] + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("--list"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + flask + hello (echo hello) + python + python3 + python3.12 + + ----- stderr ----- + "###); + #[cfg(windows)] rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("--list"), @r###" success: true exit_code: 0 ----- stdout ----- flask hello (echo hello) + pydoc python python3 python3.12 + pythonw ----- stderr ----- "###); From 49c2794191a82d258bc4597d8779d859aa01d823 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:17:14 +0800 Subject: [PATCH 04/11] Fix typo --- typos.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 typos.toml diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000000..04ef337911 --- /dev/null +++ b/typos.toml @@ -0,0 +1,2 @@ +[default.extend-identifiers] +ws = "ws" From 089eb1a367efaaf0035dd67a737178b8728070d0 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:20:42 +0800 Subject: [PATCH 05/11] Add comments and fmt --- rye/tests/test_run.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index 7f8d2e9df0..69bef805c4 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -106,18 +106,25 @@ fn test_basic_run() { // Run Rye scripts space.edit_toml("pyproject.toml", |doc| { let mut scripts = table(); + // A simple string command scripts["script_1"] = value(r#"python -c 'print("Hello from script_1!")'"#); + // A simple command using `cmd` key scripts["script_2"]["cmd"] = value(r#"python -c 'print("Hello from script_2!")'"#); + // A `call` script scripts["script_3"]["call"] = value("my_project:hello"); + // A failing script scripts["script_4"]["cmd"] = value(r#"python -c 'import sys; sys.exit(1)'"#); + // A `chain` script scripts["script_5"]["chain"] = value(Array::from_iter(["script_1", "script_2", "script_3"])); - scripts["script_6"]["chain"] = - value(Array::from_iter(["script_1", "script_2", "script_3", "script_4", "script_3"])); - + // A failing `chain` script + scripts["script_6"]["chain"] = value(Array::from_iter([ + "script_1", "script_2", "script_3", "script_4", "script_3", + ])); + // A script with environment variables scripts["script_7"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); scripts["script_7"]["env"]["HELLO"] = value("Hello from script_7!"); - + // A script with an env-file scripts["script_8"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); scripts["script_8"]["env-file"] = value(env_file.to_string_lossy().into_owned()); From 56d84a48a3a9a560c268d0239149c1cdb9cc6960 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:24:18 +0800 Subject: [PATCH 06/11] Add comments --- docs/guide/commands/run.md | 2 +- rye/tests/test_run.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/guide/commands/run.md b/docs/guide/commands/run.md index 25379cf471..560dbff87a 100644 --- a/docs/guide/commands/run.md +++ b/docs/guide/commands/run.md @@ -1,7 +1,7 @@ # `run` Runs a command installed into this package. This either runs a script or application -made available in the virtualenv or a Rye specific script. If not command is provided, it will list all available commands. +made available in the virtualenv or a Rye specific script. If no command is provided, it will list all available commands. If there is a script in the virtualenv having the same name as a Rye script, the virtualenv script will take precedence over the Rye script. diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index 69bef805c4..dac89ffd78 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -215,6 +215,7 @@ fn test_run_name_collision() { .unwrap(); assert!(status.success()); + // Add a script with the same name as a virtualenv script, it should be shadowed by the virtualenv script. space.edit_toml("pyproject.toml", |doc| { doc["tool"]["rye"]["scripts"] = table(); doc["tool"]["rye"]["scripts"]["flask"] = From ee08fa98f5ef48e4413c940ef8c8c52ed8050751 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:40:49 +0800 Subject: [PATCH 07/11] Fix tests --- rye/tests/common/mod.rs | 1 + rye/tests/test_run.rs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rye/tests/common/mod.rs b/rye/tests/common/mod.rs index a1827c4fdc..ebca7bb85b 100644 --- a/rye/tests/common/mod.rs +++ b/rye/tests/common/mod.rs @@ -30,6 +30,7 @@ pub const INSTA_FILTERS: &[(&str, &str)] = &[ (r" in (\d+\.)?\d+(ms|s)\b", " in [EXECUTION_TIME]"), (r"\\\\?([\w\d.])", "/$1"), (r"rye.exe", "rye"), + (r"exit (code|status)", "exit code"), ]; fn marked_tempdir() -> TempDir { diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index dac89ffd78..ba680592c1 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -46,8 +46,6 @@ fn test_run_list() { hello (echo hello) pydoc python - python3 - python3.12 pythonw ----- stderr ----- @@ -181,7 +179,7 @@ fn test_basic_run() { Hello from my-project! ----- stderr ----- - error: script failed with exit status: 1 + error: script failed with exit code: 1 "###); rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###" success: true From a62fda880e19b4894d83dd542262f97792a69ab9 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:00:14 +0800 Subject: [PATCH 08/11] Add a case --- rye/tests/test_run.rs | 51 +++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index ba680592c1..15fff3b932 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -106,25 +106,29 @@ fn test_basic_run() { let mut scripts = table(); // A simple string command scripts["script_1"] = value(r#"python -c 'print("Hello from script_1!")'"#); + // A simple array command + let mut script_2_args = Array::new(); + script_2_args.extend(["python", "-c", "print('Hello from script_2!')"]); + scripts["script_2"] = value(script_2_args); // A simple command using `cmd` key - scripts["script_2"]["cmd"] = value(r#"python -c 'print("Hello from script_2!")'"#); + scripts["script_3"]["cmd"] = value(r#"python -c 'print("Hello from script_3!")'"#); // A `call` script - scripts["script_3"]["call"] = value("my_project:hello"); + scripts["script_4"]["call"] = value("my_project:hello"); // A failing script - scripts["script_4"]["cmd"] = value(r#"python -c 'import sys; sys.exit(1)'"#); + scripts["script_5"]["cmd"] = value(r#"python -c 'import sys; sys.exit(1)'"#); // A `chain` script - scripts["script_5"]["chain"] = - value(Array::from_iter(["script_1", "script_2", "script_3"])); + scripts["script_6"]["chain"] = + value(Array::from_iter(["script_1", "script_2", "script_3", r#"python -c 'print("hello")'"#])); // A failing `chain` script - scripts["script_6"]["chain"] = value(Array::from_iter([ - "script_1", "script_2", "script_3", "script_4", "script_3", + scripts["script_7"]["chain"] = value(Array::from_iter([ + "script_1", "script_2", "script_3", "script_5", "script_3", ])); // A script with environment variables - scripts["script_7"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); - scripts["script_7"]["env"]["HELLO"] = value("Hello from script_7!"); - // A script with an env-file scripts["script_8"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); - scripts["script_8"]["env-file"] = value(env_file.to_string_lossy().into_owned()); + scripts["script_8"]["env"]["HELLO"] = value("Hello from script_8!"); + // A script with an env-file + scripts["script_9"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); + scripts["script_9"]["env-file"] = value(env_file.to_string_lossy().into_owned()); doc["tool"]["rye"]["scripts"] = scripts; }); @@ -149,47 +153,56 @@ fn test_basic_run() { success: true exit_code: 0 ----- stdout ----- - Hello from my-project! + Hello from script_3! ----- stderr ----- "###); rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_4"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from my-project! + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_5"), @r###" success: false exit_code: 1 ----- stdout ----- ----- stderr ----- "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_5"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_6"), @r###" success: true exit_code: 0 ----- stdout ----- Hello from script_1! Hello from script_2! - Hello from my-project! + Hello from script_3! + hello ----- stderr ----- "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_6"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###" success: false exit_code: 1 ----- stdout ----- Hello from script_1! Hello from script_2! - Hello from my-project! + Hello from script_3! ----- stderr ----- error: script failed with exit code: 1 "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_8"), @r###" success: true exit_code: 0 ----- stdout ----- - Hello from script_7! + Hello from script_8! ----- stderr ----- "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_8"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_9"), @r###" success: true exit_code: 0 ----- stdout ----- From 8c7abf2200d1c6298b77c77db92a339aabf26ae7 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 16:23:37 +0800 Subject: [PATCH 09/11] Split test functions --- rye/tests/test_run.rs | 94 ++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index 15fff3b932..7fd5fabc38 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -66,7 +66,7 @@ fn test_basic_run() { .status() .unwrap(); assert!(status.success()); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("flask").arg("--version"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("-q").arg("flask").arg("--version"), @r###" success: true exit_code: 0 ----- stdout ----- @@ -116,19 +116,12 @@ fn test_basic_run() { scripts["script_4"]["call"] = value("my_project:hello"); // A failing script scripts["script_5"]["cmd"] = value(r#"python -c 'import sys; sys.exit(1)'"#); - // A `chain` script - scripts["script_6"]["chain"] = - value(Array::from_iter(["script_1", "script_2", "script_3", r#"python -c 'print("hello")'"#])); - // A failing `chain` script - scripts["script_7"]["chain"] = value(Array::from_iter([ - "script_1", "script_2", "script_3", "script_5", "script_3", - ])); // A script with environment variables - scripts["script_8"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); - scripts["script_8"]["env"]["HELLO"] = value("Hello from script_8!"); + scripts["script_6"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); + scripts["script_6"]["env"]["HELLO"] = value("Hello from script_6!"); // A script with an env-file - scripts["script_9"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); - scripts["script_9"]["env-file"] = value(env_file.to_string_lossy().into_owned()); + scripts["script_7"]["cmd"] = value(r#"python -c 'import os; print(os.getenv("HELLO"))'"#); + scripts["script_7"]["env-file"] = value(env_file.to_string_lossy().into_owned()); doc["tool"]["rye"]["scripts"] = scripts; }); @@ -176,40 +169,87 @@ fn test_basic_run() { success: true exit_code: 0 ----- stdout ----- + Hello from script_6! + + ----- stderr ----- + "###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + Hello from env_file! + + ----- stderr ----- + "###); +} + +#[test] +fn test_script_chain() { + let space = Space::new(); + space.init("my-project"); + + space.edit_toml("pyproject.toml", |doc| { + let mut scripts = table(); + // A simple string command + scripts["script_1"] = value(r#"python -c 'print("Hello from script_1!")'"#); + // A simple command using `cmd` key + scripts["script_2"]["cmd"] = value(r#"python -c 'print("Hello from script_2!")'"#); + // A failing script + scripts["script_3"]["cmd"] = value(r#"python -c 'import sys; sys.exit(1)'"#); + // A `chain` script + scripts["script_4"]["chain"] = value(Array::from_iter([ + "script_1", + "script_2", + r#"python -c 'print("hello")'"#, + ])); + // A failing `chain` script + scripts["script_5"]["chain"] = value(Array::from_iter([ + "script_1", "script_2", "script_3", "script_1", + ])); + // A nested `chain` script + scripts["script_6"]["chain"] = + value(Array::from_iter(["script_1", "script_4", "script_5"])); + // NEED FIX: A recursive `chain` script + scripts["script_7"]["chain"] = value(Array::from_iter(["script_7"])); + + doc["tool"]["rye"]["scripts"] = scripts; + }); + + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("-q").arg("script_4"), @r###" + success: true + exit_code: 0 + ----- stdout ----- Hello from script_1! Hello from script_2! - Hello from script_3! hello ----- stderr ----- "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###" + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_5"), @r###" success: false exit_code: 1 ----- stdout ----- Hello from script_1! Hello from script_2! - Hello from script_3! ----- stderr ----- error: script failed with exit code: 1 "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_8"), @r###" - success: true - exit_code: 0 - ----- stdout ----- - Hello from script_8! - - ----- stderr ----- - "###); - rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_9"), @r###" - success: true - exit_code: 0 + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_6"), @r###" + success: false + exit_code: 1 ----- stdout ----- - Hello from env_file! + Hello from script_1! + Hello from script_1! + Hello from script_2! + hello + Hello from script_1! + Hello from script_2! ----- stderr ----- + error: script failed with exit code: 1 "###); + // rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###""###); } #[test] From 75b30bb1f36ce986371a0f3d5cb4270bbe076d75 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:02:06 +0800 Subject: [PATCH 10/11] Fix recursive chained script Based on #982, need to rebase after that get merged. --- rye/src/cli/run.rs | 11 ++++++++--- rye/tests/test_run.rs | 11 +++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/rye/src/cli/run.rs b/rye/src/cli/run.rs index 394c025827..eab545f21e 100644 --- a/rye/src/cli/run.rs +++ b/rye/src/cli/run.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::env::{self, join_paths, split_paths}; use std::ffi::OsString; use std::path::PathBuf; @@ -62,7 +62,7 @@ pub fn execute(cmd: Args) -> Result<(), Error> { None => unreachable!(), }; - invoke_script(&pyproject, args, true)?; + invoke_script(&pyproject, args, true, &mut HashSet::new())?; unreachable!(); } @@ -70,6 +70,7 @@ fn invoke_script( pyproject: &PyProject, mut args: Vec, exec: bool, + seen_chain: &mut HashSet, ) -> Result { let venv_bin = pyproject.venv_bin_path(); let mut env_overrides = None; @@ -126,9 +127,13 @@ fn invoke_script( if args.len() != 1 { bail!("extra arguments to chained commands are not allowed"); } + if seen_chain.contains(&args[0]) { + bail!("found recursive chain script"); + } + seen_chain.insert(args[0].clone()); for args in commands { let status = - invoke_script(pyproject, args.into_iter().map(Into::into).collect(), false)?; + invoke_script(pyproject, args.into_iter().map(Into::into).collect(), false, seen_chain)?; if !status.success() { if !exec { return Ok(status); diff --git a/rye/tests/test_run.rs b/rye/tests/test_run.rs index 7fd5fabc38..1362a53a6d 100644 --- a/rye/tests/test_run.rs +++ b/rye/tests/test_run.rs @@ -209,7 +209,7 @@ fn test_script_chain() { // A nested `chain` script scripts["script_6"]["chain"] = value(Array::from_iter(["script_1", "script_4", "script_5"])); - // NEED FIX: A recursive `chain` script + // A recursive `chain` script scripts["script_7"]["chain"] = value(Array::from_iter(["script_7"])); doc["tool"]["rye"]["scripts"] = scripts; @@ -249,7 +249,14 @@ fn test_script_chain() { ----- stderr ----- error: script failed with exit code: 1 "###); - // rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###""###); + rye_cmd_snapshot!(space.rye_cmd().arg("run").arg("script_7"), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + error: found recursive chain script + "###); } #[test] From 8b45002e3d31188a98a0ed69d65f93f10578e300 Mon Sep 17 00:00:00 2001 From: j178 <10510431+j178@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:10:36 +0800 Subject: [PATCH 11/11] Fmt and changelog --- CHANGELOG.md | 2 ++ rye/src/cli/run.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b33b6d36..123c09c38b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ that were not yet released. _Unreleased_ +- Fixed a stack overflow issue caused by recursively chained Rye scripts. #983 + ## 0.32.0 diff --git a/rye/src/cli/run.rs b/rye/src/cli/run.rs index eab545f21e..5807b56854 100644 --- a/rye/src/cli/run.rs +++ b/rye/src/cli/run.rs @@ -132,8 +132,12 @@ fn invoke_script( } seen_chain.insert(args[0].clone()); for args in commands { - let status = - invoke_script(pyproject, args.into_iter().map(Into::into).collect(), false, seen_chain)?; + let status = invoke_script( + pyproject, + args.into_iter().map(Into::into).collect(), + false, + seen_chain, + )?; if !status.success() { if !exec { return Ok(status);