diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f2053e79..2a41ef2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,56 @@ For larger changes, consider creating an issue outlining your proposed change. If you have suggestions on how we might improve the contributing documentation, let us know! +## Architecture + +Karva uses a **main-process + worker-subprocess** execution model. When you run `karva test`, the main process (`karva`) collects test files, partitions them across workers, then spawns one or more `karva-worker` subprocesses to actually execute the tests. Each worker writes its results to a shared cache directory, and the main process aggregates results when all workers finish. + +### Crate Map + +**Binaries:** + +- `karva` — Main CLI binary. Parses args, discovers test files, partitions work, spawns workers, aggregates results. +- `karva_worker` — Worker subprocess binary. Receives a subset of test files, runs them, writes results to cache. + +**Shared libraries (used by both binaries):** + +- `karva_cli` — Shared CLI types (`SubTestCommand`, `Verbosity`, etc.), the bridge between main and worker. +- `karva_cache` — Cache directory layout, result serialization, duration tracking. +- `karva_system` — OS abstraction (`System` trait), file metadata, path utilities, environment variables. +- `karva_metadata` — Project configuration (`ProjectSettings`), config file parsing. +- `karva_diagnostic` — Test result types (`TestRunResult`), diagnostic reporting. +- `karva_logging` — Tracing setup, `Printer`, colored output control. +- `karva_python_semantic` — Python version detection, AST-level semantic types. + +**Main-process only:** + +- `karva_runner` — Orchestration: worker spawning, partitioning, parallel collection. +- `karva_project` — Project database, test path resolution. +- `karva_collector` — File-level test collection (parsing Python files for test functions). +- `karva_combine` — Result combination and summary output. + +**Worker-process only:** + +- `karva_test_semantic` — Core test execution library: discovery, context, extensions, PyO3 runner. + +**Infrastructure / Build:** + +- `karva_python` — PyO3 `cdylib`, the Python wheel entry point. Wraps both `karva` and `karva_worker`. +- `karva_macros` — Procedural macros. +- `karva_dev` — Dev tools (CLI reference generation, etc.). + +**Dev / Testing:** + +- `karva_benchmark` — Wall-time benchmarks using divan. +- `karva_test_projects` — Real-world project definitions for benchmarks and diff testing. +- `karva_diff` — Binary for comparing output between two karva wheel versions. + +### Key Design Decisions + +- **Binaries don't depend on each other.** `karva` and `karva_worker` communicate only through the filesystem (cache directory) and CLI arguments. +- **Shared types live in `karva_cli`.** Both binaries depend on `karva_cli` for common command-line types like `SubTestCommand`. +- **The worker embeds a Python interpreter.** `karva_test_semantic` uses PyO3 to attach to Python for test execution, while the main process only needs Python for the wheel packaging. + ### Prerequisites Karva is written in Rust. You can install the [Rust Toolchain](https://www.rust-lang.org/tools/install) to get started. diff --git a/Cargo.lock b/Cargo.lock index ebba77bf..024690b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1199,12 +1199,12 @@ dependencies = [ "codspeed-criterion-compat", "codspeed-divan-compat", "karva_cli", - "karva_core", "karva_metadata", "karva_project", - "karva_projects", "karva_runner", "karva_system", + "karva_test_projects", + "karva_test_semantic", ] [[package]] @@ -1249,38 +1249,6 @@ dependencies = [ "ruff_python_ast", ] -[[package]] -name = "karva_core" -version = "0.0.0" -dependencies = [ - "anyhow", - "argfile", - "camino", - "clap", - "colored 3.1.1", - "ctor", - "karva_cache", - "karva_cli", - "karva_collector", - "karva_diagnostic", - "karva_logging", - "karva_metadata", - "karva_python_semantic", - "karva_system", - "pyo3", - "regex", - "rstest", - "ruff_db", - "ruff_macros", - "ruff_notebook", - "ruff_python_ast", - "ruff_python_parser", - "ruff_source_file", - "tempfile", - "tracing", - "wild", -] - [[package]] name = "karva_dev" version = "0.0.0" @@ -1319,8 +1287,8 @@ dependencies = [ "anyhow", "camino", "clap", - "karva_projects", "karva_system", + "karva_test_projects", "tempfile", ] @@ -1375,25 +1343,13 @@ dependencies = [ "karva_system", ] -[[package]] -name = "karva_projects" -version = "0.0.0" -dependencies = [ - "anyhow", - "camino", - "karva_system", - "ruff_python_ast", - "serde", - "serde_json", - "tracing", -] - [[package]] name = "karva_python" version = "0.0.1-alpha.3" dependencies = [ "karva", - "karva_core", + "karva_test_semantic", + "karva_worker", "pyo3", ] @@ -1427,10 +1383,6 @@ dependencies = [ "which", ] -[[package]] -name = "karva_static" -version = "0.0.0" - [[package]] name = "karva_system" version = "0.0.0" @@ -1440,13 +1392,74 @@ dependencies = [ "etcetera", "filetime", "karva_python_semantic", - "karva_static", - "ruff_db", "tempfile", "thiserror", "tracing", ] +[[package]] +name = "karva_test_projects" +version = "0.0.0" +dependencies = [ + "anyhow", + "camino", + "karva_system", + "ruff_python_ast", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "karva_test_semantic" +version = "0.0.0" +dependencies = [ + "anyhow", + "camino", + "ctor", + "karva_cache", + "karva_cli", + "karva_collector", + "karva_diagnostic", + "karva_logging", + "karva_metadata", + "karva_python_semantic", + "karva_system", + "pyo3", + "regex", + "ruff_db", + "ruff_macros", + "ruff_notebook", + "ruff_python_ast", + "ruff_python_parser", + "ruff_source_file", + "tempfile", + "tracing", +] + +[[package]] +name = "karva_worker" +version = "0.0.0" +dependencies = [ + "anyhow", + "argfile", + "camino", + "clap", + "colored 3.1.1", + "karva_cache", + "karva_cli", + "karva_diagnostic", + "karva_logging", + "karva_metadata", + "karva_python_semantic", + "karva_system", + "karva_test_semantic", + "ruff_db", + "ruff_notebook", + "ruff_source_file", + "wild", +] + [[package]] name = "lazy_static" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index 4f6dec73..961fa7f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,17 +17,17 @@ karva_cache = { path = "crates/karva_cache" } karva_cli = { path = "crates/karva_cli" } karva_collector = { path = "crates/karva_collector" } karva_combine = { path = "crates/karva_combine" } -karva_core = { path = "crates/karva_core" } karva_diagnostic = { path = "crates/karva_diagnostic" } karva_logging = { path = "crates/karva_logging" } karva_macros = { path = "crates/karva_macros" } karva_metadata = { path = "crates/karva_metadata" } karva_project = { path = "crates/karva_project" } -karva_projects = { path = "crates/karva_projects" } karva_python_semantic = { path = "crates/karva_python_semantic" } karva_runner = { path = "crates/karva_runner" } -karva_static = { path = "crates/karva_static" } karva_system = { path = "crates/karva_system" } +karva_test_projects = { path = "crates/karva_test_projects" } +karva_test_semantic = { path = "crates/karva_test_semantic" } +karva_worker = { path = "crates/karva_worker" } anyhow = { version = "1.0.100" } argfile = { version = "0.2.1" } @@ -116,3 +116,5 @@ rc_mutex = "warn" rest_pat_in_fully_bound_structs = "warn" if_not_else = "allow" use_self = "warn" +cast_sign_loss = "allow" +cast_possible_truncation = "allow" diff --git a/crates/karva_benchmark/Cargo.toml b/crates/karva_benchmark/Cargo.toml index 28225b01..3d6cbfbd 100644 --- a/crates/karva_benchmark/Cargo.toml +++ b/crates/karva_benchmark/Cargo.toml @@ -15,12 +15,12 @@ license = { workspace = true } codspeed-criterion-compat = { workspace = true } divan = { workspace = true } karva_cli = { workspace = true } -karva_core = { workspace = true } karva_metadata = { workspace = true } karva_project = { workspace = true } -karva_projects = { workspace = true } karva_runner = { workspace = true } karva_system = { workspace = true } +karva_test_projects = { workspace = true } +karva_test_semantic = { workspace = true } [[bench]] name = "karva_benchmark_walltime" diff --git a/crates/karva_benchmark/benches/karva_benchmark_walltime.rs b/crates/karva_benchmark/benches/karva_benchmark_walltime.rs index 688c8ace..b9b137f5 100644 --- a/crates/karva_benchmark/benches/karva_benchmark_walltime.rs +++ b/crates/karva_benchmark/benches/karva_benchmark_walltime.rs @@ -1,6 +1,6 @@ use divan::{Bencher, bench}; use karva_benchmark::walltime::{ProjectBenchmark, bench_project, warmup_project}; -use karva_projects::real_world_projects::KARVA_BENCHMARK_PROJECT; +use karva_test_projects::real_world_projects::KARVA_BENCHMARK_PROJECT; #[bench(sample_size = 3, sample_count = 3)] fn karva_benchmark(bencher: Bencher) { diff --git a/crates/karva_benchmark/src/walltime.rs b/crates/karva_benchmark/src/walltime.rs index 354d195e..91637e18 100644 --- a/crates/karva_benchmark/src/walltime.rs +++ b/crates/karva_benchmark/src/walltime.rs @@ -2,11 +2,11 @@ use std::sync::Once; use divan::Bencher; use karva_cli::SubTestCommand; -use karva_core::testing::setup_module; use karva_metadata::{Options, ProjectMetadata, SrcOptions, TestOptions}; use karva_project::ProjectDatabase; -use karva_projects::{InstalledProject, RealWorldProject}; use karva_system::OsSystem; +use karva_test_projects::{InstalledProject, RealWorldProject}; +use karva_test_semantic::testing::setup_module; static SETUP_MODULE_ONCE: Once = Once::new(); diff --git a/crates/karva_core/src/bin/main.rs b/crates/karva_core/src/bin/main.rs deleted file mode 100644 index 6d094572..00000000 --- a/crates/karva_core/src/bin/main.rs +++ /dev/null @@ -1,5 +0,0 @@ -use karva_core::cli::{ExitStatus, karva_core_main}; - -fn main() -> ExitStatus { - karva_core_main(|args| args) -} diff --git a/crates/karva_core/src/lib.rs b/crates/karva_core/src/lib.rs deleted file mode 100644 index be6b1b5e..00000000 --- a/crates/karva_core/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod cli; -pub(crate) mod collection; -mod context; -pub(crate) mod diagnostic; -pub(crate) mod discovery; -pub(crate) mod extensions; -mod python; -mod runner; -pub mod testing; -pub mod utils; - -pub(crate) use context::Context; -pub use python::init_module; diff --git a/crates/karva_diff/Cargo.toml b/crates/karva_diff/Cargo.toml index d3b077f2..5e9a41f6 100644 --- a/crates/karva_diff/Cargo.toml +++ b/crates/karva_diff/Cargo.toml @@ -12,8 +12,8 @@ authors = { workspace = true } license = { workspace = true } [dependencies] -karva_projects = { workspace = true } karva_system = { workspace = true } +karva_test_projects = { workspace = true } anyhow = { workspace = true } camino = { workspace = true } diff --git a/crates/karva_diff/src/main.rs b/crates/karva_diff/src/main.rs index 6f6ff3c8..6b636fda 100644 --- a/crates/karva_diff/src/main.rs +++ b/crates/karva_diff/src/main.rs @@ -6,8 +6,8 @@ use std::process::{Command, ExitCode}; use anyhow::{Context, Result}; use camino::Utf8PathBuf; use clap::Parser; -use karva_projects::{RealWorldProject, all_projects}; use karva_system::path::absolute; +use karva_test_projects::{RealWorldProject, all_projects}; use tempfile::NamedTempFile; #[derive(Debug, Parser)] diff --git a/crates/karva_python/Cargo.toml b/crates/karva_python/Cargo.toml index 2bbddb15..3bdd3f01 100644 --- a/crates/karva_python/Cargo.toml +++ b/crates/karva_python/Cargo.toml @@ -16,7 +16,8 @@ crate-type = ["rlib", "cdylib"] [dependencies] karva = { workspace = true } -karva_core = { workspace = true } +karva_test_semantic = { workspace = true } +karva_worker = { workspace = true } pyo3 = { workspace = true } diff --git a/crates/karva_python/src/lib.rs b/crates/karva_python/src/lib.rs index 0fb7d72a..291e0d60 100644 --- a/crates/karva_python/src/lib.rs +++ b/crates/karva_python/src/lib.rs @@ -1,5 +1,6 @@ use karva::karva_main; -use karva_core::{cli::karva_core_main, init_module}; +use karva_test_semantic::init_module; +use karva_worker::cli::karva_worker_main; use pyo3::prelude::*; #[pyfunction] @@ -19,8 +20,8 @@ pub(crate) fn karva_run() -> i32 { } #[pyfunction] -pub(crate) fn karva_core_run() -> i32 { - karva_core_main(|args| { +pub(crate) fn karva_worker_run() -> i32 { + karva_worker_main(|args| { let mut args: Vec<_> = args.into_iter().skip(1).collect(); if !args.is_empty() { if let Some(arg) = args.first() { @@ -37,7 +38,7 @@ pub(crate) fn karva_core_run() -> i32 { #[pymodule] pub(crate) fn _karva(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(karva_run, m)?)?; - m.add_function(wrap_pyfunction!(karva_core_run, m)?)?; + m.add_function(wrap_pyfunction!(karva_worker_run, m)?)?; init_module(py, m)?; Ok(()) } diff --git a/crates/karva_runner/src/collection.rs b/crates/karva_runner/src/collection.rs index cdd3ba02..7af1aa0d 100644 --- a/crates/karva_runner/src/collection.rs +++ b/crates/karva_runner/src/collection.rs @@ -16,7 +16,7 @@ use karva_system::{ /// Collector used for collecting all test functions and fixtures in a package. /// /// This is only used in the main `karva` cli. -/// If we used this in the `karva-core` cli, this would be very inefficient. +/// If we used this in the `karva-worker` cli, this would be very inefficient. pub struct ParallelCollector<'a> { system: &'a dyn System, settings: CollectionSettings<'a>, diff --git a/crates/karva_runner/src/orchestration.rs b/crates/karva_runner/src/orchestration.rs index 083d3c8c..ebfa7a00 100644 --- a/crates/karva_runner/src/orchestration.rs +++ b/crates/karva_runner/src/orchestration.rs @@ -139,7 +139,7 @@ fn spawn_workers( run_hash: &RunHash, args: &SubTestCommand, ) -> Result { - let core_binary = find_karva_core_binary(&db.system().current_directory().to_path_buf())?; + let core_binary = find_karva_worker_binary(&db.system().current_directory().to_path_buf())?; let mut worker_manager = WorkerManager::default(); for (worker_id, partition) in partitions.iter().enumerate() { @@ -169,7 +169,7 @@ fn spawn_workers( .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .spawn() - .context("Failed to spawn karva_core worker process")?; + .context("Failed to spawn karva-worker process")?; tracing::info!( "Worker {} spawned with {} tests", @@ -258,26 +258,26 @@ pub fn run_parallel_tests( Ok(result) } -const KARVA_CORE_BINARY_NAME: &str = "karva-core"; +const KARVA_WORKER_BINARY_NAME: &str = "karva-worker"; -/// Find the `karva-core` binary -fn find_karva_core_binary(current_dir: &Utf8PathBuf) -> Result { - if let Ok(path) = which::which(KARVA_CORE_BINARY_NAME) { +/// Find the `karva-worker` binary +fn find_karva_worker_binary(current_dir: &Utf8PathBuf) -> Result { + if let Ok(path) = which::which(KARVA_WORKER_BINARY_NAME) { if let Ok(utf8_path) = Utf8PathBuf::try_from(path) { tracing::debug!(path = %utf8_path, "Found binary in PATH"); return Ok(utf8_path); } } - if let Some(venv_binary) = venv_binary(KARVA_CORE_BINARY_NAME, current_dir) { + if let Some(venv_binary) = venv_binary(KARVA_WORKER_BINARY_NAME, current_dir) { return Ok(venv_binary); } - if let Some(venv_binary) = venv_binary_from_active_env(KARVA_CORE_BINARY_NAME) { + if let Some(venv_binary) = venv_binary_from_active_env(KARVA_WORKER_BINARY_NAME) { return Ok(venv_binary); } - anyhow::bail!("Could not find karva_core binary") + anyhow::bail!("Could not find karva-worker binary") } fn inner_cli_args(settings: &ProjectSettings, args: &SubTestCommand) -> Vec { diff --git a/crates/karva_static/Cargo.toml b/crates/karva_static/Cargo.toml deleted file mode 100644 index 009ed1bd..00000000 --- a/crates/karva_static/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "karva_static" -version = "0.0.0" -edition.workspace = true -rust-version.workspace = true -homepage.workspace = true -documentation.workspace = true -repository.workspace = true -authors.workspace = true -license.workspace = true - -[dependencies] - -[lints] -workspace = true diff --git a/crates/karva_static/src/lib.rs b/crates/karva_static/src/lib.rs deleted file mode 100644 index 2f962fbc..00000000 --- a/crates/karva_static/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub struct EnvVars; - -impl EnvVars { - // Externally defined environment variables - - /// This is a standard Rayon environment variable. - pub const RAYON_NUM_THREADS: &'static str = "RAYON_NUM_THREADS"; - - /// This is a standard Karva environment variable. - pub const KARVA_MAX_PARALLELISM: &'static str = "KARVA_MAX_PARALLELISM"; - - /// This is a standard Karva environment variable. - pub const KARVA_CONFIG_FILE: &'static str = "KARVA_CONFIG_FILE"; -} diff --git a/crates/karva_system/Cargo.toml b/crates/karva_system/Cargo.toml index aa835d02..af259a17 100644 --- a/crates/karva_system/Cargo.toml +++ b/crates/karva_system/Cargo.toml @@ -11,13 +11,11 @@ license.workspace = true [dependencies] karva_python_semantic = { workspace = true } -karva_static = { workspace = true } anyhow = { workspace = true } camino = { workspace = true } etcetera = { workspace = true } filetime = { workspace = true } -ruff_db = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } diff --git a/crates/karva_system/src/file_revision.rs b/crates/karva_system/src/file_revision.rs new file mode 100644 index 00000000..db516259 --- /dev/null +++ b/crates/karva_system/src/file_revision.rs @@ -0,0 +1,78 @@ +use filetime::FileTime; + +/// A number representing the revision of a file. +/// +/// Two revisions that don't compare equal signify that the file has been modified. +/// Revisions aren't guaranteed to be monotonically increasing or in any specific order. +/// +/// Possible revisions are: +/// * The last modification time of the file. +/// * The hash of the file's content. +/// * The revision as it comes from an external system. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] +pub struct FileRevision(u128); + +impl FileRevision { + pub fn new(value: u128) -> Self { + Self(value) + } + + pub fn now() -> Self { + Self::from(file_time_now()) + } + + pub const fn zero() -> Self { + Self(0) + } + + #[must_use] + pub fn as_u128(self) -> u128 { + self.0 + } +} + +impl From for FileRevision { + fn from(value: u128) -> Self { + Self(value) + } +} + +impl From for FileRevision { + fn from(value: u64) -> Self { + Self(u128::from(value)) + } +} + +impl From for FileRevision { + fn from(value: filetime::FileTime) -> Self { + let seconds = value.seconds() as u128; + let seconds = seconds << 64; + let nanos = u128::from(value.nanoseconds()); + + Self(seconds | nanos) + } +} + +pub fn file_time_now() -> FileTime { + FileTime::now() +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn revision_from_file_time() { + let file_time = file_time_now(); + let revision = FileRevision::from(file_time); + + let revision = revision.as_u128(); + + let nano = revision & 0xFFFF_FFFF_FFFF_FFFF; + let seconds = revision >> 64; + + assert_eq!(file_time.nanoseconds(), nano as u32); + assert_eq!(file_time.seconds(), seconds as i64); + } +} diff --git a/crates/karva_system/src/lib.rs b/crates/karva_system/src/lib.rs index 56deedff..5855579f 100644 --- a/crates/karva_system/src/lib.rs +++ b/crates/karva_system/src/lib.rs @@ -5,9 +5,23 @@ use std::{fmt::Debug, num::NonZeroUsize}; use anyhow::Context; use camino::{Utf8Path, Utf8PathBuf}; use filetime::FileTime; -use karva_static::EnvVars; -use ruff_db::system::{FileType, Metadata}; +pub struct EnvVars; + +impl EnvVars { + /// This is a standard Rayon environment variable. + pub const RAYON_NUM_THREADS: &'static str = "RAYON_NUM_THREADS"; + + /// This is a standard Karva environment variable. + pub const KARVA_MAX_PARALLELISM: &'static str = "KARVA_MAX_PARALLELISM"; + + /// This is a standard Karva environment variable. + pub const KARVA_CONFIG_FILE: &'static str = "KARVA_CONFIG_FILE"; +} + +use crate::file_revision::FileRevision; + +mod file_revision; pub mod path; pub mod time; @@ -203,3 +217,53 @@ pub fn venv_binary_from_active_env(binary_name: &str) -> Option { None } } + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Metadata { + revision: FileRevision, + permissions: Option, + file_type: FileType, +} + +impl Metadata { + pub fn new(revision: FileRevision, permissions: Option, file_type: FileType) -> Self { + Self { + revision, + permissions, + file_type, + } + } + + pub fn revision(&self) -> FileRevision { + self.revision + } + + pub fn permissions(&self) -> Option { + self.permissions + } + + pub fn file_type(&self) -> FileType { + self.file_type + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] +pub enum FileType { + File, + Directory, + Symlink, +} + +impl FileType { + pub const fn is_file(self) -> bool { + matches!(self, Self::File) + } + + pub const fn is_directory(self) -> bool { + matches!(self, Self::Directory) + } + + pub const fn is_symlink(self) -> bool { + matches!(self, Self::Symlink) + } +} diff --git a/crates/karva_projects/Cargo.toml b/crates/karva_test_projects/Cargo.toml similarity index 94% rename from crates/karva_projects/Cargo.toml rename to crates/karva_test_projects/Cargo.toml index 6e6c49ae..1548e1dd 100644 --- a/crates/karva_projects/Cargo.toml +++ b/crates/karva_test_projects/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "karva_projects" +name = "karva_test_projects" version = "0.0.0" edition = { workspace = true } diff --git a/crates/karva_projects/src/lib.rs b/crates/karva_test_projects/src/lib.rs similarity index 100% rename from crates/karva_projects/src/lib.rs rename to crates/karva_test_projects/src/lib.rs diff --git a/crates/karva_projects/src/real_world_projects.rs b/crates/karva_test_projects/src/real_world_projects.rs similarity index 100% rename from crates/karva_projects/src/real_world_projects.rs rename to crates/karva_test_projects/src/real_world_projects.rs diff --git a/crates/karva_core/Cargo.toml b/crates/karva_test_semantic/Cargo.toml similarity index 80% rename from crates/karva_core/Cargo.toml rename to crates/karva_test_semantic/Cargo.toml index 077c84f5..8a017530 100644 --- a/crates/karva_core/Cargo.toml +++ b/crates/karva_test_semantic/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "karva_core" +name = "karva_test_semantic" version = "0.0.0" edition = { workspace = true } @@ -10,10 +10,6 @@ repository = { workspace = true } authors = { workspace = true } license = { workspace = true } -[[bin]] -name = "karva-core" -path = "src/bin/main.rs" - [dependencies] karva_cache = { workspace = true } karva_cli = { workspace = true } @@ -25,10 +21,7 @@ karva_python_semantic = { workspace = true } karva_system = { workspace = true } anyhow = { workspace = true } -argfile = { workspace = true } camino = { workspace = true } -clap = { workspace = true, features = ["wrap_help", "string", "env"] } -colored = { workspace = true } pyo3 = { workspace = true } regex = { workspace = true } ruff_db = { workspace = true } @@ -39,12 +32,10 @@ ruff_python_parser = { workspace = true } ruff_source_file = { workspace = true } tempfile = { workspace = true } tracing = { workspace = true } -wild = { workspace = true } [dev-dependencies] ctor = { workspace = true } regex = { workspace = true } -rstest = { workspace = true } [lints] workspace = true diff --git a/crates/karva_core/src/collection.rs b/crates/karva_test_semantic/src/collection.rs similarity index 100% rename from crates/karva_core/src/collection.rs rename to crates/karva_test_semantic/src/collection.rs diff --git a/crates/karva_core/src/context.rs b/crates/karva_test_semantic/src/context.rs similarity index 100% rename from crates/karva_core/src/context.rs rename to crates/karva_test_semantic/src/context.rs diff --git a/crates/karva_core/src/diagnostic.rs b/crates/karva_test_semantic/src/diagnostic.rs similarity index 100% rename from crates/karva_core/src/diagnostic.rs rename to crates/karva_test_semantic/src/diagnostic.rs diff --git a/crates/karva_core/src/diagnostic/metadata.rs b/crates/karva_test_semantic/src/diagnostic/metadata.rs similarity index 100% rename from crates/karva_core/src/diagnostic/metadata.rs rename to crates/karva_test_semantic/src/diagnostic/metadata.rs diff --git a/crates/karva_core/src/discovery/discoverer.rs b/crates/karva_test_semantic/src/discovery/discoverer.rs similarity index 100% rename from crates/karva_core/src/discovery/discoverer.rs rename to crates/karva_test_semantic/src/discovery/discoverer.rs diff --git a/crates/karva_core/src/discovery/mod.rs b/crates/karva_test_semantic/src/discovery/mod.rs similarity index 100% rename from crates/karva_core/src/discovery/mod.rs rename to crates/karva_test_semantic/src/discovery/mod.rs diff --git a/crates/karva_core/src/discovery/models/function.rs b/crates/karva_test_semantic/src/discovery/models/function.rs similarity index 100% rename from crates/karva_core/src/discovery/models/function.rs rename to crates/karva_test_semantic/src/discovery/models/function.rs diff --git a/crates/karva_core/src/discovery/models/mod.rs b/crates/karva_test_semantic/src/discovery/models/mod.rs similarity index 100% rename from crates/karva_core/src/discovery/models/mod.rs rename to crates/karva_test_semantic/src/discovery/models/mod.rs diff --git a/crates/karva_core/src/discovery/models/module.rs b/crates/karva_test_semantic/src/discovery/models/module.rs similarity index 100% rename from crates/karva_core/src/discovery/models/module.rs rename to crates/karva_test_semantic/src/discovery/models/module.rs diff --git a/crates/karva_core/src/discovery/models/package.rs b/crates/karva_test_semantic/src/discovery/models/package.rs similarity index 100% rename from crates/karva_core/src/discovery/models/package.rs rename to crates/karva_test_semantic/src/discovery/models/package.rs diff --git a/crates/karva_core/src/discovery/visitor.rs b/crates/karva_test_semantic/src/discovery/visitor.rs similarity index 100% rename from crates/karva_core/src/discovery/visitor.rs rename to crates/karva_test_semantic/src/discovery/visitor.rs diff --git a/crates/karva_core/src/extensions/fixtures/builtins/mock_env.rs b/crates/karva_test_semantic/src/extensions/fixtures/builtins/mock_env.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/builtins/mock_env.rs rename to crates/karva_test_semantic/src/extensions/fixtures/builtins/mock_env.rs diff --git a/crates/karva_core/src/extensions/fixtures/builtins/mod.rs b/crates/karva_test_semantic/src/extensions/fixtures/builtins/mod.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/builtins/mod.rs rename to crates/karva_test_semantic/src/extensions/fixtures/builtins/mod.rs diff --git a/crates/karva_core/src/extensions/fixtures/builtins/temp_path.rs b/crates/karva_test_semantic/src/extensions/fixtures/builtins/temp_path.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/builtins/temp_path.rs rename to crates/karva_test_semantic/src/extensions/fixtures/builtins/temp_path.rs diff --git a/crates/karva_core/src/extensions/fixtures/finalizer.rs b/crates/karva_test_semantic/src/extensions/fixtures/finalizer.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/finalizer.rs rename to crates/karva_test_semantic/src/extensions/fixtures/finalizer.rs diff --git a/crates/karva_core/src/extensions/fixtures/mod.rs b/crates/karva_test_semantic/src/extensions/fixtures/mod.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/mod.rs rename to crates/karva_test_semantic/src/extensions/fixtures/mod.rs diff --git a/crates/karva_core/src/extensions/fixtures/normalized_fixture.rs b/crates/karva_test_semantic/src/extensions/fixtures/normalized_fixture.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/normalized_fixture.rs rename to crates/karva_test_semantic/src/extensions/fixtures/normalized_fixture.rs diff --git a/crates/karva_core/src/extensions/fixtures/python.rs b/crates/karva_test_semantic/src/extensions/fixtures/python.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/python.rs rename to crates/karva_test_semantic/src/extensions/fixtures/python.rs diff --git a/crates/karva_core/src/extensions/fixtures/scope.rs b/crates/karva_test_semantic/src/extensions/fixtures/scope.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/scope.rs rename to crates/karva_test_semantic/src/extensions/fixtures/scope.rs diff --git a/crates/karva_core/src/extensions/fixtures/traits.rs b/crates/karva_test_semantic/src/extensions/fixtures/traits.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/traits.rs rename to crates/karva_test_semantic/src/extensions/fixtures/traits.rs diff --git a/crates/karva_core/src/extensions/fixtures/utils.rs b/crates/karva_test_semantic/src/extensions/fixtures/utils.rs similarity index 100% rename from crates/karva_core/src/extensions/fixtures/utils.rs rename to crates/karva_test_semantic/src/extensions/fixtures/utils.rs diff --git a/crates/karva_core/src/extensions/functions/mod.rs b/crates/karva_test_semantic/src/extensions/functions/mod.rs similarity index 100% rename from crates/karva_core/src/extensions/functions/mod.rs rename to crates/karva_test_semantic/src/extensions/functions/mod.rs diff --git a/crates/karva_core/src/extensions/functions/python.rs b/crates/karva_test_semantic/src/extensions/functions/python.rs similarity index 100% rename from crates/karva_core/src/extensions/functions/python.rs rename to crates/karva_test_semantic/src/extensions/functions/python.rs diff --git a/crates/karva_core/src/extensions/functions/raises.rs b/crates/karva_test_semantic/src/extensions/functions/raises.rs similarity index 100% rename from crates/karva_core/src/extensions/functions/raises.rs rename to crates/karva_test_semantic/src/extensions/functions/raises.rs diff --git a/crates/karva_core/src/extensions/mod.rs b/crates/karva_test_semantic/src/extensions/mod.rs similarity index 100% rename from crates/karva_core/src/extensions/mod.rs rename to crates/karva_test_semantic/src/extensions/mod.rs diff --git a/crates/karva_core/src/extensions/tags/custom.rs b/crates/karva_test_semantic/src/extensions/tags/custom.rs similarity index 100% rename from crates/karva_core/src/extensions/tags/custom.rs rename to crates/karva_test_semantic/src/extensions/tags/custom.rs diff --git a/crates/karva_core/src/extensions/tags/expect_fail.rs b/crates/karva_test_semantic/src/extensions/tags/expect_fail.rs similarity index 100% rename from crates/karva_core/src/extensions/tags/expect_fail.rs rename to crates/karva_test_semantic/src/extensions/tags/expect_fail.rs diff --git a/crates/karva_core/src/extensions/tags/mod.rs b/crates/karva_test_semantic/src/extensions/tags/mod.rs similarity index 100% rename from crates/karva_core/src/extensions/tags/mod.rs rename to crates/karva_test_semantic/src/extensions/tags/mod.rs diff --git a/crates/karva_core/src/extensions/tags/parametrize.rs b/crates/karva_test_semantic/src/extensions/tags/parametrize.rs similarity index 100% rename from crates/karva_core/src/extensions/tags/parametrize.rs rename to crates/karva_test_semantic/src/extensions/tags/parametrize.rs diff --git a/crates/karva_core/src/extensions/tags/python.rs b/crates/karva_test_semantic/src/extensions/tags/python.rs similarity index 100% rename from crates/karva_core/src/extensions/tags/python.rs rename to crates/karva_test_semantic/src/extensions/tags/python.rs diff --git a/crates/karva_core/src/extensions/tags/skip.rs b/crates/karva_test_semantic/src/extensions/tags/skip.rs similarity index 100% rename from crates/karva_core/src/extensions/tags/skip.rs rename to crates/karva_test_semantic/src/extensions/tags/skip.rs diff --git a/crates/karva_core/src/extensions/tags/use_fixtures.rs b/crates/karva_test_semantic/src/extensions/tags/use_fixtures.rs similarity index 100% rename from crates/karva_core/src/extensions/tags/use_fixtures.rs rename to crates/karva_test_semantic/src/extensions/tags/use_fixtures.rs diff --git a/crates/karva_test_semantic/src/lib.rs b/crates/karva_test_semantic/src/lib.rs new file mode 100644 index 00000000..04c9c46b --- /dev/null +++ b/crates/karva_test_semantic/src/lib.rs @@ -0,0 +1,44 @@ +pub(crate) mod collection; +mod context; +pub(crate) mod diagnostic; +pub(crate) mod discovery; +pub(crate) mod extensions; +mod python; +mod runner; +pub mod testing; +pub mod utils; + +pub(crate) use context::Context; +pub use python::init_module; + +use karva_diagnostic::{Reporter, TestRunResult}; +use karva_metadata::ProjectSettings; +use karva_system::System; +use karva_system::path::{TestPath, TestPathError}; +use ruff_python_ast::PythonVersion; + +use crate::discovery::StandardDiscoverer; +use crate::runner::PackageRunner; +use crate::utils::attach_with_project; + +/// Run tests given the system, settings, Python version, reporter, and test paths. +/// +/// This encapsulates the core test execution logic: attaching to a Python interpreter, +/// discovering tests, and running them. +pub fn run_tests( + system: &dyn System, + settings: &ProjectSettings, + python_version: PythonVersion, + reporter: &dyn Reporter, + test_paths: Vec>, +) -> TestRunResult { + let context = Context::new(system, settings, python_version, reporter); + + attach_with_project(settings.terminal().show_python_output, |py| { + let session = StandardDiscoverer::new(&context).discover_with_py(py, test_paths); + + PackageRunner::new(&context).execute(py, &session); + + context.into_result() + }) +} diff --git a/crates/karva_core/src/python.rs b/crates/karva_test_semantic/src/python.rs similarity index 100% rename from crates/karva_core/src/python.rs rename to crates/karva_test_semantic/src/python.rs diff --git a/crates/karva_core/src/runner/finalizer_cache.rs b/crates/karva_test_semantic/src/runner/finalizer_cache.rs similarity index 100% rename from crates/karva_core/src/runner/finalizer_cache.rs rename to crates/karva_test_semantic/src/runner/finalizer_cache.rs diff --git a/crates/karva_core/src/runner/fixture_cache.rs b/crates/karva_test_semantic/src/runner/fixture_cache.rs similarity index 100% rename from crates/karva_core/src/runner/fixture_cache.rs rename to crates/karva_test_semantic/src/runner/fixture_cache.rs diff --git a/crates/karva_core/src/runner/fixture_resolver.rs b/crates/karva_test_semantic/src/runner/fixture_resolver.rs similarity index 100% rename from crates/karva_core/src/runner/fixture_resolver.rs rename to crates/karva_test_semantic/src/runner/fixture_resolver.rs diff --git a/crates/karva_core/src/runner/mod.rs b/crates/karva_test_semantic/src/runner/mod.rs similarity index 100% rename from crates/karva_core/src/runner/mod.rs rename to crates/karva_test_semantic/src/runner/mod.rs diff --git a/crates/karva_core/src/runner/package_runner.rs b/crates/karva_test_semantic/src/runner/package_runner.rs similarity index 100% rename from crates/karva_core/src/runner/package_runner.rs rename to crates/karva_test_semantic/src/runner/package_runner.rs diff --git a/crates/karva_core/src/runner/test_iterator.rs b/crates/karva_test_semantic/src/runner/test_iterator.rs similarity index 100% rename from crates/karva_core/src/runner/test_iterator.rs rename to crates/karva_test_semantic/src/runner/test_iterator.rs diff --git a/crates/karva_core/src/testing.rs b/crates/karva_test_semantic/src/testing.rs similarity index 100% rename from crates/karva_core/src/testing.rs rename to crates/karva_test_semantic/src/testing.rs diff --git a/crates/karva_core/src/utils.rs b/crates/karva_test_semantic/src/utils.rs similarity index 100% rename from crates/karva_core/src/utils.rs rename to crates/karva_test_semantic/src/utils.rs diff --git a/crates/karva_worker/Cargo.toml b/crates/karva_worker/Cargo.toml new file mode 100644 index 00000000..e7da7971 --- /dev/null +++ b/crates/karva_worker/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "karva_worker" +version = "0.0.0" + +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } + +[[bin]] +name = "karva-worker" +path = "src/bin/main.rs" + +[dependencies] +karva_cache = { workspace = true } +karva_cli = { workspace = true } +karva_diagnostic = { workspace = true } +karva_logging = { workspace = true } +karva_metadata = { workspace = true } +karva_python_semantic = { workspace = true } +karva_system = { workspace = true } +karva_test_semantic = { workspace = true } + +anyhow = { workspace = true } +argfile = { workspace = true } +camino = { workspace = true } +clap = { workspace = true, features = ["wrap_help", "string", "env"] } +colored = { workspace = true } +ruff_db = { workspace = true } +ruff_notebook = { workspace = true } +ruff_source_file = { workspace = true } +wild = { workspace = true } + +[lints] +workspace = true diff --git a/crates/karva_worker/src/bin/main.rs b/crates/karva_worker/src/bin/main.rs new file mode 100644 index 00000000..904c9e2d --- /dev/null +++ b/crates/karva_worker/src/bin/main.rs @@ -0,0 +1,5 @@ +use karva_worker::cli::{ExitStatus, karva_worker_main}; + +fn main() -> ExitStatus { + karva_worker_main(|args| args) +} diff --git a/crates/karva_core/src/cli.rs b/crates/karva_worker/src/cli.rs similarity index 89% rename from crates/karva_core/src/cli.rs rename to crates/karva_worker/src/cli.rs index 5900a8c2..373fe4ce 100644 --- a/crates/karva_core/src/cli.rs +++ b/crates/karva_worker/src/cli.rs @@ -19,17 +19,12 @@ use ruff_db::diagnostic::{DisplayDiagnosticConfig, FileResolver, Input, UnifiedF use ruff_db::files::File; use ruff_notebook::NotebookIndex; -use crate::Context; -use crate::discovery::StandardDiscoverer; -use crate::runner::PackageRunner; -use crate::utils::attach_with_project; - -/// Command-line arguments for the `karva_core` worker process. +/// Command-line arguments for the `karva_worker` process. /// /// This struct is used internally when tests are distributed across /// multiple worker processes for parallel execution. #[derive(Parser)] -#[command(name = "karva_core", about = "Karva test worker")] +#[command(name = "karva_worker", about = "Karva test worker")] struct Args { /// Directory where test results and duration cache are stored. #[arg(long)] @@ -77,7 +72,7 @@ impl ExitStatus { self as i32 } } -pub fn karva_core_main(f: impl FnOnce(Vec) -> Vec) -> ExitStatus { +pub fn karva_worker_main(f: impl FnOnce(Vec) -> Vec) -> ExitStatus { run(f).unwrap_or_else(|error| { use std::io::Write; @@ -161,15 +156,13 @@ fn run(f: impl FnOnce(Vec) -> Vec) -> anyhow::Result