Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/karva/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ clap = { workspace = true, features = ["wrap_help", "string", "env"] }
colored = { workspace = true }
crossbeam-channel = { workspace = true }
notify-debouncer-mini = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true, features = ["release_max_level_debug"] }
wild = { workspace = true }

Expand Down
1 change: 1 addition & 0 deletions crates/karva/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod cache;
pub mod show_config;
pub mod snapshot;
pub mod test;
pub mod version;
46 changes: 46 additions & 0 deletions crates/karva/src/commands/show_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use std::fmt::Write;

use anyhow::{Context as _, Result};
use karva_cli::ShowConfigCommand;
use karva_logging::Printer;
use karva_metadata::{Options, ProjectMetadata, ProjectOptionsOverrides};
use karva_project::Project;
use karva_project::path::absolute;
use karva_python_semantic::current_python_version;

use crate::ExitStatus;
use crate::utils::cwd;

pub fn show_config(args: ShowConfigCommand) -> Result<ExitStatus> {
let cwd = cwd().map_err(|_| {
anyhow::anyhow!(
"The current working directory contains non-Unicode characters. karva only supports Unicode paths."
)
})?;

let python_version = current_python_version();

let config_file = args.config_file.as_ref().map(|path| absolute(path, &cwd));

let mut project_metadata = if let Some(config_file) = &config_file {
ProjectMetadata::from_config_file(config_file.clone(), &cwd, python_version)?
} else {
ProjectMetadata::discover(&cwd, python_version)?
};

let overrides =
ProjectOptionsOverrides::new(config_file, Options::default()).with_profile(args.profile);
project_metadata
.apply_overrides(&overrides)
.map_err(|err| anyhow::anyhow!("{err}"))?;

let project = Project::from_metadata(project_metadata);

let serialized =
toml::to_string(project.settings()).context("failed to serialize configuration")?;

let mut stdout = Printer::default().stream_for_message().lock();
write!(stdout, "{serialized}")?;

Ok(ExitStatus::Success)
}
3 changes: 3 additions & 0 deletions crates/karva/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ fn run(f: impl FnOnce(Vec<OsString>) -> Vec<OsString>) -> anyhow::Result<ExitSta
Command::Test(test_args) => commands::test::test(*test_args),
Command::Snapshot(snapshot_args) => commands::snapshot::snapshot(snapshot_args),
Command::Cache(cache_args) => commands::cache::cache(&cache_args),
Command::ShowConfig(show_config_args) => {
commands::show_config::show_config(show_config_args)
}
Command::Version => commands::version::version().map(|()| ExitStatus::Success),
}
}
Expand Down
11 changes: 6 additions & 5 deletions crates/karva/tests/it/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2653,11 +2653,12 @@ fn test_no_subcommand_prints_help() {
Usage: karva <COMMAND>

Commands:
test Run tests
snapshot Manage snapshots created by `karva.assert_snapshot()`
cache Manage the karva cache
version Display Karva's version
help Print this message or the help of the given subcommand(s)
test Run tests
snapshot Manage snapshots created by `karva.assert_snapshot()`
cache Manage the karva cache
show-config Print the resolved configuration karva would run with
version Display Karva's version
help Print this message or the help of the given subcommand(s)

Options:
-h, --help Print help
Expand Down
6 changes: 6 additions & 0 deletions crates/karva/tests/it/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,12 @@ impl TestContext {
command.arg("version").current_dir(self.root());
command
}

pub fn show_config(&self) -> Command {
let mut command = self.karva_command();
command.arg("show-config").current_dir(self.root());
command
}
}

impl Default for TestContext {
Expand Down
1 change: 1 addition & 0 deletions crates/karva/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ mod filterset;
mod last_failed;
mod partition;
mod run_ignored;
mod show_config;
mod version;
mod watch;
190 changes: 190 additions & 0 deletions crates/karva/tests/it/show_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use insta_cmd::assert_cmd_snapshot;

use crate::common::TestContext;

#[test]
fn show_config_default_profile() {
let context = TestContext::default();

assert_cmd_snapshot!(context.show_config(), @r#"
success: true
exit_code: 0
----- stdout -----
[src]
respect-ignore-files = true
include = []

[terminal]
output-format = "full"
show-python-output = false
status-level = "pass"
final-status-level = "pass"

[test]
test-function-prefix = "test"
try-import-fixtures = false
retry = 0
no-tests = "auto"

[coverage]
sources = []
report = "term"

----- stderr -----
"#);
}

#[test]
fn show_config_resolves_pyproject_options() {
let context = TestContext::with_file(
"pyproject.toml",
r#"
[tool.karva.profile.default.test]
test-function-prefix = "check"
fail-fast = true

[tool.karva.profile.default.terminal]
output-format = "concise"
"#,
);

assert_cmd_snapshot!(context.show_config(), @r#"
success: true
exit_code: 0
----- stdout -----
[src]
respect-ignore-files = true
include = []

[terminal]
output-format = "concise"
show-python-output = false
status-level = "pass"
final-status-level = "pass"

[test]
test-function-prefix = "check"
max-fail = 1
try-import-fixtures = false
retry = 0
no-tests = "auto"

[coverage]
sources = []
report = "term"

----- stderr -----
"#);
}

#[test]
fn show_config_named_profile_layers_over_default() {
let context = TestContext::with_file(
"karva.toml",
r#"
[profile.default.test]
test-function-prefix = "check"

[profile.ci.test]
retry = 3

[profile.ci.terminal]
output-format = "concise"
"#,
);

assert_cmd_snapshot!(context.show_config().args(["--profile", "ci"]), @r#"
success: true
exit_code: 0
----- stdout -----
[src]
respect-ignore-files = true
include = []

[terminal]
output-format = "concise"
show-python-output = false
status-level = "pass"
final-status-level = "pass"

[test]
test-function-prefix = "check"
try-import-fixtures = false
retry = 3
no-tests = "auto"

[coverage]
sources = []
report = "term"

----- stderr -----
"#);
}

#[test]
fn show_config_emits_set_timeouts_and_coverage() {
let context = TestContext::with_file(
"karva.toml",
r#"
[profile.default.test]
slow-timeout = 0.5
timeout = 120

[profile.default.coverage]
sources = ["src"]
report = "term-missing"
fail-under = 90
"#,
);

assert_cmd_snapshot!(context.show_config(), @r#"
success: true
exit_code: 0
----- stdout -----
[src]
respect-ignore-files = true
include = []

[terminal]
output-format = "full"
show-python-output = false
status-level = "pass"
final-status-level = "pass"

[test]
test-function-prefix = "test"
try-import-fixtures = false
retry = 0
no-tests = "auto"
slow-timeout = 0.5
timeout = 120.0

[coverage]
sources = ["src"]
report = "term-missing"
fail-under = 90.0

----- stderr -----
"#);
}

#[test]
fn show_config_unknown_profile_errors() {
let context = TestContext::with_file(
"karva.toml",
r"
[profile.ci.test]
retry = 3
",
);

assert_cmd_snapshot!(context.show_config().args(["--profile", "bogus"]), @r"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
Karva failed
Cause: profile `bogus` is not defined in configuration (available: ci, default)
");
}
5 changes: 5 additions & 0 deletions crates/karva_cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ use clap::builder::styling::{AnsiColor, Effects};
mod cache;
mod enums;
mod partition;
mod show_config;
mod snapshot;
mod test;
mod verbosity;

pub use cache::{CacheAction, CacheCommand};
pub use enums::{CovReport, NoTests, OutputFormat, RunIgnored};
pub use partition::PartitionSelection;
pub use show_config::ShowConfigCommand;
pub use snapshot::{
SnapshotAction, SnapshotCommand, SnapshotDeleteArgs, SnapshotFilterArgs, SnapshotPruneArgs,
};
Expand Down Expand Up @@ -44,6 +46,9 @@ pub enum Command {
/// Manage the karva cache.
Cache(CacheCommand),

/// Print the resolved configuration karva would run with.
ShowConfig(ShowConfigCommand),

/// Display Karva's version
Version,
}
31 changes: 31 additions & 0 deletions crates/karva_cli/src/show_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use camino::Utf8PathBuf;
use clap::Parser;

/// Print the resolved configuration karva would run with.
///
/// Resolves the same settings the test runner builds — defaults layered with
/// `karva.toml` / `pyproject.toml` and any selected profile — and prints them
/// as TOML.
#[derive(Debug, Parser)]
pub struct ShowConfigCommand {
/// The path to a `karva.toml` file to use for configuration.
#[arg(
long,
env = "KARVA_CONFIG_FILE",
value_name = "PATH",
help_heading = "Config options"
)]
pub config_file: Option<Utf8PathBuf>,

/// Configuration profile to resolve.
///
/// Defaults to `default`.
#[arg(
short = 'P',
long,
env = "KARVA_PROFILE",
value_name = "NAME",
help_heading = "Config options"
)]
pub profile: Option<String>,
}
9 changes: 9 additions & 0 deletions crates/karva_metadata/src/max_fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ impl MaxFail {
self.0.is_some()
}

/// Returns `true` when no failure limit is configured.
///
/// `MaxFail::unlimited()` wraps `None`, which serializers like TOML
/// cannot represent — this is exposed primarily so `serde`'s
/// `skip_serializing_if` can omit the field.
pub fn is_unlimited(&self) -> bool {
self.0.is_none()
}

/// Returns `true` when the configuration would stop after a single failure.
///
/// This is how the legacy `--fail-fast` boolean is surfaced internally.
Expand Down
Loading
Loading