diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index aed47654f..2c5ee4438 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,6 +15,7 @@ env: CARGO_NET_RETRY: 10 CARGO_TERM_COLOR: always RUSTUP_MAX_RETRIES: 10 + PYTHON_VERSION: "3.12" jobs: cargo-fmt: @@ -47,6 +48,10 @@ jobs: name: "cargo test | ${{ matrix.os }}" steps: - uses: actions/checkout@v4 + - name: "Install Python" + uses: actions/setup-python@v4 + with: + python-version: ${{ env.PYTHON_VERSION }} - name: "Install Rust toolchain" run: rustup show - name: "Install cargo insta" diff --git a/Cargo.lock b/Cargo.lock index 130e83e7d..367f04bf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1308,6 +1308,7 @@ dependencies = [ "console", "lazy_static", "linked-hash-map", + "regex", "serde", "similar", "yaml-rust", @@ -2060,6 +2061,7 @@ dependencies = [ "futures", "gourgeist", "indicatif", + "insta", "insta-cmd", "install-wheel-rs", "itertools", diff --git a/crates/puffin-cli/Cargo.toml b/crates/puffin-cli/Cargo.toml index e13fc2050..6a9609078 100644 --- a/crates/puffin-cli/Cargo.toml +++ b/crates/puffin-cli/Cargo.toml @@ -48,4 +48,11 @@ which = { workspace = true } assert_cmd = { version = "2.0.8" } assert_fs = { version = "1.0.13" } insta-cmd = { version = "0.4.0" } +insta = { version = "1.34.0", features = ["filters"] } predicates = { version = "3.0.4" } + +[features] +# Introduces a dependency on a local Python installation. +python = [] +# Introduces a dependency on PyPI. +pypi = [] diff --git a/crates/puffin-cli/src/main.rs b/crates/puffin-cli/src/main.rs index 37a526a62..366dcc066 100644 --- a/crates/puffin-cli/src/main.rs +++ b/crates/puffin-cli/src/main.rs @@ -32,6 +32,10 @@ struct Cli { /// Avoid reading from or writing to the cache. #[arg(global = true, long, short)] no_cache: bool, + + /// Path to the cache directory. + #[arg(global = true, long, env = "PUFFIN_CACHE_DIR")] + cache_dir: Option, } #[derive(Subcommand)] @@ -133,9 +137,17 @@ async fn main() -> ExitCode { printer::Printer::Default }; + let project_dirs = ProjectDirs::from("", "", "puffin"); + let cache_dir = (!cli.no_cache) + .then(|| { + cli.cache_dir + .as_deref() + .or_else(|| project_dirs.as_ref().map(ProjectDirs::cache_dir)) + }) + .flatten(); + let result = match cli.command { Commands::PipCompile(args) => { - let dirs = ProjectDirs::from("", "", "puffin"); let requirements = args .src_file .into_iter() @@ -151,59 +163,30 @@ async fn main() -> ExitCode { &constraints, args.output_file.as_deref(), args.resolution.unwrap_or_default(), - dirs.as_ref() - .map(ProjectDirs::cache_dir) - .filter(|_| !cli.no_cache), + cache_dir, printer, ) .await } Commands::PipSync(args) => { - let dirs = ProjectDirs::from("", "", "puffin"); let sources = args .src_file .into_iter() .map(RequirementsSource::from) .collect::>(); - commands::pip_sync( - &sources, - dirs.as_ref() - .map(ProjectDirs::cache_dir) - .filter(|_| !cli.no_cache), - printer, - ) - .await + commands::pip_sync(&sources, cache_dir, printer).await } Commands::PipUninstall(args) => { - let dirs = ProjectDirs::from("", "", "puffin"); let sources = args .package .into_iter() .map(RequirementsSource::from) .chain(args.requirement.into_iter().map(RequirementsSource::from)) .collect::>(); - commands::pip_uninstall( - &sources, - dirs.as_ref() - .map(ProjectDirs::cache_dir) - .filter(|_| !cli.no_cache), - printer, - ) - .await - } - Commands::Clean => { - let dirs = ProjectDirs::from("", "", "puffin"); - commands::clean(dirs.as_ref().map(ProjectDirs::cache_dir), printer).await - } - Commands::Freeze => { - let dirs = ProjectDirs::from("", "", "puffin"); - commands::freeze( - dirs.as_ref() - .map(ProjectDirs::cache_dir) - .filter(|_| !cli.no_cache), - printer, - ) + commands::pip_uninstall(&sources, cache_dir, printer).await } + Commands::Clean => commands::clean(cache_dir, printer).await, + Commands::Freeze => commands::freeze(cache_dir, printer), Commands::Venv(args) => commands::venv(&args.name, args.python.as_deref(), printer).await, Commands::Add(args) => commands::add(&args.name, printer), Commands::Remove(args) => commands::remove(&args.name, printer), diff --git a/crates/puffin-cli/tests/pip_sync.rs b/crates/puffin-cli/tests/pip_sync.rs new file mode 100644 index 000000000..668170760 --- /dev/null +++ b/crates/puffin-cli/tests/pip_sync.rs @@ -0,0 +1,387 @@ +#![cfg(all(feature = "python", feature = "pypi"))] + +use std::process::Command; + +use anyhow::Result; +use assert_cmd::prelude::*; +use assert_fs::prelude::*; +use insta_cmd::_macro_support::insta; +use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; + +const BIN_NAME: &str = "puffin"; + +#[test] +fn missing_requirements_txt() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let requirements_txt = temp_dir.child("requirements.txt"); + + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir)); + + requirements_txt.assert(predicates::path::missing()); + + Ok(()) +} + +#[test] +fn missing_venv() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + + venv.assert(predicates::path::missing()); + + Ok(()) +} + +/// Install a package into a virtual environment. +#[test] +fn install() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import markupsafe") + .current_dir(&temp_dir) + .assert() + .success(); + + Ok(()) +} + +/// Install multiple packages into a virtual environment. +#[test] +fn install_many() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import markupsafe; import tomli") + .current_dir(&temp_dir) + .assert() + .success(); + + Ok(()) +} + +/// Attempt to install an already-installed package into a virtual environment. +#[test] +fn noop() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir) + .assert() + .success(); + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import markupsafe") + .current_dir(&temp_dir) + .assert() + .success(); + + Ok(()) +} + +/// Install a package into a virtual environment, then install the same package into a different +/// virtual environment. +#[test] +fn link() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir) + .assert() + .success(); + + let venv = temp_dir.child(".venv"); + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import markupsafe") + .current_dir(&temp_dir) + .assert() + .success(); + + Ok(()) +} + +/// Install a package into a virtual environment, then sync the virtual environment with a +/// different requirements file. +#[test] +fn add_remove() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir) + .assert() + .success(); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("tomli==2.0.1")?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import tomli") + .current_dir(&temp_dir) + .assert() + .success(); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import markupsafe") + .current_dir(&temp_dir) + .assert() + .failure(); + + Ok(()) +} + +/// Install a package into a virtual environment, then install a second package into the same +/// virtual environment. +#[test] +fn install_sequential() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = temp_dir.child(".venv"); + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .arg("--cache-dir") + .arg(cache_dir.path()) + .current_dir(&temp_dir) + .assert() + .success(); + venv.assert(predicates::path::is_dir()); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir) + .assert() + .success(); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3\ntomli==2.0.1")?; + + insta::with_settings!({ + filters => vec![ + (r"\d+ms", "[TIME]"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir)); + }); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import markupsafe; import tomli") + .current_dir(&temp_dir) + .assert() + .success(); + + Ok(()) +} diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__add_remove.snap b/crates/puffin-cli/tests/snapshots/pip_sync__add_remove.snap new file mode 100644 index 000000000..173550a3a --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__add_remove.snap @@ -0,0 +1,25 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpdWoKpL + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpVhsDyT/.venv +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- +Resolved 1 package in [TIME] +Downloaded 1 package in [TIME] +Unzipped 1 package in [TIME] +Uninstalled 1 package in [TIME] +Installed 1 package in [TIME] + - markupsafe@2.1.3 + + tomli@2.0.1 + diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__install.snap b/crates/puffin-cli/tests/snapshots/pip_sync__install.snap new file mode 100644 index 000000000..a692a67c7 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__install.snap @@ -0,0 +1,20 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt + - "--no-cache" +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- +Resolved 1 package in [TIME] +Downloaded 1 package in [TIME] +Unzipped 1 package in [TIME] +Installed 1 package in [TIME] + + markupsafe@2.1.3 + diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__install_many.snap b/crates/puffin-cli/tests/snapshots/pip_sync__install_many.snap new file mode 100644 index 000000000..7400aa8f6 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__install_many.snap @@ -0,0 +1,24 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpoqh2hP + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpZCdve2/.venv +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- +Resolved 2 packages in [TIME] +Downloaded 2 packages in [TIME] +Unzipped 2 packages in [TIME] +Installed 2 packages in [TIME] + + markupsafe@2.1.3 + + tomli@2.0.1 + diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__install_sequential.snap b/crates/puffin-cli/tests/snapshots/pip_sync__install_sequential.snap new file mode 100644 index 000000000..1109c7b07 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__install_sequential.snap @@ -0,0 +1,23 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpWxh1hG + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpUUUwuX/.venv +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- +Resolved 1 package in [TIME] +Downloaded 1 package in [TIME] +Unzipped 1 package in [TIME] +Installed 1 package in [TIME] + + tomli@2.0.1 + diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__link.snap b/crates/puffin-cli/tests/snapshots/pip_sync__link.snap new file mode 100644 index 000000000..652bd9024 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__link.snap @@ -0,0 +1,20 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmprConY4 + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpxukjZu/.venv +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- +Installed 1 package in [TIME] + + markupsafe@2.1.3 + diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__missing_requirements_txt.snap b/crates/puffin-cli/tests/snapshots/pip_sync__missing_requirements_txt.snap new file mode 100644 index 000000000..125f702f1 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__missing_requirements_txt.snap @@ -0,0 +1,16 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt +--- +success: false +exit_code: 2 +----- stdout ----- + +----- stderr ----- +error: failed to open file `requirements.txt` + Caused by: No such file or directory (os error 2) + diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__missing_venv.snap b/crates/puffin-cli/tests/snapshots/pip_sync__missing_venv.snap new file mode 100644 index 000000000..061a17cdd --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__missing_venv.snap @@ -0,0 +1,18 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpvJQh45/.venv +--- +success: false +exit_code: 2 +----- stdout ----- + +----- stderr ----- +error: failed to open file `requirements.txt` + Caused by: No such file or directory (os error 2) + diff --git a/crates/puffin-cli/tests/snapshots/pip_sync__noop.snap b/crates/puffin-cli/tests/snapshots/pip_sync__noop.snap new file mode 100644 index 000000000..6c0a12c5b --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/pip_sync__noop.snap @@ -0,0 +1,19 @@ +--- +source: crates/puffin-cli/tests/pip_sync.rs +info: + program: puffin + args: + - pip-sync + - requirements.txt + - "--cache-dir" + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpEvtFoB + env: + VIRTUAL_ENV: /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpYZqcOB/.venv +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- +Audited 1 package in [TIME] + diff --git a/crates/puffin-cli/tests/snapshots/venv__create_venv.snap b/crates/puffin-cli/tests/snapshots/venv__create_venv.snap new file mode 100644 index 000000000..bcfe75d21 --- /dev/null +++ b/crates/puffin-cli/tests/snapshots/venv__create_venv.snap @@ -0,0 +1,16 @@ +--- +source: crates/puffin-cli/tests/venv.rs +info: + program: puffin + args: + - venv + - /var/folders/nt/6gf2v7_s3k13zq_t3944rwz40000gn/T/.tmpn0fxWx/.venv +--- +success: true +exit_code: 0 +----- stdout ----- + +----- stderr ----- +Using Python interpreter: /usr/bin/python3 +Creating virtual environment at: /home/ferris/project/.venv + diff --git a/crates/puffin-cli/tests/venv.rs b/crates/puffin-cli/tests/venv.rs new file mode 100644 index 000000000..69fe06bd4 --- /dev/null +++ b/crates/puffin-cli/tests/venv.rs @@ -0,0 +1,32 @@ +#![cfg(feature = "python")] + +use std::process::Command; + +use anyhow::Result; +use assert_fs::prelude::*; +use insta_cmd::_macro_support::insta; +use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; + +const BIN_NAME: &str = "puffin"; + +#[test] +fn create_venv() -> Result<()> { + let tempdir = assert_fs::TempDir::new()?; + let venv = tempdir.child(".venv"); + + insta::with_settings!({ + filters => vec![ + (r"Using Python interpreter: .+", "Using Python interpreter: /usr/bin/python3"), + (tempdir.to_str().unwrap(), "/home/ferris/project"), + ] + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("venv") + .arg(venv.as_os_str()) + .current_dir(&tempdir)); + }); + + venv.assert(predicates::path::is_dir()); + + Ok(()) +} diff --git a/crates/puffin-resolver/Cargo.toml b/crates/puffin-resolver/Cargo.toml index 25443a650..c9bbc936d 100644 --- a/crates/puffin-resolver/Cargo.toml +++ b/crates/puffin-resolver/Cargo.toml @@ -36,3 +36,7 @@ waitmap = { workspace = true } [dev-dependencies] once_cell = { version = "1.18.0" } insta = { version = "1.34.0" } + +[features] +# Introduces a dependency on PyPI. +pypi = [] diff --git a/crates/puffin-resolver/tests/resolver.rs b/crates/puffin-resolver/tests/resolver.rs index da5e7c737..fb7c89a94 100644 --- a/crates/puffin-resolver/tests/resolver.rs +++ b/crates/puffin-resolver/tests/resolver.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "pypi")] + //! Integration tests for the resolver. These tests rely on a live network connection, and hit //! `PyPI` directly.