From d520961b3f9db513b2dea5355149d43a4adcc01a Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Tue, 21 Dec 2021 22:21:42 +0000 Subject: [PATCH 1/3] xtask: add coverage command --- .cargo/config | 2 ++ Cargo.toml | 3 ++- Makefile | 16 +++++++++++++ xtask/Cargo.toml | 8 +++++++ xtask/src/main.rs | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 .cargo/config create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/main.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 00000000000..35049cbcb13 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/Cargo.toml b/Cargo.toml index b81f1ae6ea2..ef83f41f464 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,8 @@ harness = false members = [ "pyo3-macros", "pyo3-macros-backend", - "examples" + "examples", + "xtask" ] [package.metadata.docs.rs] diff --git a/Makefile b/Makefile index 20e7fa7105d..229575d0841 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: test test_py publish clippy lint fmt fmt_py fmt_rust ALL_ADDITIVE_FEATURES = macros multiple-pymethods num-bigint num-complex hashbrown serde indexmap eyre anyhow +COVERAGE_PACKAGES = --package pyo3 --package pyo3-build-config --package pyo3-macros-backend --package pyo3-macros list_all_additive_features: @echo $(ALL_ADDITIVE_FEATURES) @@ -25,6 +26,21 @@ fmt_rust: fmt: fmt_rust fmt_py @true +coverage: + # cargo llvm-cov clean --workspace + # cargo llvm-cov $(COVERAGE_PACKAGES) --no-report + # cargo llvm-cov $(COVERAGE_PACKAGES) --no-report --features abi3 + # cargo llvm-cov $(COVERAGE_PACKAGES) --no-report --features $(ALL_ADDITIVE_FEATURES) + # cargo llvm-cov $(COVERAGE_PACKAGES) --no-report --features abi3 $(ALL_ADDITIVE_FEATURES) + bash -c "\ + set -a\ + source <(cargo llvm-cov show-env)\ + export TOX_TESTENV_PASSENV=*\ + make test_py\ + " + cargo llvm-cov $(COVERAGE_PACKAGES) --no-run --summary-only + + clippy: cargo clippy --features="$(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings cargo clippy --features="abi3 $(ALL_ADDITIVE_FEATURES)" --all-targets --workspace -- -Dwarnings diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000000..f147d00064c --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.51" +clap = { version = "3.0.0-rc.7", features = ["derive"] } diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000000..b077c5cc3dc --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,57 @@ +use anyhow::Result; +use clap::Parser; +use std::{collections::HashMap, process::Command}; + +#[derive(Parser)] +enum Subcommand { + Coverage, +} + +fn main() -> Result<()> { + match Subcommand::parse() { + Subcommand::Coverage => { + run(&mut llvm_cov_command(&["clean", "--workspace"]))?; + // FIXME: run (including with various feature combinations) + // run(&mut llvm_cov_command(&["--no-report"]))?; + let env = get_coverage_env()?; + for entry in std::fs::read_dir("pytests")? { + let path = entry?.path(); + if path.is_dir() { + run(Command::new("tox").arg("-c").arg(path).envs(&env))?; + } + } + // FIXME: also run for examples + // FIXME: make it possible to generate lcov report too + run(&mut llvm_cov_command(&["--no-run", "--summary-only"]))?; + } + } + Ok(()) +} + +fn run(command: &mut Command) -> Result<()> { + command.spawn()?.wait()?; + Ok(()) +} + +fn llvm_cov_command(args: &[&str]) -> Command { + let mut command = Command::new("cargo"); + command.args(["llvm-cov", "--package=pyo3"]).args(args); + command +} + +fn get_coverage_env() -> Result> { + let mut env = HashMap::new(); + + let output = String::from_utf8(llvm_cov_command(&["show-env"]).output()?.stdout)?; + + for line in output.trim().split('\n') { + // TODO use split_once on MSRV 1.52 + let mut iter = line.splitn(2, '='); + env.insert(iter.next().unwrap().into(), iter.next().unwrap().trim_matches('"').into()); + } + + env.insert("TOX_TESTENV_PASSENV".to_owned(), "*".to_owned()); + env.insert("RUSTUP_TOOLCHAIN".to_owned(), "nightly".to_owned()); + + Ok(env) +} From d600a8c3a38e907526da0046b779ea1564b2fbfe Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sun, 26 Dec 2021 22:39:42 +0000 Subject: [PATCH 2/3] xtask: add test-py subcommand & use in CI --- .github/pull_request_template.md | 2 +- .github/workflows/ci.yml | 2 +- xtask/src/main.rs | 81 ++++++++++++++++++++++++-------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 19f06e7d6f6..3429c6c363f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,6 +10,6 @@ Be aware the CI pipeline will check your pull request for the following: - Rust lints (`make clippy`) - Rust formatting (`cargo fmt`) - Python formatting (`black . --check`. You can install black with `pip install black`) - - Compatibility with all supported Python versions for all examples. This uses `tox`; you can do run it using `make test_py`. + - Compatibility with all supported Python versions for all examples. This uses `tox`; you can do run it using `cargo xtask test-py`. You can run a similar set of checks as the CI pipeline using `make test`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index afffe8745b4..9ee1960dc3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,7 +213,7 @@ jobs: shell: bash run: | python -m pip install -U pip tox - make test_py + cargo xtask test-py env: CARGO_TARGET_DIR: ${{ github.workspace }} TOX_TESTENV_PASSENV: "CARGO_BUILD_TARGET CARGO_TARGET_DIR" diff --git a/xtask/src/main.rs b/xtask/src/main.rs index b077c5cc3dc..56630c5551b 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,34 +1,52 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Parser; use std::{collections::HashMap, process::Command}; #[derive(Parser)] enum Subcommand { + /// Runs `cargo llvm-cov` for the PyO3 codebase. Coverage, + /// Runs tests in examples/ and pytests/ + TestPy, } -fn main() -> Result<()> { - match Subcommand::parse() { - Subcommand::Coverage => { - run(&mut llvm_cov_command(&["clean", "--workspace"]))?; - // FIXME: run (including with various feature combinations) - // run(&mut llvm_cov_command(&["--no-report"]))?; - let env = get_coverage_env()?; - for entry in std::fs::read_dir("pytests")? { - let path = entry?.path(); - if path.is_dir() { - run(Command::new("tox").arg("-c").arg(path).envs(&env))?; - } - } - // FIXME: also run for examples - // FIXME: make it possible to generate lcov report too - run(&mut llvm_cov_command(&["--no-run", "--summary-only"]))?; +impl Subcommand { + fn execute(self) -> Result<()> { + match self { + Subcommand::Coverage => subcommand_coverage(), + Subcommand::TestPy => run_python_tests(None), } } +} + +fn main() -> Result<()> { + Subcommand::parse().execute() +} + +/// Runs `cargo llvm-cov` for the PyO3 codebase. +fn subcommand_coverage() -> Result<()> { + run(&mut llvm_cov_command(&["clean", "--workspace"]))?; + run(&mut llvm_cov_command(&["--no-report"]))?; + + // FIXME: add various feature combinations using 'full' feature. + // run(&mut llvm_cov_command(&["--no-report"]))?; + + // XXX: the following block doesn't work until https://github.com/taiki-e/cargo-llvm-cov/pull/115 is merged + let env = get_coverage_env()?; + run_python_tests(&env)?; + // (after here works with stable llvm-cov) + + // TODO: add an argument to make it possible to generate lcov report & use this in CI. + run(&mut llvm_cov_command(&["--no-run", "--summary-only"]))?; Ok(()) } fn run(command: &mut Command) -> Result<()> { + print!("running: {}", command.get_program().to_string_lossy()); + for arg in command.get_args() { + print!(" {}", arg.to_string_lossy()); + } + println!(); command.spawn()?.wait()?; Ok(()) } @@ -39,15 +57,32 @@ fn llvm_cov_command(args: &[&str]) -> Command { command } +fn run_python_tests<'a>( + env: impl IntoIterator + Copy, +) -> Result<()> { + for entry in std::fs::read_dir("pytests")? { + let path = entry?.path(); + if path.is_dir() && path.join("tox.ini").exists() { + run(Command::new("tox").arg("-c").arg(path).envs(env))?; + } + } + for entry in std::fs::read_dir("examples")? { + let path = entry?.path(); + if path.is_dir() && path.join("tox.ini").exists() { + run(Command::new("tox").arg("-c").arg(path).envs(env))?; + } + } + Ok(()) +} + fn get_coverage_env() -> Result> { let mut env = HashMap::new(); let output = String::from_utf8(llvm_cov_command(&["show-env"]).output()?.stdout)?; for line in output.trim().split('\n') { - // TODO use split_once on MSRV 1.52 - let mut iter = line.splitn(2, '='); - env.insert(iter.next().unwrap().into(), iter.next().unwrap().trim_matches('"').into()); + let (key, value) = split_once(line, '=').context("expected '=' in each output line")?; + env.insert(key.to_owned(), value.trim_matches('"').to_owned()); } env.insert("TOX_TESTENV_PASSENV".to_owned(), "*".to_owned()); @@ -55,3 +90,9 @@ fn get_coverage_env() -> Result> { Ok(env) } + +// Replacement for str.split_once() on Rust older than 1.52 +fn split_once(s: &str, pat: char) -> Option<(&str, &str)> { + let mut iter = s.splitn(2, pat); + Some((iter.next()?, iter.next()?)) +} From f344c2392885741907619bd96ce0ecc75be43234 Mon Sep 17 00:00:00 2001 From: David Hewitt <1939362+davidhewitt@users.noreply.github.com> Date: Sun, 26 Dec 2021 23:29:26 +0000 Subject: [PATCH 3/3] xtask: support MSRV --- xtask/Cargo.toml | 7 +++++-- xtask/src/main.rs | 39 ++++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index f147d00064c..f600841250c 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "xtask" version = "0.1.0" -edition = "2021" +edition = "2018" [dependencies] anyhow = "1.0.51" -clap = { version = "3.0.0-rc.7", features = ["derive"] } + +# Clap 3 requires MSRV 1.54 +rustversion = "1.0" +structopt = { version = "0.3", default-features = false } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 56630c5551b..12814b14fb3 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,8 +1,8 @@ use anyhow::{Context, Result}; -use clap::Parser; use std::{collections::HashMap, process::Command}; +use structopt::StructOpt; -#[derive(Parser)] +#[derive(StructOpt)] enum Subcommand { /// Runs `cargo llvm-cov` for the PyO3 codebase. Coverage, @@ -20,7 +20,7 @@ impl Subcommand { } fn main() -> Result<()> { - Subcommand::parse().execute() + Subcommand::from_args().execute() } /// Runs `cargo llvm-cov` for the PyO3 codebase. @@ -42,18 +42,14 @@ fn subcommand_coverage() -> Result<()> { } fn run(command: &mut Command) -> Result<()> { - print!("running: {}", command.get_program().to_string_lossy()); - for arg in command.get_args() { - print!(" {}", arg.to_string_lossy()); - } - println!(); + println!("running: {}", format_command(command)); command.spawn()?.wait()?; Ok(()) } fn llvm_cov_command(args: &[&str]) -> Command { let mut command = Command::new("cargo"); - command.args(["llvm-cov", "--package=pyo3"]).args(args); + command.args(&["llvm-cov", "--package=pyo3"]).args(args); command } @@ -92,7 +88,32 @@ fn get_coverage_env() -> Result> { } // Replacement for str.split_once() on Rust older than 1.52 +#[rustversion::before(1.52)] fn split_once(s: &str, pat: char) -> Option<(&str, &str)> { let mut iter = s.splitn(2, pat); Some((iter.next()?, iter.next()?)) } + +#[rustversion::since(1.52)] +fn split_once(s: &str, pat: char) -> Option<(&str, &str)> { + s.split_once(pat) +} + +#[rustversion::since(1.57)] +fn format_command(command: &Command) -> String { + let mut buf = String::new(); + buf.push('`'); + buf.push_str(&command.get_program().to_string_lossy()); + for arg in command.get_args() { + buf.push(' '); + buf.push_str(&arg.to_string_lossy()); + } + buf.push('`'); + buf +} + +#[rustversion::before(1.57)] +fn format_command(command: &Command) -> String { + // Debug impl isn't as nice as the above, but will do on < 1.57 + format!("{:?}", command) +}