Skip to content
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [Unreleased]

## [0.3.3] - 2026-09-09

### Added

- Select exact conformance client revisions with repeatable `--client-version`
arguments, without including every revision in a protocol era.

### Changed

- Run official conformance and Inspector inside a Docker tooling image, removing
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cf-integration"
version = "0.3.2"
version = "0.3.3"
edition = "2024"
rust-version = "1.97"
license = "Apache-2.0"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ cf-integration conformance run --lane external --standalone \
```

`--client-era` and `--server-era` accept `legacy`, `modern`, or `dual`.
To choose exact revisions instead of a client era, use repeatable
`--client-version` arguments, for example
`--client-version 2025-11-25 --client-version 2026-07-28`. This excludes
`2025-06-18` from the client matrix while leaving fixture-server selection unchanged.
`--bless` replaces only the selected baselines and only after every selected
run succeeds. `--standalone` permits the external lane only.
Ctrl-C finishes cleanup for the active run, skips the remaining matrix entries,
Expand Down
58 changes: 41 additions & 17 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,14 @@ impl Action {
lanes,
standalone,
client_eras,
client_versions,
server_eras,
..
}) => {
let mut summary = format!(
"Lane: {}\nClient era: {}\nServer era: {}",
join_lane_labels(lanes),
join_client_eras(client_eras),
join_client_eras(client_eras, client_versions),
join_server_eras(server_eras),
);
if *standalone {
Expand Down Expand Up @@ -238,7 +239,7 @@ fn join_lane_labels(lanes: &[SemanticLane]) -> String {
.join(", ")
}

fn join_client_eras(client_eras: &[ConformanceServerEra]) -> String {
fn join_client_eras(client_eras: &[ConformanceServerEra], client_versions: &[String]) -> String {
client_eras
.iter()
.map(|era| {
Expand All @@ -247,7 +248,12 @@ fn join_client_eras(client_eras: &[ConformanceServerEra]) -> String {
ConformanceServerEra::Legacy => LEGACY_CLIENT_PROTOCOL_VERSIONS,
ConformanceServerEra::Modern => MODERN_CLIENT_PROTOCOL_VERSIONS,
};
format!("{} [{}]", era.label(), versions.join(", "))
let selected = client_versions
.iter()
.filter(|version| versions.contains(&version.as_str()))
.map(String::as_str)
.collect::<Vec<_>>();
format!("{} [{}]", era.label(), selected.join(", "))
})
.collect::<Vec<_>>()
.join("; ")
Expand Down Expand Up @@ -421,7 +427,8 @@ pub(crate) fn resolve_action(cli: Cli, environment: &Environment) -> Result<Acti
}
Command::Conformance(args) => Ok(Action::Conformance(match args.command {
ConformanceCommand::Run(args) => {
let (client_eras, client_versions) = resolve_client_eras(args.client_era);
let (client_eras, client_versions) =
resolve_client_protocols(args.client_era, args.client_version);
let lanes = if standalone && args.lane.is_empty() {
vec![SemanticLane::ExternalDataPlane]
} else {
Expand Down Expand Up @@ -640,29 +647,46 @@ fn resolve_lanes(lanes: impl IntoIterator<Item = SemanticLane>) -> Vec<SemanticL
}
}

fn resolve_client_eras(
fn resolve_client_protocols(
eras: Vec<crate::cli::CliConformanceEra>,
selected_versions: Vec<String>,
) -> (Vec<ConformanceServerEra>, Vec<String>) {
let eras = if eras.is_empty() {
vec![crate::cli::CliConformanceEra::Modern]
let eras = if !selected_versions.is_empty() {
selected_versions
.iter()
.map(|version| {
if LEGACY_CLIENT_PROTOCOL_VERSIONS.contains(&version.as_str()) {
ConformanceServerEra::Legacy
} else {
ConformanceServerEra::Modern
}
})
.collect()
} else if eras.is_empty() {
vec![ConformanceServerEra::Modern]
} else {
eras
eras.into_iter().map(Into::into).collect()
};
let mut seen_eras = BTreeSet::new();
let eras = eras
.into_iter()
.map(Into::into)
.filter(|era| seen_eras.insert(*era))
.collect::<Vec<ConformanceServerEra>>();
let versions = if selected_versions.is_empty() {
eras.iter()
.flat_map(|era| match era {
ConformanceServerEra::Dual => DUAL_CLIENT_PROTOCOL_VERSIONS,
ConformanceServerEra::Legacy => LEGACY_CLIENT_PROTOCOL_VERSIONS,
ConformanceServerEra::Modern => MODERN_CLIENT_PROTOCOL_VERSIONS,
})
.map(|version| (*version).to_owned())
.collect()
} else {
selected_versions
};
let mut seen_versions = BTreeSet::new();
let versions = eras
.iter()
.flat_map(|era| match era {
ConformanceServerEra::Dual => DUAL_CLIENT_PROTOCOL_VERSIONS,
ConformanceServerEra::Legacy => LEGACY_CLIENT_PROTOCOL_VERSIONS,
ConformanceServerEra::Modern => MODERN_CLIENT_PROTOCOL_VERSIONS,
})
.map(|version| (*version).to_owned())
let versions = versions
.into_iter()
.filter(|version| seen_versions.insert(version.clone()))
.collect();
(eras, versions)
Expand Down
59 changes: 59 additions & 0 deletions src/app_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,65 @@ fn conformance_lanes_are_deduplicated_and_normalized() {
);
}

#[test]
fn conformance_selects_exact_client_versions_without_expanding_legacy() {
let resolved = action(
&[
"cf-integration",
"conformance",
"run",
"--lane",
"builtin",
"--client-version",
"2025-11-25",
"--client-version",
"2026-07-28",
"--client-version",
"2025-11-25",
"--server-era",
"dual",
],
&[],
);
let Action::Conformance(ConformanceAction::Run {
client_versions,
server_eras,
..
}) = &resolved
else {
panic!("expected a conformance run");
};
assert_eq!(client_versions, &["2025-11-25", "2026-07-28"]);
assert_eq!(server_eras, &[ConformanceServerEra::Dual]);
assert!(
resolved
.startup_summary()
.contains("Client era: legacy [2025-11-25]; modern [2026-07-28]")
);
}

#[test]
fn conformance_rejects_unknown_or_ambiguous_client_versions() {
for (arguments, expected) in [
(
vec!["--client-version", "2025-01-01"],
clap::error::ErrorKind::InvalidValue,
),
(
vec!["--client-version", "2025-11-25", "--client-era", "dual"],
clap::error::ErrorKind::ArgumentConflict,
),
] {
let error = Cli::try_parse_from(
["cf-integration", "conformance", "run"]
.into_iter()
.chain(arguments),
)
.expect_err("invalid client selection must fail before execution");
assert_eq!(error.kind(), expected);
}
}

#[test]
fn standalone_conformance_is_external_only() {
let standalone = action(
Expand Down
10 changes: 10 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::fmt;
use std::path::PathBuf;
use std::str::FromStr;

use crate::conformance::profile::DUAL_CLIENT_PROTOCOL_VERSIONS;
use crate::mcp::protocol::{LEGACY_PROTOCOL_VERSION, PROTOCOL_VERSION};
use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};

Expand Down Expand Up @@ -417,6 +418,15 @@ pub(crate) struct ConformanceRunArgs {
#[arg(long, value_enum, action = ArgAction::Append)]
pub(crate) client_era: Vec<CliConformanceEra>,

/// Exact client protocol revision; repeat to select a matrix instead of an era.
#[arg(
long,
value_parser = clap::builder::PossibleValuesParser::new(DUAL_CLIENT_PROTOCOL_VERSIONS.iter().copied()),
conflicts_with = "client_era",
action = ArgAction::Append
)]
pub(crate) client_version: Vec<String>,

/// Protocol era exposed by the fixture; repeat for a matrix.
#[arg(long, value_enum, action = ArgAction::Append)]
pub(crate) server_era: Vec<CliConformanceEra>,
Expand Down
9 changes: 9 additions & 0 deletions src/cli_public_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -571,12 +571,21 @@ fn conformance_accepts_repeatable_exact_lanes_and_protocol_eras() {
"--spec-version",
"2025-11-25",
]);
rejected(&[
"cf-integration",
"conformance",
"run",
"--client-version",
"2025-01-01",
]);
rejected(&[
"cf-integration",
"conformance",
"run",
"--client-version",
"2025-11-25",
"--client-era",
"dual",
]);
rejected(&["cf-integration", "conformance", "run", "--suite", "active"]);
rejected(&[
Expand Down