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
5 changes: 3 additions & 2 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/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ clap = { version = "4", features = ["derive", "env"] }
toml_edit = { version = "0.23.9" }
ctrlc = { version = "3.5.2", features = ["termination"] }
serde_json = { version = "1.0.149" }
semver = { version = "1.0.28" }
23 changes: 23 additions & 0 deletions crates/cli/src/commands/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub enum CommandError {
#[error(transparent)]
Clean(#[from] CleanError),

#[error(transparent)]
NextestVersionCheck(#[from] NextestVersionCheckError),

#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
Expand Down Expand Up @@ -62,3 +65,23 @@ pub enum CleanError {
#[error("Failed to remove file '{1}': {0}")]
RemoveFile(std::io::Error, PathBuf),
}

#[derive(thiserror::Error, Debug)]
pub enum NextestVersionCheckError {
#[error("Nextest is not installed. Please run `simplexup` to install.")]
NextestNotInstalled,

#[error("Failed to parse nextest version string '{0}': {1}")]
NextestVersionParseError(String, semver::Error),

#[error("Failed to parse the required version bound '{0}': {1}")]
NextestVersionReqParseError(String, semver::Error),

#[error(
"Your nextest version {current_version} does not meet the requirement: {required_bound}. Please run `simplexup` to install."
)]
UnsupportedNextestVersion {
current_version: String,
required_bound: String,
},
}
108 changes: 78 additions & 30 deletions crates/cli/src/commands/test.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use std::path::PathBuf;
use std::process::Stdio;

use semver::{Version, VersionReq};
use smplx_test::TestConfig;
use smplx_test::config::Verbosity;
use smplx_test::{SMPLX_TEST_MARKER, TestConfig};

use super::core::{TestArguments, TestFlags};
use super::error::CommandError;
use super::error::{CommandError, NextestVersionCheckError};

// TODO: it's impossible to insert "_smplx_test" constant value in concat macro, remove or reuse constant
/// Nextest dsl variable to filter and use only simplex tests
const SMPLX_DSL_TEST_MARKER: &str = concat!("test(/", "_smplx_test", "$/)");

pub struct Test {}

Expand All @@ -18,6 +23,8 @@ impl Test {
/// # Panics
/// Panics if the output of the cargo test command is not valid UTF-8.
pub fn run(mut config: TestConfig, args: &TestArguments, flags: &TestFlags) -> Result<(), CommandError> {
Test::enforce_nextest_version(&config.nextest_semver_requirement)?;

let cache_path = Self::get_test_config_cache_name()?;

if flags.verbose {
Expand Down Expand Up @@ -52,9 +59,10 @@ impl Test {
flags: &TestFlags,
) -> std::process::Command {
let mut cargo_test_command = std::process::Command::new("cargo");
cargo_test_command.arg("test");
cargo_test_command.arg("nextest");
cargo_test_command.arg("run");

cargo_test_command.args(Self::build_cargo_test_args(args, flags));
cargo_test_command.args(Self::build_cargo_nextest_args(args, flags));
cargo_test_command.args(Self::build_test_bin_args(args, flags));

cargo_test_command
Expand All @@ -66,55 +74,95 @@ impl Test {
cargo_test_command
}

fn build_cargo_test_args(args: &TestArguments, flags: &TestFlags) -> Vec<String> {
/// Builds `cargo nextest --version`
fn build_nextest_version_command() -> std::process::Command {
let mut nextest_version_command = std::process::Command::new("cargo");
nextest_version_command.arg("nextest").arg("--version");

nextest_version_command
}

fn enforce_nextest_version(required_bound: &str) -> Result<(), CommandError> {
let output = Self::build_nextest_version_command().output()?;

if !output.status.success() {
Err(NextestVersionCheckError::NextestNotInstalled)?;
}

let version_output = String::from_utf8(output.stdout).unwrap_or_default();

// Parse "cargo-nextest 0.9.133 (65e806bd5 2026-04-14)"
let version_str = version_output
.split_whitespace()
.nth(1)
.expect("Expected to have at least 2 arguments to obtain the nextest version");

let current_version = Version::parse(version_str)
.map_err(|e| NextestVersionCheckError::NextestVersionParseError(version_str.into(), e))?;

let requirement = VersionReq::parse(required_bound)
.map_err(|e| NextestVersionCheckError::NextestVersionReqParseError(required_bound.into(), e))?;

if !requirement.matches(&current_version) {
Err(NextestVersionCheckError::UnsupportedNextestVersion {
current_version: current_version.to_string(),
required_bound: required_bound.to_string(),
})?;
}

Ok(())
}

fn build_cargo_nextest_args(args: &TestArguments, flags: &TestFlags) -> Vec<String> {
let mut cargo_test_args = Vec::new();

if let Some(target) = &args.target {
cargo_test_args.push("--test".into());
cargo_test_args.push(target.clone());
if args.filters.is_empty() {
cargo_test_args.push("--filterset".into());

if let Some(target) = &args.target {
cargo_test_args.push(format!("binary({target}) and {SMPLX_DSL_TEST_MARKER}"));
} else {
cargo_test_args.push(SMPLX_DSL_TEST_MARKER.into());
}
} else {
cargo_test_args.extend(args.filters.iter().cloned());
}

if flags.no_fail_fast {
cargo_test_args.push("--no-fail-fast".into());
}
if flags.nocapture {
cargo_test_args.push("--nocapture".into());
}
if flags.quiet {
cargo_test_args.push("--cargo-quiet".into());
}
if flags.verbose {
cargo_test_args.push("--verbose".into());
}

cargo_test_args
}

fn build_test_bin_args(args: &TestArguments, flags: &TestFlags) -> Vec<String> {
fn build_test_bin_args(_args: &TestArguments, flags: &TestFlags) -> Vec<String> {
let mut test_bin_args = Vec::new();

test_bin_args.push("--".into());

// TODO: custom filters may run non-simplex tests due to cargo limitations. Figure out how to fix this
if args.filters.is_empty() {
test_bin_args.push(SMPLX_TEST_MARKER.to_string());
} else {
test_bin_args.extend(args.filters.iter().cloned());
}

test_bin_args.extend(Self::build_test_bin_flags(flags));
if !test_bin_args.is_empty() {
test_bin_args.insert(0, "--".into());
}

test_bin_args
}

fn build_test_bin_flags(flags: &TestFlags) -> Vec<String> {
let mut test_bin_args = Vec::new();
let mut test_bin_flags = Vec::new();

if flags.nocapture {
test_bin_args.push("--nocapture".into());
}
if flags.show_output {
test_bin_args.push("--show-output".into());
}
if flags.ignored {
test_bin_args.push("--ignored".into());
}
if flags.quiet {
test_bin_args.push("--quiet".into());
test_bin_flags.push("--ignored".into());
}

test_bin_args
test_bin_flags
}

fn get_test_config_cache_name() -> Result<PathBuf, CommandError> {
Expand Down
10 changes: 10 additions & 0 deletions crates/test/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use super::error::TestError;
pub const TEST_ENV_NAME: &str = "SIMPLEX_TEST_ENV";
pub const DEFAULT_TEST_MNEMONIC: &str = "exist carry drive collect lend cereal occur much tiger just involve mean";
pub const DEFAULT_BITCOINS: u64 = 10_000_000;
const DEFAULT_NEXTEST_SEMVER_REQUIREMENT: &str = "=0.9.133";

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
Expand All @@ -23,6 +24,9 @@ pub struct TestConfig {
pub esplora: Option<EsploraConfig>,
pub rpc: Option<RpcConfig>,
pub verbosity: Option<Verbosity>,
#[doc(hidden)]
#[serde(skip, default = "get_default_nextest_semver_requirement")]
pub nextest_semver_requirement: String,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -98,6 +102,12 @@ impl Default for TestConfig {
esplora: None,
rpc: None,
verbosity: Some(Verbosity(3)),
nextest_semver_requirement: get_default_nextest_semver_requirement(),
}
}
}

#[inline]
fn get_default_nextest_semver_requirement() -> String {
DEFAULT_NEXTEST_SEMVER_REQUIREMENT.into()
}
24 changes: 24 additions & 0 deletions simplexup/simplexup
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ set -eo pipefail
# NOTE: if you make modifications to this script, please increment the version number.
# WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date.
SIMPLEXUP_INSTALLER_VERSION="0.0.4"
NEXTEST_VERSION="0.9.133"

BASE_DIR=${XDG_CONFIG_HOME:-$HOME}
SIMPLEX_DIR=${SIMPLEX_DIR:-"$BASE_DIR/.simplex"}
Expand Down Expand Up @@ -63,6 +64,9 @@ main() {
LATEST_TAG=$(latest_tag)
SIMPLEX_VERSION=${SIMPLEX_VERSION:-$LATEST_TAG}

# Install default version of nextest for simplex
install_nextest_version "$NEXTEST_VERSION"

# Check if the requested version exists
if [[ "${SIMPLEX_VERSION}" != "${LATEST_TAG}" ]]; then
validate_tag_version "${SIMPLEX_VERSION}" "${SIMPLEX_REPO}"
Expand Down Expand Up @@ -601,6 +605,26 @@ install_simplex_from_commit() {
rm -rf "${tmp_dir}"
}

install_nextest_version() {
local nextest_version=$1

# Check if cargo-nextest is already installed and matches the required version
say "Installing cargo-nextest version..."
if check_cmd cargo-nextest nextest; then
local current_version
current_version=$(cargo nextest show-config version | head -n 1 | awk '{print $4}')

if [[ "$current_version" == "$nextest_version" ]]; then
say "cargo-nextest $nextest_version is already installed, skipping installation."
return 0
fi
fi

say "Installing cargo-nextest version $nextest_version..."
ensure cargo install cargo-nextest --version "$nextest_version" --locked --force
return 0
}

# Form a comma separated list with 'and' for user experience
form_str_list() {
local STR_LIST=("$@")
Expand Down
Loading