From f5a36c65c5a39fc51b70268c8712e464f207ac23 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 12:39:07 +0100 Subject: [PATCH 01/44] test(configuration): cover source selection baseline for #2151 --- .../ISSUE.md | 13 +- packages/configuration/src/lib.rs | 190 ++++++++++++++++++ 2 files changed, 198 insertions(+), 5 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index b72a38773..71367611a 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -6,9 +6,9 @@ priority: p2 epic: null github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md -branch: "2151-add-tracker-config-path-argument-spec" -related-pr: null -last-updated-utc: 2026-09-07 10:30 +branch: "2151-add-tracker-config-path-argument" +related-pr: 2153 +last-updated-utc: 2026-09-07 11:45 semantic-links: skill-links: - create-issue @@ -302,7 +302,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | ID | Status | Task | Notes / Expected Output | | --- | ------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| T1 | TODO | Establish baseline source behavior | Add/refine configuration-package tests (using `figment::Jail`) that pin the existing complete-TOML, path-variable, override, mandatory-value, default, missing-file, and relative-path behavior before changing it. Follow the `write-unit-test` skill. | +| T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | | T2 | TODO | Introduce typed source selection | Refactor `Info` or an equivalent source type to accept an optional explicit file path (`PathBuf`/`Utf8PathBuf`) without reading CLI state in the configuration package. Validate the CLI path as an exact readable file before loading it, while preserving the existing environment-path semantics. Preserve existing callers. | | T3 | TODO | Define the CLI boundary | Use the existing `clap` dependency to parse `-c` / `--config-toml-path` **without** the `env` attribute. Keep parsing separate from configuration loading. Test short and long forms, missing value, unknown argument, and help output; assert exit code `2` for usage errors per the CLI output contract. | | T4 | TODO | Wire startup and precedence | Pass the parsed path through `app::start`, `bootstrap::app::setup`, and `initialize_configuration`. Implement CLI-path precedence without environment mutation. Add focused branch coverage. | @@ -323,7 +323,7 @@ are the deployable feature; later tasks extend verification and documentation. - [x] Folder-style spec drafted and moved to `docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md` - [x] Spec reviewed and approved by user/maintainer - [x] GitHub issue [#2151](https://github.com/torrust/torrust-tracker/issues/2151) created and issue number added to this spec -- [ ] (Optional, recommended for this cross-cutting issue) Spec-only PR merged into `develop` before implementation +- [x] Spec-only PR [#2153](https://github.com/torrust/torrust-tracker/pull/2153) merged into `develop` before implementation - [ ] First passing CLI-only vertical slice reviewed for ownership, cleanup, deadline, and ADR decisions - [ ] Implementation completed - [ ] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) @@ -343,6 +343,9 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 10:05 UTC - Maintainer / GitHub Copilot - Confirmed precedence: argument > `TORRUST_TRACKER_CONFIG_TOML` > `TORRUST_TRACKER_CONFIG_TOML_PATH` > default; overrides always merge on top. Added a dedicated "Proposed Source Precedence" section with the eight-row selection table and tied T1/T4/AC2/AC3 to it. - 2026-09-07 10:10 UTC - GitHub Copilot - Created GitHub issue [#2151](https://github.com/torrust/torrust-tracker/issues/2151) after maintainer approval. This spec will move to `docs/issues/open/2151-add-tracker-config-path-argument/`. - 2026-09-07 10:30 UTC - GitHub Copilot - Moved the approved specification to this open-issue folder and prepared the spec-only delivery branch. Source-level issue markers are deferred to the implementation branch so the specification PR changes only `docs/issues/`. +- 2026-09-07 11:21 UTC - GitHub - Merged spec-only PR [#2153](https://github.com/torrust/torrust-tracker/pull/2153) into `develop` (merge commit `796c8157`). +- 2026-09-07 11:25 UTC - GitHub Copilot - Created implementation branch `2151-add-tracker-config-path-argument` from the merged `torrust/develop` baseline. Started T1 baseline source-behavior analysis. +- 2026-09-07 11:45 UTC - GitHub Copilot - Completed T1. Added deterministic `Info::new` to `Configuration::load` regression tests using `figment::Jail` with cleared environment state. Verified complete-TOML, path, and default base-source selection; path-source override precedence; the missing-file `MissingMandatoryOption` result; and relative environment-path parent search. `cargo test --package torrust-tracker-configuration` passed (118 tests); `cargo fmt --check` and `git diff --check` passed. ## Acceptance Criteria diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 17aff9c5e..93399a123 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -181,6 +181,196 @@ impl Info { } } +#[cfg(test)] +mod tests { + use std::net::SocketAddr; + + use figment::Jail; + + use super::{ENV_VAR_CONFIG_TOML, ENV_VAR_CONFIG_TOML_PATH, Error, Info}; + use crate::v3_0_0::Configuration; + + const MANDATORY_CONFIGURATION: &str = r#" + [metadata] + schema_version = "3.0.0" + + [logging] + trace_filter = "info" + + [core] + listed = false + private = false + "#; + + fn configuration_with_health_check_port(port: u16) -> String { + format!( + r#" + {MANDATORY_CONFIGURATION} + + [health_check_api] + bind_address = "127.0.0.1:{port}" + "# + ) + } + + fn load_configuration(default_path: &str) -> Result { + let info = Info::new(default_path.to_owned())?; + + Configuration::load(&info) + } + + fn health_check_address(port: u16) -> SocketAddr { + format!("127.0.0.1:{port}") + .parse() + .expect("test health-check address should parse") + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_select_complete_toml_when_complete_toml_and_path_environment_sources_are_set() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let path_configuration = configuration_with_health_check_port(41001); + jail.create_file("path.toml", &path_configuration)?; + jail.set_env(ENV_VAR_CONFIG_TOML, configuration_with_health_check_port(41002)); + jail.set_env(ENV_VAR_CONFIG_TOML_PATH, "path.toml"); + + // Act + let configuration = load_configuration("default.toml").expect("complete TOML source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41002)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_select_complete_toml_when_only_complete_toml_environment_source_is_set() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.set_env(ENV_VAR_CONFIG_TOML, configuration_with_health_check_port(41003)); + + // Act + let configuration = load_configuration("default.toml").expect("complete TOML source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41003)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_select_the_path_file_when_only_path_environment_source_is_set() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let path_configuration = configuration_with_health_check_port(41004); + jail.create_file("path.toml", &path_configuration)?; + jail.set_env(ENV_VAR_CONFIG_TOML_PATH, "path.toml"); + + // Act + let configuration = load_configuration("default.toml").expect("path environment source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41004)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_select_the_given_default_file_when_no_environment_base_source_is_set() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let default_configuration = configuration_with_health_check_port(41005); + jail.create_file("default.toml", &default_configuration)?; + + // Act + let configuration = load_configuration("default.toml").expect("default source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41005)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_apply_an_environment_override_to_a_path_environment_source() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let path_configuration = configuration_with_health_check_port(41006); + jail.create_file("path.toml", &path_configuration)?; + jail.set_env(ENV_VAR_CONFIG_TOML_PATH, "path.toml"); + jail.set_env( + "TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS", + "127.0.0.1:41007", + ); + + // Act + let configuration = load_configuration("default.toml").expect("path environment source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41007)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_report_the_first_mandatory_option_when_the_path_environment_file_is_missing() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.set_env(ENV_VAR_CONFIG_TOML_PATH, "missing.toml"); + + // Act + let result = load_configuration("default.toml"); + + // Assert + assert!(matches!( + result, + Err(Error::MissingMandatoryOption { path }) if path == "metadata.schema_version" + )); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_search_parent_directories_for_a_relative_path_environment_source() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let parent_configuration = configuration_with_health_check_port(41008); + jail.create_file("tracker.toml", &parent_configuration)?; + jail.create_dir("child")?; + jail.change_dir("child")?; + jail.set_env(ENV_VAR_CONFIG_TOML_PATH, "tracker.toml"); + + // Act + let configuration = load_configuration("default.toml").expect("parent-directory source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41008)); + + Ok(()) + }); + } +} + /// Announce policy for the `BitTorrent` announce cycle. /// /// **Deprecated**: import from [`torrust_tracker_primitives::AnnouncePolicy`] instead. From 316026689dd9d4a65df6dc0ab23d8b43211248d0 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 13:13:58 +0100 Subject: [PATCH 02/44] refactor(configuration): add explicit file source for #2151 --- .../ISSUE.md | 25 +- packages/configuration/src/lib.rs | 333 +++++++++++++++++- packages/configuration/src/v2_0_0/mod.rs | 30 +- packages/configuration/src/v3_0_0/mod.rs | 40 ++- 4 files changed, 391 insertions(+), 37 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 71367611a..d774d6cb9 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 11:45 +last-updated-utc: 2026-09-07 12:10 semantic-links: skill-links: - create-issue @@ -300,17 +300,17 @@ first vertical slice: Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Notes / Expected Output | -| --- | ------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | -| T2 | TODO | Introduce typed source selection | Refactor `Info` or an equivalent source type to accept an optional explicit file path (`PathBuf`/`Utf8PathBuf`) without reading CLI state in the configuration package. Validate the CLI path as an exact readable file before loading it, while preserving the existing environment-path semantics. Preserve existing callers. | -| T3 | TODO | Define the CLI boundary | Use the existing `clap` dependency to parse `-c` / `--config-toml-path` **without** the `env` attribute. Keep parsing separate from configuration loading. Test short and long forms, missing value, unknown argument, and help output; assert exit code `2` for usage errors per the CLI output contract. | -| T4 | TODO | Wire startup and precedence | Pass the parsed path through `app::start`, `bootstrap::app::setup`, and `initialize_configuration`. Implement CLI-path precedence without environment mutation. Add focused branch coverage. | -| T5 | TODO | Review first vertical slice | Review source ownership, normal/failure/drop cleanup, readiness deadlines, and ADR need after a CLI-only executable test passes. Resolve material findings before continuing. | -| T6 | TODO | Preserve overrides and defaults | Verify a per-value override wins over a CLI-selected file, mandatory fields remain explicit, and optional fields still receive Rust defaults. | -| T7 | TODO | Add executable-boundary coverage | Extend the native fixture or add a focused fixture to launch two children with distinct CLI paths, isolated storage, and port-zero bindings. Call `env_remove` for both `TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` on each `Command`; children inherit the parent environment otherwise. Use progressive test increments and stop for maintainer review after the final increment. | -| T8 | TODO | Update documentation | Update only affected user-facing documentation: `README.md`, root/configuration crate docs, `tests/AGENTS.md`, and relevant container, benchmarking, profiling, and `Containerfile` references. Retain valid environment examples and avoid duplicating procedures. | -| T9 | TODO | Validate and record evidence | Run checks, execute manual scenarios, re-review every acceptance criterion against evidence, and complete the implementation completion review. | +| ID | Status | Task | Notes / Expected Output | +| --- | ------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | +| T2 | DONE | Introduce typed source selection | Added `Info::new_with_explicit_config_toml_path(..., Option)`. Explicit paths are eagerly read and loaded from retained contents, take precedence over environment base sources, preserve their exact `PathBuf` in diagnostics, and never use parent lookup. Legacy environment and default file behavior remains on `Toml::file`. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); Rust formatting and Clippy passed. | +| T3 | TODO | Define the CLI boundary | Use the existing `clap` dependency to parse `-c` / `--config-toml-path` **without** the `env` attribute. Keep parsing separate from configuration loading. Test short and long forms, missing value, unknown argument, and help output; assert exit code `2` for usage errors per the CLI output contract. | +| T4 | TODO | Wire startup and precedence | Pass the parsed path through `app::start`, `bootstrap::app::setup`, and `initialize_configuration`. Implement CLI-path precedence without environment mutation. Add focused branch coverage. | +| T5 | TODO | Review first vertical slice | Review source ownership, normal/failure/drop cleanup, readiness deadlines, and ADR need after a CLI-only executable test passes. Resolve material findings before continuing. | +| T6 | TODO | Preserve overrides and defaults | Verify a per-value override wins over a CLI-selected file, mandatory fields remain explicit, and optional fields still receive Rust defaults. | +| T7 | TODO | Add executable-boundary coverage | Extend the native fixture or add a focused fixture to launch two children with distinct CLI paths, isolated storage, and port-zero bindings. Call `env_remove` for both `TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` on each `Command`; children inherit the parent environment otherwise. Use progressive test increments and stop for maintainer review after the final increment. | +| T8 | TODO | Update documentation | Update only affected user-facing documentation: `README.md`, root/configuration crate docs, `tests/AGENTS.md`, and relevant container, benchmarking, profiling, and `Containerfile` references. Retain valid environment examples and avoid duplicating procedures. | +| T9 | TODO | Validate and record evidence | Run checks, execute manual scenarios, re-review every acceptance criterion against evidence, and complete the implementation completion review. | Each task must be independently buildable and tested. T1 is a behavior-preserving safety-net change; T2 is a configuration refactor; T3-T4 @@ -346,6 +346,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 11:21 UTC - GitHub - Merged spec-only PR [#2153](https://github.com/torrust/torrust-tracker/pull/2153) into `develop` (merge commit `796c8157`). - 2026-09-07 11:25 UTC - GitHub Copilot - Created implementation branch `2151-add-tracker-config-path-argument` from the merged `torrust/develop` baseline. Started T1 baseline source-behavior analysis. - 2026-09-07 11:45 UTC - GitHub Copilot - Completed T1. Added deterministic `Info::new` to `Configuration::load` regression tests using `figment::Jail` with cleared environment state. Verified complete-TOML, path, and default base-source selection; path-source override precedence; the missing-file `MissingMandatoryOption` result; and relative environment-path parent search. `cargo test --package torrust-tracker-configuration` passed (118 tests); `cargo fmt --check` and `git diff --check` passed. +- 2026-09-07 12:10 UTC - GitHub Copilot - Completed T2. Added typed `PathBuf` source selection through `Info::new_with_explicit_config_toml_path`, retaining explicit source identity and eagerly captured TOML contents. Both v2 and v3 loaders use the captured contents for explicit files while retaining legacy file-provider behavior for environment/default sources. Added precedence, override, missing-file, directory, exact-relative-path, malformed-content redaction, non-UTF-8-path, and captured-content regression coverage. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. ## Acceptance Criteria diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 93399a123..9a2e8f8d8 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -12,6 +12,9 @@ pub mod validator; use std::collections::HashMap; use std::env; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; use std::sync::Arc; use camino::Utf8PathBuf; @@ -141,6 +144,14 @@ impl Version { pub struct Info { config_toml: Option, config_toml_path: String, + explicit_config_toml_path: Option, +} + +/// The base source from which to load configuration data. +pub(crate) enum ConfigTomlSource<'a> { + Inline(&'a str), + Explicit { path: &'a Path, contents: &'a str }, + File(&'a str), } impl Info { @@ -152,6 +163,42 @@ impl Info { /// #[allow(clippy::needless_pass_by_value)] pub fn new(default_config_toml_path: String) -> Result { + Self::new_with_explicit_config_toml_path(default_config_toml_path, None) + } + + /// Builds configuration information from an optional explicit configuration-file path. + /// + /// An explicit path takes priority over all environment base sources. Its contents are read + /// eagerly so the path is resolved exactly from the current working directory and no Figment + /// parent-directory lookup occurs. + /// + /// # Errors + /// + /// Returns [`Error::UnableToLoadExplicitConfigFile`] if the explicit path cannot be read as a + /// regular file. + #[allow(clippy::needless_pass_by_value)] + pub fn new_with_explicit_config_toml_path( + default_config_toml_path: String, + explicit_config_toml_path: Option, + ) -> Result { + match explicit_config_toml_path { + Some(path) => Self::from_explicit_file(default_config_toml_path, path), + None => Ok(Self::from_environment(default_config_toml_path)), + } + } + + fn from_explicit_file(default_config_toml_path: String, path: PathBuf) -> Result { + let config_toml = Self::read_explicit_config_toml_file(&path)?; + info!(path = ?path, "Loading extra configuration from explicit configuration file"); + + Ok(Self { + config_toml: Some(config_toml), + config_toml_path: default_config_toml_path, + explicit_config_toml_path: Some(path), + }) + } + + fn from_environment(default_config_toml_path: String) -> Self { let env_var_config_toml = ENV_VAR_CONFIG_TOML.to_string(); let env_var_config_toml_path = ENV_VAR_CONFIG_TOML_PATH.to_string(); @@ -174,16 +221,65 @@ impl Info { }, ); - Ok(Self { - config_toml, - config_toml_path, + // The path is irrelevant when inline configuration is selected. Keeping it empty also + // distinguishes this legacy environment source from an explicit file source. + match config_toml { + None => Self { + config_toml: None, + config_toml_path, + explicit_config_toml_path: None, + }, + Some(config_toml) => Self { + config_toml: Some(config_toml), + config_toml_path, + explicit_config_toml_path: None, + }, + } + } + + pub(crate) fn config_toml_source(&self) -> ConfigTomlSource<'_> { + match (&self.explicit_config_toml_path, &self.config_toml) { + (Some(path), Some(contents)) => ConfigTomlSource::Explicit { path, contents }, + (None, Some(config_toml)) => ConfigTomlSource::Inline(config_toml), + (_, None) => ConfigTomlSource::File(&self.config_toml_path), + } + } + + pub(crate) fn attach_explicit_config_path(&self, source: Error) -> Error { + match self.config_toml_source() { + ConfigTomlSource::Explicit { path, .. } => Error::UnableToProcessExplicitConfigFile { + path: path.to_path_buf(), + source: (Arc::new(source) as DynError).into(), + }, + ConfigTomlSource::Inline(_) | ConfigTomlSource::File(_) => source, + } + } + + fn read_explicit_config_toml_file(path: &PathBuf) -> Result { + let metadata = fs::metadata(path).map_err(|source| Error::UnableToLoadExplicitConfigFile { + path: path.clone(), + source, + })?; + + if !metadata.is_file() { + return Err(Error::UnableToLoadExplicitConfigFile { + path: path.clone(), + source: io::Error::new(io::ErrorKind::InvalidInput, "path is not a regular file"), + }); + } + + fs::read_to_string(path).map_err(|source| Error::UnableToLoadExplicitConfigFile { + path: path.clone(), + source, }) } } #[cfg(test)] mod tests { + use std::io; use std::net::SocketAddr; + use std::path::PathBuf; use figment::Jail; @@ -219,6 +315,12 @@ mod tests { Configuration::load(&info) } + fn load_configuration_with_explicit_path(path: PathBuf) -> Result { + let info = Info::new_with_explicit_config_toml_path("default.toml".to_owned(), Some(path))?; + + Configuration::load(&info) + } + fn health_check_address(port: u16) -> SocketAddr { format!("127.0.0.1:{port}") .parse() @@ -369,6 +471,211 @@ mod tests { Ok(()) }); } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_select_an_explicit_file_over_complete_toml_and_path_environment_sources() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.create_file("explicit.toml", &configuration_with_health_check_port(41009))?; + jail.create_file("path.toml", &configuration_with_health_check_port(41010))?; + jail.set_env(ENV_VAR_CONFIG_TOML, configuration_with_health_check_port(41011)); + jail.set_env(ENV_VAR_CONFIG_TOML_PATH, "path.toml"); + + // Act + let configuration = + load_configuration_with_explicit_path(PathBuf::from("explicit.toml")).expect("explicit source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41009)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_select_an_explicit_file_when_no_environment_base_source_is_set() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.create_file("explicit.toml", &configuration_with_health_check_port(41012))?; + + // Act + let configuration = + load_configuration_with_explicit_path(PathBuf::from("explicit.toml")).expect("explicit source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41012)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_apply_an_environment_override_to_an_explicit_file() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.create_file("explicit.toml", &configuration_with_health_check_port(41013))?; + jail.set_env( + "TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS", + "127.0.0.1:41014", + ); + + // Act + let configuration = + load_configuration_with_explicit_path(PathBuf::from("explicit.toml")).expect("explicit source should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41014)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_return_a_path_specific_error_when_an_explicit_file_is_missing() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let path = PathBuf::from("missing.toml"); + + // Act + let result = load_configuration_with_explicit_path(path.clone()); + + // Assert + assert!(matches!( + result, + Err(Error::UnableToLoadExplicitConfigFile { + path: error_path, + source, + }) if error_path == path && source.kind() == io::ErrorKind::NotFound + )); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_return_a_path_specific_error_when_an_explicit_path_is_a_directory() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.create_dir("configuration")?; + let path = PathBuf::from("configuration"); + + // Act + let result = load_configuration_with_explicit_path(path.clone()); + + // Assert + assert!(matches!( + result, + Err(Error::UnableToLoadExplicitConfigFile { + path: error_path, + source, + }) if error_path == path && source.kind() == io::ErrorKind::InvalidInput + )); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_not_search_parent_directories_for_a_relative_explicit_path() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.create_file("tracker.toml", &configuration_with_health_check_port(41015))?; + jail.create_dir("child")?; + jail.change_dir("child")?; + let path = PathBuf::from("tracker.toml"); + + // Act + let result = load_configuration_with_explicit_path(path.clone()); + + // Assert + assert!(matches!(result, Err(Error::UnableToLoadExplicitConfigFile { path: error_path, .. }) if error_path == path)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_include_the_explicit_path_but_not_contents_when_explicit_toml_is_malformed() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let path = PathBuf::from("malformed.toml"); + let malformed_content = "sensitive-malformed-content = ["; + jail.create_file(&path, malformed_content)?; + + // Act + let error = load_configuration_with_explicit_path(path.clone()).expect_err("malformed explicit TOML should not load"); + + // Assert + let display = error.to_string(); + assert!(display.contains(path.to_str().expect("test path should be UTF-8"))); + assert!(!display.contains(malformed_content)); + + Ok(()) + }); + } + + #[cfg(unix)] + #[test] + #[allow(clippy::result_large_err)] + fn it_should_preserve_a_non_utf8_explicit_path_when_explicit_toml_is_malformed() { + use std::os::unix::ffi::OsStringExt; + + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let path = PathBuf::from(std::ffi::OsString::from_vec(b"malformed-\xFF.toml".to_vec())); + let malformed_content = "sensitive-malformed-content = ["; + jail.create_file(&path, malformed_content)?; + + // Act + let error = load_configuration_with_explicit_path(path.clone()).expect_err("malformed explicit TOML should not load"); + + // Assert + assert!( + matches!(error, Error::UnableToProcessExplicitConfigFile { path: ref error_path, .. } if error_path == &path) + ); + assert!(!error.to_string().contains(malformed_content)); + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_load_the_content_read_when_the_explicit_file_changes_after_info_is_created() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let path = PathBuf::from("explicit.toml"); + let initially_read_content = configuration_with_health_check_port(41016); + jail.create_file(&path, &initially_read_content)?; + let info = Info::new_with_explicit_config_toml_path("default.toml".to_owned(), Some(path)) + .expect("explicit configuration file should be readable"); + jail.create_file("explicit.toml", &configuration_with_health_check_port(41017))?; + + // Act + let configuration = Configuration::load(&info).expect("eagerly read explicit content should load"); + + // Assert + assert_eq!(configuration.health_check_api.bind_address, health_check_address(41016)); + + Ok(()) + }); + } } /// Announce policy for the `BitTorrent` announce cycle. @@ -387,6 +694,26 @@ pub use torrust_tracker_primitives::AnnouncePolicy; /// Errors that can occur when loading the configuration. #[derive(Error, Debug)] pub enum Error { + /// Unable to read an explicitly selected configuration file. + #[error("Unable to load explicit configuration file `{path}`: {source}")] + UnableToLoadExplicitConfigFile { + /// The explicitly selected path that could not be read. + path: PathBuf, + /// The file-system failure. + #[source] + source: io::Error, + }, + + /// Unable to parse or extract an explicitly selected configuration file. + #[error("Unable to process explicit configuration file `{path}`: {source}")] + UnableToProcessExplicitConfigFile { + /// The explicitly selected path whose contents could not be processed. + path: PathBuf, + /// The preserved configuration diagnostic. + #[source] + source: LocatedError<'static, dyn std::error::Error + Send + Sync>, + }, + /// Unable to load the configuration from the environment variable. /// This error only occurs if there is no configuration file and the /// `TORRUST_TRACKER_CONFIG_TOML` environment variable is not set. diff --git a/packages/configuration/src/v2_0_0/mod.rs b/packages/configuration/src/v2_0_0/mod.rs index cf55fac60..5b68068bd 100644 --- a/packages/configuration/src/v2_0_0/mod.rs +++ b/packages/configuration/src/v2_0_0/mod.rs @@ -252,7 +252,7 @@ use self::http_tracker::HttpTracker; use self::tracker_api::HttpApi; use self::udp_tracker::UdpTracker; use crate::validator::{SemanticValidationError, Validator}; -use crate::{Error, Info, Metadata, Version}; +use crate::{ConfigTomlSource, Error, Info, Metadata, Version}; /// This configuration version const VERSION_2_0_0: &str = "2.0.0"; @@ -336,17 +336,17 @@ impl Configuration { /// /// Will return `Err` if the environment variable does not exist or has a bad configuration. pub fn load(info: &Info) -> Result { + Self::load_from_source(info).map_err(|source| info.attach_explicit_config_path(source)) + } + + fn load_from_source(info: &Info) -> Result { // Load configuration provided by the user, prioritizing env vars - let figment = info.config_toml.as_ref().map_or_else( - || { - Figment::from(Toml::file(&info.config_toml_path)) - .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) - }, - |config_toml| { - Figment::from(Toml::string(config_toml)) - .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) - }, - ); + let figment = match info.config_toml_source() { + ConfigTomlSource::Inline(config_toml) => Figment::from(Toml::string(config_toml)), + ConfigTomlSource::Explicit { contents, .. } => Figment::from(Toml::string(contents)), + ConfigTomlSource::File(path) => Figment::from(Toml::file(path)), + } + .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)); // Make sure user has provided the mandatory options. Self::check_mandatory_options(&figment)?; @@ -577,6 +577,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -610,6 +611,7 @@ mod tests { let info = Info { config_toml: Some(config_toml), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -646,6 +648,7 @@ mod tests { let info = Info { config_toml: Some(config_toml), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -681,6 +684,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -700,6 +704,7 @@ mod tests { let info = Info { config_toml: Some(default_config_toml()), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -804,6 +809,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let config = Configuration::load(&info).expect("Should load config"); @@ -842,6 +848,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let result = Configuration::load(&info); @@ -877,6 +884,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let result = Configuration::load(&info); diff --git a/packages/configuration/src/v3_0_0/mod.rs b/packages/configuration/src/v3_0_0/mod.rs index b93ffa2f1..77217c0b2 100644 --- a/packages/configuration/src/v3_0_0/mod.rs +++ b/packages/configuration/src/v3_0_0/mod.rs @@ -284,7 +284,7 @@ use self::tracker_api::HttpApi; use self::udp_tracker::UdpTracker; use self::udp_tracker_server::UdpTrackerServer; use crate::validator::{SemanticValidationError, Validator}; -use crate::{Error, Info, Metadata, Version}; +use crate::{ConfigTomlSource, Error, Info, Metadata, Version}; /// This configuration version const VERSION_3_0_0: &str = "3.0.0"; @@ -367,17 +367,17 @@ impl Configuration { /// /// Will return `Err` if the environment variable does not exist or has a bad configuration. pub fn load(info: &Info) -> Result { + Self::load_from_source(info).map_err(|source| info.attach_explicit_config_path(source)) + } + + fn load_from_source(info: &Info) -> Result { // Load configuration provided by the user, prioritizing env vars - let figment = info.config_toml.as_ref().map_or_else( - || { - Figment::from(Toml::file(&info.config_toml_path)) - .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) - }, - |config_toml| { - Figment::from(Toml::string(config_toml)) - .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)) - }, - ); + let figment = match info.config_toml_source() { + ConfigTomlSource::Inline(config_toml) => Figment::from(Toml::string(config_toml)), + ConfigTomlSource::Explicit { contents, .. } => Figment::from(Toml::string(contents)), + ConfigTomlSource::File(path) => Figment::from(Toml::file(path)), + } + .merge(Env::prefixed(CONFIG_OVERRIDE_PREFIX).split(CONFIG_OVERRIDE_SEPARATOR)); // Make sure user has provided the mandatory options. Self::check_mandatory_options(&figment)?; @@ -650,6 +650,7 @@ mod tests { .to_string(), ), config_toml_path: String::new(), + explicit_config_toml_path: None, }; // Act @@ -691,6 +692,7 @@ mod tests { .to_string() .into(), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("configuration should load"); @@ -730,6 +732,7 @@ mod tests { .to_string() .into(), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("configuration should load"); @@ -764,6 +767,7 @@ mod tests { .to_string() .into(), config_toml_path: String::new(), + explicit_config_toml_path: None, }; assert!( @@ -822,6 +826,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -855,6 +860,7 @@ mod tests { let info = Info { config_toml: Some(config_toml), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -891,6 +897,7 @@ mod tests { let info = Info { config_toml: Some(config_toml), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -931,6 +938,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -973,6 +981,7 @@ mod tests { "# )), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("network database configuration should load"); @@ -1006,6 +1015,7 @@ mod tests { let info = Info { config_toml: Some(default_config_toml()), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("Could not load configuration from file"); @@ -1181,6 +1191,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let config = Configuration::load(&info).expect("Should load config"); @@ -1226,6 +1237,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let config = Configuration::load(&info).expect("Should load config"); @@ -1269,6 +1281,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let configuration = Configuration::load(&info).expect("configuration should load"); @@ -1309,6 +1322,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let result = Configuration::load(&info); @@ -1344,6 +1358,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let result = Configuration::load(&info); @@ -1379,6 +1394,7 @@ mod tests { let info = Info { config_toml: None, config_toml_path: "tracker.toml".to_string(), + explicit_config_toml_path: None, }; let result = Configuration::load(&info); @@ -1413,6 +1429,7 @@ mod tests { let info = Info { config_toml: Some(config_toml), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let result = Configuration::load(&info); @@ -1442,6 +1459,7 @@ mod tests { let info = Info { config_toml: Some(config_toml), config_toml_path: String::new(), + explicit_config_toml_path: None, }; let result = Configuration::load(&info); From 0e336ef7448feca8bb4adab24cd4b08f912b6f42 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 16:06:59 +0100 Subject: [PATCH 03/44] feat(tracker): add explicit configuration path argument --- .../ISSUE.md | 10 +- src/app.rs | 20 ++- src/bootstrap/app.rs | 9 +- src/bootstrap/config.rs | 142 +++++++++++++++++- src/main.rs | 126 +++++++++++++++- 5 files changed, 294 insertions(+), 13 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index d774d6cb9..af104540b 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 12:10 +last-updated-utc: 2026-09-07 15:15 semantic-links: skill-links: - create-issue @@ -304,9 +304,9 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | --- | ------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | | T2 | DONE | Introduce typed source selection | Added `Info::new_with_explicit_config_toml_path(..., Option)`. Explicit paths are eagerly read and loaded from retained contents, take precedence over environment base sources, preserve their exact `PathBuf` in diagnostics, and never use parent lookup. Legacy environment and default file behavior remains on `Toml::file`. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); Rust formatting and Clippy passed. | -| T3 | TODO | Define the CLI boundary | Use the existing `clap` dependency to parse `-c` / `--config-toml-path` **without** the `env` attribute. Keep parsing separate from configuration loading. Test short and long forms, missing value, unknown argument, and help output; assert exit code `2` for usage errors per the CLI output contract. | -| T4 | TODO | Wire startup and precedence | Pass the parsed path through `app::start`, `bootstrap::app::setup`, and `initialize_configuration`. Implement CLI-path precedence without environment mutation. Add focused branch coverage. | -| T5 | TODO | Review first vertical slice | Review source ownership, normal/failure/drop cleanup, readiness deadlines, and ADR need after a CLI-only executable test passes. Resolve material findings before continuing. | +| T3 | DONE | Define the CLI boundary | Added a main-owned `clap` parser for `-c` / `--config-toml-path` with no `env` binding. Parser tests cover short/long forms, missing/empty values, unknown arguments, help, exit codes, and absent environment binding. `cargo test --package torrust-tracker --bin torrust-tracker` passed (7 tests). | +| T4 | DONE | Wire startup and precedence | Threaded `Option` from `main` through `app::start_with_explicit_config_toml_path`, `bootstrap::app::setup`, and `initialize_configuration` into T2 without environment mutation. Direct bootstrap tests cover every CLI-present table row: CLI only, CLI plus full TOML, CLI plus path, and CLI plus both sources. Root library tests passed (84 tests). | +| T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | | T6 | TODO | Preserve overrides and defaults | Verify a per-value override wins over a CLI-selected file, mandatory fields remain explicit, and optional fields still receive Rust defaults. | | T7 | TODO | Add executable-boundary coverage | Extend the native fixture or add a focused fixture to launch two children with distinct CLI paths, isolated storage, and port-zero bindings. Call `env_remove` for both `TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` on each `Command`; children inherit the parent environment otherwise. Use progressive test increments and stop for maintainer review after the final increment. | | T8 | TODO | Update documentation | Update only affected user-facing documentation: `README.md`, root/configuration crate docs, `tests/AGENTS.md`, and relevant container, benchmarking, profiling, and `Containerfile` references. Retain valid environment examples and avoid duplicating procedures. | @@ -347,6 +347,8 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 11:25 UTC - GitHub Copilot - Created implementation branch `2151-add-tracker-config-path-argument` from the merged `torrust/develop` baseline. Started T1 baseline source-behavior analysis. - 2026-09-07 11:45 UTC - GitHub Copilot - Completed T1. Added deterministic `Info::new` to `Configuration::load` regression tests using `figment::Jail` with cleared environment state. Verified complete-TOML, path, and default base-source selection; path-source override precedence; the missing-file `MissingMandatoryOption` result; and relative environment-path parent search. `cargo test --package torrust-tracker-configuration` passed (118 tests); `cargo fmt --check` and `git diff --check` passed. - 2026-09-07 12:10 UTC - GitHub Copilot - Completed T2. Added typed `PathBuf` source selection through `Info::new_with_explicit_config_toml_path`, retaining explicit source identity and eagerly captured TOML contents. Both v2 and v3 loaders use the captured contents for explicit files while retaining legacy file-provider behavior for environment/default sources. Added precedence, override, missing-file, directory, exact-relative-path, malformed-content redaction, non-UTF-8-path, and captured-content regression coverage. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. +- 2026-09-07 15:05 UTC - GitHub Copilot - Completed T3 and the first T4 vertical slice. The binary parses an optional `PathBuf` with clap and passes it through the application and bootstrap boundaries to the typed configuration API, with no environment mutation. Parser, root-library, and configuration tests passed (7, 82, and 127 tests respectively), along with `cargo check`, Rust formatting, Clippy, and diff checks. T5 review found no new child-process ownership, cleanup, or deadline concern and no ADR need. T4 remains in progress until direct tests cover every CLI-present precedence-table row. +- 2026-09-07 15:15 UTC - GitHub Copilot - Completed T4 by adding the remaining direct bootstrap precedence tests for CLI plus full-TOML environment source and CLI plus path environment source. Together with the earlier CLI-only and CLI-plus-both-sources tests, all four CLI-present table rows are covered. Root library (84 tests), parser binary (7 tests), and configuration library (127 tests) passed; Rust formatting, Clippy, and diff checks passed. ## Acceptance Criteria diff --git a/src/app.rs b/src/app.rs index 30acfd6ac..12aa9a76f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -22,6 +22,7 @@ //! - HTTP trackers: the user can enable multiple HTTP tracker on several ports. //! - Tracker REST API: the tracker API can be enabled/disabled. use std::future::Future; +use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; @@ -80,7 +81,24 @@ pub enum Error { /// /// Returns setup, persistence-load, or initial-service startup errors. pub async fn start() -> Result<(Arc, JobManager), Error> { - let (config, app_container) = bootstrap::app::setup().await.map_err(|source| Error::Setup { source })?; + start_with_explicit_config_toml_path(None).await +} + +/// Starts the tracker application with an optional explicitly selected TOML configuration file. +/// +/// An explicit path takes priority over environment-provided base sources. Per-value environment +/// overrides continue to apply. +/// +/// # Errors +/// +/// Returns setup, persistence-load, or initial-service startup errors. +// issue: #2151 +pub async fn start_with_explicit_config_toml_path( + explicit_config_toml_path: Option, +) -> Result<(Arc, JobManager), Error> { + let (config, app_container) = bootstrap::app::setup(explicit_config_toml_path) + .await + .map_err(|source| Error::Setup { source })?; let app_container = Arc::new(app_container); diff --git a/src/bootstrap/app.rs b/src/bootstrap/app.rs index 52c91b4b6..c1b7c3723 100644 --- a/src/bootstrap/app.rs +++ b/src/bootstrap/app.rs @@ -11,6 +11,8 @@ //! 2. Initialize static variables. //! 3. Initialize logging. //! 4. Initialize the domain tracker. +use std::path::PathBuf; + use torrust_tracker_configuration::v3_0_0::{Configuration, logging}; use torrust_tracker_configuration::validator::Validator; use torrust_tracker_udp_core::crypto::keys::{self, Keeper as _}; @@ -42,18 +44,19 @@ pub enum Error { Composition { source: crate::container::Error }, } -/// It loads the configuration from the environment and builds app container. +/// Loads the configuration and builds the application container. /// /// # Errors /// /// Returns a typed error when configuration, validation, or dependency composition fails. /// #[instrument(skip())] -pub async fn setup() -> Result<(Configuration, AppContainer), Error> { +// issue: #2151 +pub async fn setup(explicit_config_toml_path: Option) -> Result<(Configuration, AppContainer), Error> { #[cfg(not(test))] check_seed(); - let configuration = initialize_configuration().map_err(|source| Error::Configuration { source })?; + let configuration = initialize_configuration(explicit_config_toml_path).map_err(|source| Error::Configuration { source })?; configuration .validate() diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 00148842c..a21e9d348 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -2,6 +2,8 @@ //! //! All environment variables are prefixed with `TORRUST_TRACKER_`. +use std::path::PathBuf; + use torrust_tracker_configuration::Info; use torrust_tracker_configuration::v3_0_0::Configuration; @@ -35,13 +37,17 @@ pub const DEFAULT_PATH_CONFIG: &str = "./share/default/config/tracker.developmen /// /// Returns source-preserving errors if the configuration source cannot be /// prepared or parsed. -pub fn initialize_configuration() -> Result { - let info = Info::new(DEFAULT_PATH_CONFIG.to_string()).map_err(|source| Error::Source { source })?; +// issue: #2151 +pub fn initialize_configuration(explicit_config_toml_path: Option) -> Result { + let info = Info::new_with_explicit_config_toml_path(DEFAULT_PATH_CONFIG.to_string(), explicit_config_toml_path) + .map_err(|source| Error::Source { source })?; Configuration::load(&info).map_err(|source| Error::Load { source }) } #[cfg(test)] mod tests { + use std::fs; + use std::net::SocketAddr; use std::sync::{LazyLock, Mutex}; use torrust_tracker_configuration::Info; @@ -51,6 +57,29 @@ mod tests { static ENVIRONMENT_LOCK: LazyLock> = LazyLock::new(|| Mutex::new(())); + const MANDATORY_CONFIGURATION: &str = r#" + [metadata] + schema_version = "3.0.0" + + [logging] + trace_filter = "info" + + [core] + listed = false + private = false + "#; + + fn configuration_with_health_check_port(port: u16) -> String { + format!( + r#" + {MANDATORY_CONFIGURATION} + + [health_check_api] + bind_address = "127.0.0.1:{port}" + "# + ) + } + struct ConfigurationPathGuard { original_path: Option, original_toml: Option, @@ -72,6 +101,22 @@ mod tests { original_toml, } } + + #[allow(unsafe_code)] + fn set_complete_toml(toml: String) { + // SAFETY: `ENVIRONMENT_LOCK` serializes environment mutations in this test module. + unsafe { + std::env::set_var("TORRUST_TRACKER_CONFIG_TOML", toml); + } + } + + #[allow(unsafe_code)] + fn remove_complete_toml() { + // SAFETY: `ENVIRONMENT_LOCK` serializes environment mutations in this test module. + unsafe { + std::env::remove_var("TORRUST_TRACKER_CONFIG_TOML"); + } + } } impl Drop for ConfigurationPathGuard { @@ -99,7 +144,7 @@ mod tests { let _environment_lock = ENVIRONMENT_LOCK.lock().expect("lock environment access"); // Act and assert - initialize_configuration().expect("default configuration should load"); + initialize_configuration(None).expect("default configuration should load"); } #[test] @@ -113,12 +158,101 @@ mod tests { let _path_guard = ConfigurationPathGuard::replace(&missing_path); // Act - let result = initialize_configuration(); + let result = initialize_configuration(None); // Assert assert!(matches!(result, Err(Error::Load { .. }))); } + #[test] + fn it_should_load_an_explicit_path_without_mutating_environment_sources() { + // Arrange + let _environment_lock = ENVIRONMENT_LOCK.lock().expect("lock environment access"); + let explicit_directory = tempfile::tempdir().expect("create explicit configuration directory"); + let explicit_path = explicit_directory.path().join("explicit.toml"); + fs::write(&explicit_path, configuration_with_health_check_port(42151)).expect("write explicit configuration"); + let original_toml = std::env::var_os("TORRUST_TRACKER_CONFIG_TOML"); + let original_path = std::env::var_os(torrust_tracker_configuration::ENV_VAR_CONFIG_TOML_PATH); + + // Act + let configuration = initialize_configuration(Some(explicit_path)).expect("explicit configuration should load"); + + // Assert + assert_eq!( + configuration.health_check_api.bind_address, + "127.0.0.1:42151".parse::().unwrap() + ); + assert_eq!(std::env::var_os("TORRUST_TRACKER_CONFIG_TOML"), original_toml); + assert_eq!( + std::env::var_os(torrust_tracker_configuration::ENV_VAR_CONFIG_TOML_PATH), + original_path + ); + } + + #[test] + fn it_should_prefer_an_explicit_path_over_both_environment_base_sources() { + // Arrange + let _environment_lock = ENVIRONMENT_LOCK.lock().expect("lock environment access"); + let directory = tempfile::tempdir().expect("create configuration directory"); + let explicit_path = directory.path().join("explicit.toml"); + let environment_path = directory.path().join("environment.toml"); + fs::write(&explicit_path, configuration_with_health_check_port(42152)).expect("write explicit configuration"); + fs::write(&environment_path, configuration_with_health_check_port(42153)).expect("write environment path configuration"); + let _path_guard = ConfigurationPathGuard::replace(&environment_path); + ConfigurationPathGuard::set_complete_toml(configuration_with_health_check_port(42154)); + + // Act + let configuration = initialize_configuration(Some(explicit_path)).expect("explicit configuration should load"); + + // Assert + assert_eq!( + configuration.health_check_api.bind_address, + "127.0.0.1:42152".parse::().unwrap() + ); + } + + #[test] + fn it_should_prefer_an_explicit_path_over_the_complete_toml_environment_source() { + // Arrange + let _environment_lock = ENVIRONMENT_LOCK.lock().expect("lock environment access"); + let directory = tempfile::tempdir().expect("create configuration directory"); + let explicit_path = directory.path().join("explicit.toml"); + fs::write(&explicit_path, configuration_with_health_check_port(42155)).expect("write explicit configuration"); + let _path_guard = ConfigurationPathGuard::replace(&directory.path().join("environment.toml")); + ConfigurationPathGuard::set_complete_toml(configuration_with_health_check_port(42156)); + + // Act + let configuration = initialize_configuration(Some(explicit_path)).expect("explicit configuration should load"); + + // Assert + assert_eq!( + configuration.health_check_api.bind_address, + "127.0.0.1:42155".parse::().unwrap() + ); + } + + #[test] + fn it_should_prefer_an_explicit_path_over_the_path_environment_source() { + // Arrange + let _environment_lock = ENVIRONMENT_LOCK.lock().expect("lock environment access"); + let directory = tempfile::tempdir().expect("create configuration directory"); + let explicit_path = directory.path().join("explicit.toml"); + let environment_path = directory.path().join("environment.toml"); + fs::write(&explicit_path, configuration_with_health_check_port(42157)).expect("write explicit configuration"); + fs::write(&environment_path, configuration_with_health_check_port(42158)).expect("write environment path configuration"); + let _path_guard = ConfigurationPathGuard::replace(&environment_path); + ConfigurationPathGuard::remove_complete_toml(); + + // Act + let configuration = initialize_configuration(Some(explicit_path)).expect("explicit configuration should load"); + + // Assert + assert_eq!( + configuration.health_check_api.bind_address, + "127.0.0.1:42157".parse::().unwrap() + ); + } + #[test] fn it_should_load_every_shipped_configuration_template() { // Arrange diff --git a/src/main.rs b/src/main.rs index 24228fa05..2111f0e35 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,32 @@ +use std::path::PathBuf; use std::time::Duration; +use clap::Parser; use torrust_tracker_lib::app; +/// Command-line arguments accepted by the tracker executable. +#[derive(Debug, Parser)] +#[command(name = "torrust-tracker")] +struct Cli { + /// Path to the TOML configuration file to load. + // issue: #2151 + #[arg(short = 'c', long, value_parser = parse_non_empty_path)] + config_toml_path: Option, +} + +fn parse_non_empty_path(value: &str) -> Result { + if value.is_empty() { + return Err("configuration TOML path must not be empty".to_owned()); + } + + Ok(PathBuf::from(value)) +} + #[tokio::main] async fn main() { - match app::start().await { + let cli = Cli::parse(); + + match app::start_with_explicit_config_toml_path(cli.config_toml_path).await { Ok((_app_container, jobs)) => { let shutdown_signal = wait_for_shutdown_signal().await; @@ -92,3 +114,105 @@ async fn wait_for_shutdown_signal() -> &'static str { fn report_startup_failure(error: &app::Error) { eprintln!("Tracker startup failed: {error}"); } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use clap::error::ErrorKind; + use clap::{CommandFactory, Parser}; + + use super::Cli; + + #[test] + fn it_should_parse_a_short_config_toml_path_argument() { + // Arrange + let arguments = ["torrust-tracker", "-c", "tracker.toml"]; + + // Act + let cli = Cli::try_parse_from(arguments).expect("short option should parse"); + + // Assert + assert_eq!(cli.config_toml_path, Some(PathBuf::from("tracker.toml"))); + } + + #[test] + fn it_should_parse_a_long_config_toml_path_argument() { + // Arrange + let arguments = ["torrust-tracker", "--config-toml-path", "/etc/torrust/tracker.toml"]; + + // Act + let cli = Cli::try_parse_from(arguments).expect("long option should parse"); + + // Assert + assert_eq!(cli.config_toml_path, Some(PathBuf::from("/etc/torrust/tracker.toml"))); + } + + #[test] + fn it_should_return_a_usage_error_when_the_config_toml_path_value_is_missing() { + // Arrange + let arguments = ["torrust-tracker", "--config-toml-path"]; + + // Act + let error = Cli::try_parse_from(arguments).expect_err("missing option value should fail"); + + // Assert + assert_eq!(error.kind(), ErrorKind::InvalidValue); + assert_eq!(error.exit_code(), 2); + } + + #[test] + fn it_should_return_a_usage_error_when_the_config_toml_path_value_is_empty() { + // Arrange + let arguments = ["torrust-tracker", "--config-toml-path", ""]; + + // Act + let error = Cli::try_parse_from(arguments).expect_err("empty option value should fail"); + + // Assert + assert_eq!(error.kind(), ErrorKind::ValueValidation); + assert_eq!(error.exit_code(), 2); + } + + #[test] + fn it_should_return_a_usage_error_when_an_argument_is_unknown() { + // Arrange + let arguments = ["torrust-tracker", "--unknown"]; + + // Act + let error = Cli::try_parse_from(arguments).expect_err("unknown option should fail"); + + // Assert + assert_eq!(error.kind(), ErrorKind::UnknownArgument); + assert_eq!(error.exit_code(), 2); + } + + #[test] + fn it_should_render_help() { + // Arrange + let arguments = ["torrust-tracker", "--help"]; + + // Act + let error = Cli::try_parse_from(arguments).expect_err("help should stop parsing"); + + // Assert + assert_eq!(error.kind(), ErrorKind::DisplayHelp); + assert_eq!(error.exit_code(), 0); + assert!(error.to_string().contains("--config-toml-path ")); + } + + #[test] + fn it_should_not_bind_the_config_toml_path_argument_from_the_environment() { + // Arrange + + // Act + let command = Cli::command(); + let argument = command + .get_arguments() + .find(|argument| argument.get_id() == "config_toml_path") + .expect("config TOML path argument should exist"); + + // Assert + assert_eq!(argument.get_env(), None); + } +} From b5b597fb1596336f867c9969584452bcc303a071 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 16:18:56 +0100 Subject: [PATCH 04/44] test(configuration): preserve explicit source semantics --- .../ISSUE.md | 5 +- packages/configuration/src/lib.rs | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index af104540b..e2fa1a541 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 15:15 +last-updated-utc: 2026-09-07 15:20 semantic-links: skill-links: - create-issue @@ -307,7 +307,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | T3 | DONE | Define the CLI boundary | Added a main-owned `clap` parser for `-c` / `--config-toml-path` with no `env` binding. Parser tests cover short/long forms, missing/empty values, unknown arguments, help, exit codes, and absent environment binding. `cargo test --package torrust-tracker --bin torrust-tracker` passed (7 tests). | | T4 | DONE | Wire startup and precedence | Threaded `Option` from `main` through `app::start_with_explicit_config_toml_path`, `bootstrap::app::setup`, and `initialize_configuration` into T2 without environment mutation. Direct bootstrap tests cover every CLI-present table row: CLI only, CLI plus full TOML, CLI plus path, and CLI plus both sources. Root library tests passed (84 tests). | | T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | -| T6 | TODO | Preserve overrides and defaults | Verify a per-value override wins over a CLI-selected file, mandatory fields remain explicit, and optional fields still receive Rust defaults. | +| T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | | T7 | TODO | Add executable-boundary coverage | Extend the native fixture or add a focused fixture to launch two children with distinct CLI paths, isolated storage, and port-zero bindings. Call `env_remove` for both `TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` on each `Command`; children inherit the parent environment otherwise. Use progressive test increments and stop for maintainer review after the final increment. | | T8 | TODO | Update documentation | Update only affected user-facing documentation: `README.md`, root/configuration crate docs, `tests/AGENTS.md`, and relevant container, benchmarking, profiling, and `Containerfile` references. Retain valid environment examples and avoid duplicating procedures. | | T9 | TODO | Validate and record evidence | Run checks, execute manual scenarios, re-review every acceptance criterion against evidence, and complete the implementation completion review. | @@ -349,6 +349,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 12:10 UTC - GitHub Copilot - Completed T2. Added typed `PathBuf` source selection through `Info::new_with_explicit_config_toml_path`, retaining explicit source identity and eagerly captured TOML contents. Both v2 and v3 loaders use the captured contents for explicit files while retaining legacy file-provider behavior for environment/default sources. Added precedence, override, missing-file, directory, exact-relative-path, malformed-content redaction, non-UTF-8-path, and captured-content regression coverage. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. - 2026-09-07 15:05 UTC - GitHub Copilot - Completed T3 and the first T4 vertical slice. The binary parses an optional `PathBuf` with clap and passes it through the application and bootstrap boundaries to the typed configuration API, with no environment mutation. Parser, root-library, and configuration tests passed (7, 82, and 127 tests respectively), along with `cargo check`, Rust formatting, Clippy, and diff checks. T5 review found no new child-process ownership, cleanup, or deadline concern and no ADR need. T4 remains in progress until direct tests cover every CLI-present precedence-table row. - 2026-09-07 15:15 UTC - GitHub Copilot - Completed T4 by adding the remaining direct bootstrap precedence tests for CLI plus full-TOML environment source and CLI plus path environment source. Together with the earlier CLI-only and CLI-plus-both-sources tests, all four CLI-present table rows are covered. Root library (84 tests), parser binary (7 tests), and configuration library (127 tests) passed; Rust formatting, Clippy, and diff checks passed. +- 2026-09-07 15:20 UTC - GitHub Copilot - Completed T6. The existing explicit-file override test remains the regression for per-value override precedence. Added table-driven explicit-file coverage proving each mandatory option is still required before defaults are joined, plus a minimal explicit-file test proving optional values receive the unchanged Rust defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. ## Acceptance Criteria diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 9a2e8f8d8..a011011a2 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -536,6 +536,62 @@ mod tests { }); } + #[test] + #[allow(clippy::result_large_err)] + fn it_should_require_each_mandatory_option_when_loading_an_explicit_file() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + let mandatory_options = [ + ("metadata.schema_version", "schema_version = \"3.0.0\""), + ("logging.trace_filter", "trace_filter = \"info\""), + ("core.private", "private = false"), + ("core.listed", "listed = false"), + ]; + + for (mandatory_option, toml_entry) in mandatory_options { + let configuration_without_mandatory_option = MANDATORY_CONFIGURATION.replace(toml_entry, ""); + jail.create_file("explicit.toml", &configuration_without_mandatory_option)?; + + // Act + let result = load_configuration_with_explicit_path(PathBuf::from("explicit.toml")); + + // Assert + assert!(matches!( + result, + Err(Error::UnableToProcessExplicitConfigFile { + source, + .. + }) if source.to_string().contains(&format!("Option path: {mandatory_option}")) + )); + } + + Ok(()) + }); + } + + #[test] + #[allow(clippy::result_large_err)] + fn it_should_apply_existing_defaults_when_only_mandatory_options_are_provided_by_an_explicit_file() { + Jail::expect_with(|jail| { + // Arrange + jail.clear_env(); + jail.create_file("explicit.toml", MANDATORY_CONFIGURATION)?; + + // Act + let configuration = + load_configuration_with_explicit_path(PathBuf::from("explicit.toml")).expect("explicit source should load"); + + // Assert + assert_eq!( + toml::to_string(&configuration).expect("loaded configuration should serialize"), + toml::to_string(&Configuration::default()).expect("default configuration should serialize") + ); + + Ok(()) + }); + } + #[test] #[allow(clippy::result_large_err)] fn it_should_return_a_path_specific_error_when_an_explicit_file_is_missing() { From 480dd55321e5528555e878cf2a8dc71ac8fcb490 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 16:46:56 +0100 Subject: [PATCH 05/44] test(lifecycle): cover CLI configuration isolation --- .../ISSUE.md | 5 +- tests/lifecycle/native_tracker.rs | 110 ++++++++++++++---- tests/lifecycle/signals.rs | 72 ++++++++++++ 3 files changed, 163 insertions(+), 24 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index e2fa1a541..cb31f1bae 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 15:20 +last-updated-utc: 2026-09-07 15:40 semantic-links: skill-links: - create-issue @@ -308,7 +308,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | T4 | DONE | Wire startup and precedence | Threaded `Option` from `main` through `app::start_with_explicit_config_toml_path`, `bootstrap::app::setup`, and `initialize_configuration` into T2 without environment mutation. Direct bootstrap tests cover every CLI-present table row: CLI only, CLI plus full TOML, CLI plus path, and CLI plus both sources. Root library tests passed (84 tests). | | T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | | T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | -| T7 | TODO | Add executable-boundary coverage | Extend the native fixture or add a focused fixture to launch two children with distinct CLI paths, isolated storage, and port-zero bindings. Call `env_remove` for both `TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` on each `Command`; children inherit the parent environment otherwise. Use progressive test increments and stop for maintainer review after the final increment. | +| T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | | T8 | TODO | Update documentation | Update only affected user-facing documentation: `README.md`, root/configuration crate docs, `tests/AGENTS.md`, and relevant container, benchmarking, profiling, and `Containerfile` references. Retain valid environment examples and avoid duplicating procedures. | | T9 | TODO | Validate and record evidence | Run checks, execute manual scenarios, re-review every acceptance criterion against evidence, and complete the implementation completion review. | @@ -350,6 +350,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 15:05 UTC - GitHub Copilot - Completed T3 and the first T4 vertical slice. The binary parses an optional `PathBuf` with clap and passes it through the application and bootstrap boundaries to the typed configuration API, with no environment mutation. Parser, root-library, and configuration tests passed (7, 82, and 127 tests respectively), along with `cargo check`, Rust formatting, Clippy, and diff checks. T5 review found no new child-process ownership, cleanup, or deadline concern and no ADR need. T4 remains in progress until direct tests cover every CLI-present precedence-table row. - 2026-09-07 15:15 UTC - GitHub Copilot - Completed T4 by adding the remaining direct bootstrap precedence tests for CLI plus full-TOML environment source and CLI plus path environment source. Together with the earlier CLI-only and CLI-plus-both-sources tests, all four CLI-present table rows are covered. Root library (84 tests), parser binary (7 tests), and configuration library (127 tests) passed; Rust formatting, Clippy, and diff checks passed. - 2026-09-07 15:20 UTC - GitHub Copilot - Completed T6. The existing explicit-file override test remains the regression for per-value override precedence. Added table-driven explicit-file coverage proving each mandatory option is still required before defaults are joined, plus a minimal explicit-file test proving optional values receive the unchanged Rust defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. +- 2026-09-07 15:40 UTC - GitHub Copilot - Completed T7. Native executable fixtures now pass the isolated configuration through `--config-toml-path` and explicitly remove both inherited base-source environment variables. A concurrent child-process scenario starts two port-zero trackers, waits within each fixture deadline, asserts distinct PIDs, health-check addresses, CLI paths, and workspace-local storage paths, then sends SIGTERM and reaps both children. `cargo test --test lifecycle-signals` passed (8 tests); `cargo test --package torrust-tracker`, Rust formatting, Clippy, and diff checks passed. ## Acceptance Criteria diff --git a/tests/lifecycle/native_tracker.rs b/tests/lifecycle/native_tracker.rs index b0d34ed0e..7c2a295ed 100644 --- a/tests/lifecycle/native_tracker.rs +++ b/tests/lifecycle/native_tracker.rs @@ -1,8 +1,9 @@ //! Native child-process fixture for tracker executable lifecycle scenarios. //! -//! It owns one isolated tracker workspace, drains the child's output while the -//! tracker runs, discovers the health endpoint from its startup log, and reaps -//! the child even when graceful shutdown exceeds the scenario deadline. +//! It owns one isolated tracker workspace, supplies its configuration through +//! the executable's CLI, drains the child's output while the tracker runs, +//! discovers the health endpoint from its startup log, and reaps the child +//! even when graceful shutdown exceeds the scenario deadline. use std::net::SocketAddr; use std::os::unix::process::ExitStatusExt; @@ -63,22 +64,28 @@ pub struct NativeTracker { struct NativeTrackerWorkspace { _workspace: tempfile::TempDir, configuration_path: PathBuf, + storage_path: PathBuf, } impl NativeTrackerWorkspace { fn new() -> Self { let workspace = tempfile::tempdir().expect("create temporary tracker workspace"); - let configuration_path = write_configuration(&workspace); + let (configuration_path, storage_path) = write_configuration(&workspace); Self { _workspace: workspace, configuration_path, + storage_path, } } fn configuration_path(&self) -> &std::path::Path { &self.configuration_path } + + fn storage_path(&self) -> &std::path::Path { + &self.storage_path + } } /// Concurrently drains and retains a tracker child's output for readiness and diagnostics. @@ -163,21 +170,10 @@ enum HealthCheckProbeError { } impl NativeTracker { - /// Spawns the Cargo-built tracker binary with an isolated port-zero configuration. + /// Spawns the Cargo-built tracker binary with an isolated CLI configuration and port-zero bindings. pub fn start() -> Self { let workspace = NativeTrackerWorkspace::new(); - let mut command = Command::new(tracker_binary()); - command - // Configure only this child process. `Command::env` does not - // mutate the test process environment, so parallel fixtures each - // retain their own temporary configuration path. - .env("TORRUST_TRACKER_CONFIG_TOML_PATH", workspace.configuration_path()) - .env_remove("TORRUST_TRACKER_CONFIG_TOML") - // `shutdown` reaps normal and expected-error paths. This kills a - // panicking test's child so it cannot outlive its temporary workspace. - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + let mut command = tracker_command(workspace.configuration_path()); let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); let stdout = child.stdout.take().expect("tracker child stdout is piped"); @@ -218,6 +214,30 @@ impl NativeTracker { .ok_or_else(|| Self::failure_message_sync("tracker child exited before signal delivery")) } + /// Returns the health-check address discovered while waiting for readiness. + pub fn health_check_address(&self) -> Result { + self.health_check_client + .as_ref() + .map(|client| client.address) + .ok_or_else(|| Self::failure_message_sync("tracker health-check address is unavailable before readiness")) + } + + /// Returns the CLI-selected configuration path owned by this fixture. + pub fn configuration_path(&self) -> Result { + self.workspace + .as_ref() + .map(|workspace| workspace.configuration_path().to_path_buf()) + .ok_or_else(|| Self::failure_message_sync("tracker workspace is unavailable after shutdown")) + } + + /// Returns the isolated storage path owned by this fixture. + pub fn storage_path(&self) -> Result { + self.workspace + .as_ref() + .map(|workspace| workspace.storage_path().to_path_buf()) + .ok_or_else(|| Self::failure_message_sync("tracker workspace is unavailable after shutdown")) + } + /// Waits for a graceful exit, force-killing and reaping only after its deadline. pub async fn shutdown(mut self) -> Result { let mut child = self.child.take().expect("tracker child must be available before shutdown"); @@ -414,13 +434,32 @@ fn parse_health_check_address(line: &str) -> Option { address.parse().ok() } -fn write_configuration(workspace: &tempfile::TempDir) -> PathBuf { +fn write_configuration(workspace: &tempfile::TempDir) -> (PathBuf, PathBuf) { let storage_path = workspace.path().join("storage"); std::fs::create_dir_all(&storage_path).expect("create tracker storage directory"); let config_path = workspace.path().join("tracker.toml"); let config = CONFIGURATION.replace("{STORAGE_PATH}", &storage_path.to_string_lossy()); std::fs::write(&config_path, config).expect("write tracker configuration"); - config_path + (config_path, storage_path) +} + +/// Builds a child command whose base configuration is selected only by the CLI. +/// +/// The two legacy base-source variables are explicitly removed so inherited +/// environment state cannot override or obscure a fixture's CLI-selected file. +fn tracker_command(configuration_path: &std::path::Path) -> Command { + let mut command = Command::new(tracker_binary()); + command + .arg("--config-toml-path") + .arg(configuration_path) + .env_remove("TORRUST_TRACKER_CONFIG_TOML") + .env_remove("TORRUST_TRACKER_CONFIG_TOML_PATH") + // `shutdown` reaps normal and expected-error paths. This kills a + // panicking test's child so it cannot outlive its temporary workspace. + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + command } fn tracker_binary() -> PathBuf { @@ -431,16 +470,43 @@ fn tracker_binary() -> PathBuf { #[cfg(test)] mod tests { - use super::{parse_health_check_address, write_configuration}; + use std::ffi::OsStr; + use std::path::Path; + + use super::{parse_health_check_address, tracker_command, write_configuration}; + + #[test] + fn it_should_select_its_configuration_with_the_cli_and_remove_legacy_base_source_variables() { + // Arrange + let configuration_path = Path::new("/workspace/tracker.toml"); + + // Act + let command = tracker_command(configuration_path); + let arguments = command.as_std().get_args().collect::>(); + let environment = command.as_std().get_envs().collect::>(); + + // Assert + assert_eq!( + arguments, + vec![OsStr::new("--config-toml-path"), configuration_path.as_os_str()] + ); + for variable in ["TORRUST_TRACKER_CONFIG_TOML", "TORRUST_TRACKER_CONFIG_TOML_PATH"] { + assert!( + environment + .iter() + .any(|(name, value)| *name == OsStr::new(variable) && value.is_none()), + "command should remove inherited {variable}" + ); + } + } #[test] fn it_should_write_a_port_zero_configuration_with_workspace_local_sqlite_storage() { // Arrange let workspace = tempfile::tempdir().expect("create temporary tracker workspace"); - let storage_path = workspace.path().join("storage"); // Act - let config_path = write_configuration(&workspace); + let (config_path, storage_path) = write_configuration(&workspace); let configuration = std::fs::read_to_string(&config_path).expect("read tracker configuration"); // Assert diff --git a/tests/lifecycle/signals.rs b/tests/lifecycle/signals.rs index 68f795cc6..a5395fbc4 100644 --- a/tests/lifecycle/signals.rs +++ b/tests/lifecycle/signals.rs @@ -77,6 +77,78 @@ async fn it_should_distinguish_sigint_from_sigterm_when_shutting_down_the_tracke ); } +#[cfg(unix)] +#[tokio::test] +async fn it_should_run_two_tracker_binaries_with_independent_cli_configurations() { + // Arrange + let mut first_tracker = native_tracker::NativeTracker::start(); + let mut second_tracker = native_tracker::NativeTracker::start(); + + // Act + let (first_ready, second_ready) = tokio::join!(first_tracker.wait_until_ready(), second_tracker.wait_until_ready()); + first_ready.expect("first tracker should become ready before its fixture deadline"); + second_ready.expect("second tracker should become ready before its fixture deadline"); + let first_address = first_tracker + .health_check_address() + .expect("ready first tracker should expose its health-check address"); + let second_address = second_tracker + .health_check_address() + .expect("ready second tracker should expose its health-check address"); + let first_configuration_path = first_tracker + .configuration_path() + .expect("first tracker should retain its CLI configuration path"); + let second_configuration_path = second_tracker + .configuration_path() + .expect("second tracker should retain its CLI configuration path"); + let first_storage_path = first_tracker + .storage_path() + .expect("first tracker should retain its isolated storage path"); + let second_storage_path = second_tracker + .storage_path() + .expect("second tracker should retain its isolated storage path"); + let first_pid = first_tracker.pid().expect("ready first tracker should have a PID"); + let second_pid = second_tracker.pid().expect("ready second tracker should have a PID"); + + kill( + Pid::from_raw(i32::try_from(first_pid).expect("first child PID should fit i32")), + Signal::SIGTERM, + ) + .expect("deliver SIGTERM to the first tracker child"); + kill( + Pid::from_raw(i32::try_from(second_pid).expect("second child PID should fit i32")), + Signal::SIGTERM, + ) + .expect("deliver SIGTERM to the second tracker child"); + let (first_shutdown, second_shutdown) = tokio::join!(first_tracker.shutdown(), second_tracker.shutdown()); + + // Assert + assert_ne!( + first_address, second_address, + "port-zero health-check bindings should be independent" + ); + assert_ne!( + first_configuration_path, second_configuration_path, + "fixtures should own distinct CLI configuration paths" + ); + assert_ne!( + first_storage_path, second_storage_path, + "fixtures should own distinct workspace-local storage paths" + ); + assert_ne!(first_pid, second_pid, "fixtures should own distinct tracker children"); + assert!( + first_shutdown + .expect("first tracker should gracefully exit after SIGTERM") + .contains("Torrust tracker successfully shutdown."), + "first tracker should report graceful shutdown" + ); + assert!( + second_shutdown + .expect("second tracker should gracefully exit after SIGTERM") + .contains("Torrust tracker successfully shutdown."), + "second tracker should report graceful shutdown" + ); +} + #[cfg(unix)] #[tokio::test] async fn it_should_force_kill_and_reap_the_tracker_binary_when_the_fixture_is_dropped() { From e8a8624d425ad87e32b653e178dc7c1453ad454a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 16:58:24 +0100 Subject: [PATCH 06/44] docs(configuration): document explicit CLI path --- .../run-tracker-locally/SKILL.md | 17 ++++++++- README.md | 15 ++++++-- docs/benchmarking.md | 4 +-- docs/containers.md | 20 +++++++++-- .../ISSUE.md | 5 +-- docs/profiling.md | 3 ++ packages/configuration/src/lib.rs | 8 +++-- packages/configuration/src/v2_0_0/mod.rs | 28 +++++++-------- packages/configuration/src/v3_0_0/mod.rs | 26 +++++++------- src/AGENTS.md | 14 ++++---- src/lib.rs | 20 +++++++++-- tests/AGENTS.md | 36 +++++++++---------- 12 files changed, 127 insertions(+), 69 deletions(-) diff --git a/.github/skills/dev/environment-setup/run-tracker-locally/SKILL.md b/.github/skills/dev/environment-setup/run-tracker-locally/SKILL.md index 7a5767b83..8dfa3fca5 100644 --- a/.github/skills/dev/environment-setup/run-tracker-locally/SKILL.md +++ b/.github/skills/dev/environment-setup/run-tracker-locally/SKILL.md @@ -51,6 +51,21 @@ Loading extra configuration from default configuration file: `./share/default/co **Default database**: SQLite3 **Default configuration file**: `./share/default/config/tracker.development.sqlite3.toml` +## Selecting a Configuration Source + +The main `torrust-tracker` binary accepts an explicit file path: + +```bash +cargo run --bin torrust-tracker -- --config-toml-path ./storage/tracker/etc/tracker.toml +``` + +Base-source precedence is `--config-toml-path` > +`TORRUST_TRACKER_CONFIG_TOML` > `TORRUST_TRACKER_CONFIG_TOML_PATH` > the default +development file. `TORRUST_TRACKER_CONFIG_OVERRIDE_*` values are merged over the +selected base source. A CLI path is resolved exactly from the current working +directory; it must name a readable TOML file. Environment sources remain +supported for compatibility and deployment use. + ## Default Services By default, the development configuration starts: @@ -146,7 +161,7 @@ openssl req -x509 -out .tmp/localhost.crt -keyout .tmp/localhost.key \ 1. Start the tracker with the temporary configuration: ```bash -TORRUST_TRACKER_CONFIG_TOML_PATH="$PWD/.tmp/local-tls.toml" cargo run --bin torrust-tracker +cargo run --bin torrust-tracker -- --config-toml-path "$PWD/.tmp/local-tls.toml" ``` Read the startup log to obtain the final port assigned to a `:0` binding. diff --git a/README.md b/README.md index 40ba402ea..1ee5c55ba 100644 --- a/README.md +++ b/README.md @@ -145,17 +145,26 @@ cp ./share/default/config/tracker.development.sqlite3.toml ./storage/tracker/etc # Customize the tracker configuration (for example): vim ./storage/tracker/etc/tracker.toml -# Run the tracker with the updated configuration: -TORRUST_TRACKER_CONFIG_TOML_PATH="./storage/tracker/etc/tracker.toml" cargo run +# Run the main tracker binary with the updated configuration: +cargo run --bin torrust-tracker -- --config-toml-path ./storage/tracker/etc/tracker.toml ``` -_Optionally, you may choose to supply the entire configuration as an environmental variable:_ +_Alternatively, you may choose to select the configuration file or supply its +complete contents with environment variables:_ ```sh # Use a configuration supplied on an environmental variable: TORRUST_TRACKER_CONFIG_TOML=$(cat "./storage/tracker/etc/tracker.toml") cargo run ``` +Base-source precedence is `--config-toml-path` > +`TORRUST_TRACKER_CONFIG_TOML` > `TORRUST_TRACKER_CONFIG_TOML_PATH` > the default +development file. `TORRUST_TRACKER_CONFIG_OVERRIDE_*` values override matching +values in the selected base source. The main binary resolves a CLI path exactly +from its current working directory; it rejects missing, unreadable, non-file, +or invalid TOML sources at startup. The environment path remains supported with +its legacy resolution behavior. + _For deployment, you **should** override the `api_admin_token` by using an environmental variable:_ ```sh diff --git a/docs/benchmarking.md b/docs/benchmarking.md index 037ff971e..815e983bf 100644 --- a/docs/benchmarking.md +++ b/docs/benchmarking.md @@ -57,8 +57,8 @@ bind_address = "0.0.0.0:3000" Start the tracker: ```console -TORRUST_TRACKER_CONFIG_TOML_PATH="./share/default/config/tracker.udp.benchmarking.toml" \ - ./target/release/torrust-tracker +./target/release/torrust-tracker \ + --config-toml-path ./share/default/config/tracker.udp.benchmarking.toml ``` ### 3. Build the aquatic UDP load test diff --git a/docs/containers.md b/docs/containers.md index 6679c7e5e..ac1b5cc5f 100644 --- a/docs/containers.md +++ b/docs/containers.md @@ -147,9 +147,25 @@ podman run -it docker.io/torrust-tracker:debug ### Arguments -The arguments need to be placed before the image tag. i.e. +Docker or Podman runtime arguments are placed before the image tag. Tracker +command-line arguments are placed after it. -`run [arguments] torrust-tracker:release` +`run [runtime arguments] torrust-tracker:release [tracker arguments]` + +#### Tracker Command Options + +The main tracker binary accepts `-c` / `--config-toml-path `. The selected +path is evaluated inside the container, so use an in-container mounted path: + +```sh +docker run -it torrust/tracker:latest \ + --config-toml-path /etc/torrust/tracker/tracker.toml +``` + +This option overrides the image's `TORRUST_TRACKER_CONFIG_TOML_PATH` and any +supplied `TORRUST_TRACKER_CONFIG_TOML` base source. Per-value +`TORRUST_TRACKER_CONFIG_OVERRIDE_*` variables still override matching values in +the CLI-selected file. #### Environmental Variables diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index cb31f1bae..c1fb9b502 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 15:40 +last-updated-utc: 2026-09-07 15:55 semantic-links: skill-links: - create-issue @@ -309,7 +309,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | | T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | | T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | -| T8 | TODO | Update documentation | Update only affected user-facing documentation: `README.md`, root/configuration crate docs, `tests/AGENTS.md`, and relevant container, benchmarking, profiling, and `Containerfile` references. Retain valid environment examples and avoid duplicating procedures. | +| T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | | T9 | TODO | Validate and record evidence | Run checks, execute manual scenarios, re-review every acceptance criterion against evidence, and complete the implementation completion review. | Each task must be independently buildable and tested. T1 is a @@ -351,6 +351,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 15:15 UTC - GitHub Copilot - Completed T4 by adding the remaining direct bootstrap precedence tests for CLI plus full-TOML environment source and CLI plus path environment source. Together with the earlier CLI-only and CLI-plus-both-sources tests, all four CLI-present table rows are covered. Root library (84 tests), parser binary (7 tests), and configuration library (127 tests) passed; Rust formatting, Clippy, and diff checks passed. - 2026-09-07 15:20 UTC - GitHub Copilot - Completed T6. The existing explicit-file override test remains the regression for per-value override precedence. Added table-driven explicit-file coverage proving each mandatory option is still required before defaults are joined, plus a minimal explicit-file test proving optional values receive the unchanged Rust defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. - 2026-09-07 15:40 UTC - GitHub Copilot - Completed T7. Native executable fixtures now pass the isolated configuration through `--config-toml-path` and explicitly remove both inherited base-source environment variables. A concurrent child-process scenario starts two port-zero trackers, waits within each fixture deadline, asserts distinct PIDs, health-check addresses, CLI paths, and workspace-local storage paths, then sends SIGTERM and reaps both children. `cargo test --test lifecycle-signals` passed (8 tests); `cargo test --package torrust-tracker`, Rust formatting, Clippy, and diff checks passed. +- 2026-09-07 15:55 UTC - GitHub Copilot - Completed T8. Updated the README, container and benchmarking commands, profiling clarification, root/configuration API docs, source and test guidance, and `run-tracker-locally` skill. The CLI path is documented as the main-binary primary source; environment examples retain their compatible behavior. `validate-skill-links.sh`, `linter markdown`, `linter cspell`, and `git diff --check` passed. ## Acceptance Criteria diff --git a/docs/profiling.md b/docs/profiling.md index 247c5ad12..361fc4b51 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -44,6 +44,9 @@ To generate the graph you will need to: 2. Run the aquatic UDP load test. 3. Run the tracker with flamegraph and profiling configuration. +The `profiling` binary does not parse the main `torrust-tracker` command-line +options; configure it with the existing environment variables. + ```console cargo build --profile=release-debug --bin=profiling ./target/release/aquatic_udp_load_test -c "load-test-config.toml" diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index a011011a2..6ccd0f996 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -28,11 +28,13 @@ use tracing::info; // Environment variables -/// The whole `tracker.toml` file content. It has priority over the config file. -/// Even if the file is not on the default path. +/// Complete TOML base source from the environment. +/// +/// It has priority over [`ENV_VAR_CONFIG_TOML_PATH`] when no explicit file path +/// is supplied by the caller. const ENV_VAR_CONFIG_TOML: &str = "TORRUST_TRACKER_CONFIG_TOML"; -/// The `tracker.toml` file location. +/// Legacy environment-selected TOML file location. pub const ENV_VAR_CONFIG_TOML_PATH: &str = "TORRUST_TRACKER_CONFIG_TOML_PATH"; /// Named configuration API tokens, protected from accidental diagnostic exposure. diff --git a/packages/configuration/src/v2_0_0/mod.rs b/packages/configuration/src/v2_0_0/mod.rs index 5b68068bd..267d82965 100644 --- a/packages/configuration/src/v2_0_0/mod.rs +++ b/packages/configuration/src/v2_0_0/mod.rs @@ -4,19 +4,17 @@ //! This module contains the configuration data structures for the //! Torrust Tracker, which is a `BitTorrent` tracker server. //! -//! The configuration is loaded from a [TOML](https://toml.io/en/) file -//! `tracker.toml` in the project root folder or from an environment variable -//! with the same content as the file. -//! -//! Configuration can not only be loaded from a file, but also from an -//! environment variable `TORRUST_TRACKER_CONFIG_TOML`. This is useful when running -//! the tracker in a Docker container or environments where you do not have a -//! persistent storage or you cannot inject a configuration file. Refer to -//! [`Torrust Tracker documentation`](https://docs.rs/torrust-tracker) for more -//! information about how to pass configuration to the tracker. -//! -//! When you run the tracker without providing the configuration via a file or -//! env var, the default configuration is used. +//! The configuration crate loads an explicit TOML file supplied by its caller, +//! complete TOML from `TORRUST_TRACKER_CONFIG_TOML`, a file selected by +//! `TORRUST_TRACKER_CONFIG_TOML_PATH`, or a caller-selected default file. It +//! does not parse executable arguments; the main `torrust-tracker` binary +//! supplies the explicit path for `-c` / `--config-toml-path`. +//! +//! Base-source precedence is explicit path, complete TOML environment value, +//! environment-selected path, then caller default. Per-value +//! `TORRUST_TRACKER_CONFIG_OVERRIDE_*` variables are merged over the selected +//! base source. Explicit paths are exact and must name readable files; +//! environment-selected paths retain legacy resolution behavior. //! //! # Table of contents //! @@ -330,7 +328,9 @@ impl Configuration { /// configuration in toml format is included in the `info.tracker_toml` /// string. /// - /// Configuration provided via env var has priority over config file path. + /// Base-source precedence is explicit path, complete TOML environment value, + /// environment-selected path, then caller default. Per-value environment + /// overrides are merged over the selected base source. /// /// # Errors /// diff --git a/packages/configuration/src/v3_0_0/mod.rs b/packages/configuration/src/v3_0_0/mod.rs index 77217c0b2..d58ba266a 100644 --- a/packages/configuration/src/v3_0_0/mod.rs +++ b/packages/configuration/src/v3_0_0/mod.rs @@ -4,19 +4,17 @@ //! This module contains the configuration data structures for the //! Torrust Tracker, which is a `BitTorrent` tracker server. //! -//! The configuration is loaded from a [TOML](https://toml.io/en/) file -//! `tracker.toml` in the project root folder or from an environment variable -//! with the same content as the file. +//! The configuration crate loads an explicit TOML file supplied by its caller, +//! complete TOML from `TORRUST_TRACKER_CONFIG_TOML`, a file selected by +//! `TORRUST_TRACKER_CONFIG_TOML_PATH`, or a caller-selected default file. It +//! does not parse executable arguments; the main `torrust-tracker` binary +//! supplies the explicit path for `-c` / `--config-toml-path`. //! -//! Configuration can not only be loaded from a file, but also from an -//! environment variable `TORRUST_TRACKER_CONFIG_TOML`. This is useful when running -//! the tracker in a Docker container or environments where you do not have a -//! persistent storage or you cannot inject a configuration file. Refer to -//! [`Torrust Tracker documentation`](https://docs.rs/torrust-tracker) for more -//! information about how to pass configuration to the tracker. -//! -//! When you run the tracker without providing the configuration via a file or -//! env var, the default configuration is used. +//! Base-source precedence is explicit path, complete TOML environment value, +//! environment-selected path, then caller default. Per-value +//! `TORRUST_TRACKER_CONFIG_OVERRIDE_*` variables are merged over the selected +//! base source. Explicit paths are exact and must name readable files; +//! environment-selected paths retain legacy resolution behavior. //! //! # Table of contents //! @@ -361,7 +359,9 @@ impl Configuration { /// configuration in toml format is included in the `info.tracker_toml` /// string. /// - /// Configuration provided via env var has priority over config file path. + /// Base-source precedence is explicit path, complete TOML environment value, + /// environment-selected path, then caller default. Per-value environment + /// overrides are merged over the selected base source. /// /// # Errors /// diff --git a/src/AGENTS.md b/src/AGENTS.md index 9bf1b43da..3caeab6ac 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -8,12 +8,12 @@ the bootstrap sequence, and the dependency-injection container. All domain logic | Path | Purpose | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `main.rs` | Binary entry point. Calls `app::start()`, waits for Ctrl-C, then cancels jobs and waits for graceful shutdown. | +| `main.rs` | Main binary entry point. Parses `-c` / `--config-toml-path`, starts the app, then waits for shutdown and cancels jobs. | | `lib.rs` | Library crate root and crate-level documentation. Re-exports the public API used by integration tests and other binaries. | -| `app.rs` | `start()` and `complete_startup()` — orchestrate the full startup sequence (setup → load data from DB → start jobs). | +| `app.rs` | Environment-compatible `start()`, parameterized startup, and `complete_startup()` — orchestrate the full startup sequence. | | `container.rs` | `AppContainer` — dependency-injection struct that holds `Arc`-wrapped instances of every per-layer container. | -| `bootstrap/app.rs` | `setup()` — loads config, validates it, initializes logging and global services, builds `AppContainer`. | -| `bootstrap/config.rs` | `initialize_configuration()` — reads config from the environment / file. | +| `bootstrap/app.rs` | `setup()` — receives optional explicit path, loads config, validates it, initializes services, builds `AppContainer`. | +| `bootstrap/config.rs` | `initialize_configuration()` — loads config from an optional explicit path or existing environment/default sources. | | `bootstrap/jobs/` | One module per service: each module exposes a starter function called from `app::start_jobs`. | | `bootstrap/jobs/manager.rs` | `JobManager` — directly owns named component futures in a `JoinSet`, retains legacy periodic handles through a compatibility registry, owns the `CancellationToken`, and drives graceful shutdown. | | `bin/e2e_tests_runner.rs` | Binary that runs E2E tests by delegating to `src/console/ci/`. | @@ -25,9 +25,9 @@ the bootstrap sequence, and the dependency-injection container. All domain logic ```text main() - └─ app::start() - ├─ bootstrap::app::setup() - │ ├─ bootstrap::config::initialize_configuration() ← reads TOML / env vars + └─ app::start_with_explicit_config_toml_path() + ├─ bootstrap::app::setup(explicit_config_toml_path) + │ ├─ bootstrap::config::initialize_configuration() ← explicit path / TOML / env vars │ ├─ configuration.validate() ← returns typed startup errors │ ├─ initialize_global_services() ← logging, crypto seed │ └─ AppContainer::initialize(&configuration) ← builds all containers diff --git a/src/lib.rs b/src/lib.rs index 7190a8302..44d662f86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,8 +207,21 @@ //! For more information about each service and options you can visit the //! documentation for the [torrust-tracker-configuration crate](https://docs.rs/torrust-tracker-configuration). //! -//! Alternatively to the `tracker.toml` file you can use one environment -//! variable `TORRUST_TRACKER_CONFIG_TOML` to pass the configuration to the tracker: +//! The main `torrust-tracker` binary can select a configuration file explicitly: +//! +//! ```text +//! ./target/release/torrust-tracker --config-toml-path ./storage/tracker/etc/tracker.toml +//! ``` +//! +//! Base-source precedence is the explicit path, +//! `TORRUST_TRACKER_CONFIG_TOML`, `TORRUST_TRACKER_CONFIG_TOML_PATH`, then the +//! default development file. Per-value `TORRUST_TRACKER_CONFIG_OVERRIDE_*` +//! variables override matching values in the selected base source. The CLI path +//! is resolved exactly from the current working directory and must name a +//! readable TOML file. Environment sources remain supported for compatibility. +//! +//! Alternatively, use `TORRUST_TRACKER_CONFIG_TOML` to pass complete +//! configuration content to the tracker: //! //! ```text //! TORRUST_TRACKER_CONFIG_TOML=$(cat ./share/default/config/tracker.development.sqlite3.toml) ./target/release/torrust-tracker @@ -220,7 +233,8 @@ //! The env var contains the same data as the `tracker.toml`. It's particularly //! useful in you are [running the tracker with docker](https://github.com/torrust/torrust-tracker/blob/develop/docs/containers.md). //! -//! > NOTICE: The `TORRUST_TRACKER_CONFIG_TOML` env var has priority over the `tracker.toml` file. +//! > NOTICE: Without `--config-toml-path`, `TORRUST_TRACKER_CONFIG_TOML` has +//! > priority over `TORRUST_TRACKER_CONFIG_TOML_PATH`. //! //! skill-link: run-tracker-locally //! By default, if you don’t specify any `tracker.toml` file, the application diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 733b5b308..94e5ad5b6 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -83,7 +83,7 @@ async functions that receive the `AppContainer` and assert behavior. Cargo may run these binaries in parallel. Each binary binds to port `0` (OS-assigned ephemeral ports) by default, uses its own `TempDir` workspace, -and sets `TORRUST_TRACKER_CONFIG_TOML_PATH` only in its own process, so no +and configures its in-process tracker through its own environment, so no conflict occurs. Fixed-port binaries (e.g., `metrics-fixed-ports`) use distinct non-overlapping ports and must not run concurrently with other binaries that use the same ports. @@ -91,20 +91,18 @@ binaries that use the same ports. ### Child-Process Configuration Isolation Executable-boundary tests may start the tracker as a child process instead of -calling `app::start()` in their integration-test executable. Set -`TORRUST_TRACKER_CONFIG_TOML_PATH` on that specific `Command`, not in the test -process environment. A child receives its own environment snapshot when it is -spawned, so concurrent test binaries and concurrent child processes cannot -overwrite each other's configured path. Each child must still use a separate -`TempDir` workspace and port-zero listener configuration. - -The tracker currently receives its configuration-file path through environment -configuration; it does not provide a tracker-binary configuration-path command -line argument. A future explicit argument may be preferable because it makes -the child configuration visible in the invocation. If introduced, it should -take precedence over `TORRUST_TRACKER_CONFIG_TOML_PATH`, be documented as the -canonical executable-boundary test mechanism, and retain the environment -variable for compatibility until a separately approved migration removes it. +calling `app::start()` in their integration-test executable. Pass +`--config-toml-path ` to the child and remove both +`TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` from that +`Command`. This makes the selected source visible in the invocation and prevents +inherited base-source environment state from affecting the child. The CLI path +outranks both environment base sources; per-value +`TORRUST_TRACKER_CONFIG_OVERRIDE_*` variables still apply. Each child must use a +separate `TempDir` workspace and port-zero listener configuration. + +In-process application fixtures remain environment-based because they call the +compatibility wrapper `app::start()`. Do not mutate configuration variables +without the fixture's synchronization guard. ### Why one binary per configuration? @@ -117,10 +115,10 @@ in the same process: global subscriber. Once set, it cannot be reset for a second tracker instance in the same process. This means tracker applications sharing a process would share logging state and configuration. -2. **Environment-variable configuration injection**: The tracker reads its - configuration from the `TORRUST_TRACKER_CONFIG_TOML_PATH` environment - variable. Multiple tracker instances in the same process would race on - this variable. +2. **Environment-variable configuration injection in in-process fixtures**: + `app::start()` reads configuration from the environment. Multiple tracker + instances in the same process would race on those variables. Native child + fixtures instead use the main binary's explicit CLI path. 3. **Static secrets and clock state**: Values such as seed secrets and the deterministic test clock are process-global. While these could be refactored into injected dependencies, they remain lifecycle constraints today. From 13353d915499ee07629949394ce22aadb99aecc7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 17:58:25 +0100 Subject: [PATCH 07/44] test(tracker): verify CLI configuration failures --- .../ISSUE.md | 80 ++++++++++--------- src/bootstrap/config.rs | 19 ++++- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index c1fb9b502..6e9a14669 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 15:55 +last-updated-utc: 2026-09-07 16:30 semantic-links: skill-links: - create-issue @@ -310,7 +310,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | | T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | | T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | -| T9 | TODO | Validate and record evidence | Run checks, execute manual scenarios, re-review every acceptance criterion against evidence, and complete the implementation completion review. | +| T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Manual M1-M5 release-binary scenarios passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks. Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | Each task must be independently buildable and tested. T1 is a behavior-preserving safety-net change; T2 is a configuration refactor; T3-T4 @@ -324,13 +324,13 @@ are the deployable feature; later tasks extend verification and documentation. - [x] Spec reviewed and approved by user/maintainer - [x] GitHub issue [#2151](https://github.com/torrust/torrust-tracker/issues/2151) created and issue number added to this spec - [x] Spec-only PR [#2153](https://github.com/torrust/torrust-tracker/pull/2153) merged into `develop` before implementation -- [ ] First passing CLI-only vertical slice reviewed for ownership, cleanup, deadline, and ADR decisions -- [ ] Implementation completed -- [ ] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) -- [ ] Manual verification scenarios executed and recorded (status + evidence) -- [ ] Acceptance criteria reviewed after implementation and updated with evidence -- [ ] Evidence-based implementation completion review recorded: issue-local retrospective created for material discoveries, or progress log states why none was needed -- [ ] Reviewer validated acceptance criteria and updated checkboxes +- [x] First passing CLI-only vertical slice reviewed for ownership, cleanup, deadline, and ADR decisions +- [x] Implementation completed +- [x] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) +- [x] Manual verification scenarios executed and recorded (status + evidence) +- [x] Acceptance criteria reviewed after implementation and updated with evidence +- [x] Evidence-based implementation completion review recorded: progress log states why no retrospective was needed +- [x] Reviewer validated acceptance criteria and updated checkboxes - [ ] Committer verified spec progress is up to date before commit - [ ] Issue closed and spec moved from `docs/issues/open/` to `docs/issues/closed/` @@ -352,33 +352,35 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 15:20 UTC - GitHub Copilot - Completed T6. The existing explicit-file override test remains the regression for per-value override precedence. Added table-driven explicit-file coverage proving each mandatory option is still required before defaults are joined, plus a minimal explicit-file test proving optional values receive the unchanged Rust defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. - 2026-09-07 15:40 UTC - GitHub Copilot - Completed T7. Native executable fixtures now pass the isolated configuration through `--config-toml-path` and explicitly remove both inherited base-source environment variables. A concurrent child-process scenario starts two port-zero trackers, waits within each fixture deadline, asserts distinct PIDs, health-check addresses, CLI paths, and workspace-local storage paths, then sends SIGTERM and reaps both children. `cargo test --test lifecycle-signals` passed (8 tests); `cargo test --package torrust-tracker`, Rust formatting, Clippy, and diff checks passed. - 2026-09-07 15:55 UTC - GitHub Copilot - Completed T8. Updated the README, container and benchmarking commands, profiling clarification, root/configuration API docs, source and test guidance, and `run-tracker-locally` skill. The CLI path is documented as the main-binary primary source; environment examples retain their compatible behavior. `validate-skill-links.sh`, `linter markdown`, `linter cspell`, and `git diff --check` passed. +- 2026-09-07 16:30 UTC - GitHub Copilot - Completed T9 verification. The pre-commit gate passed; configuration tests passed (129), tracker tests passed, and lifecycle-signals passed (8). The release binary was built and manual scenarios M1-M5 passed with disposable logs under `.tmp/issue-2151-manual/`. The first verifier run used a 10-second shutdown wait and force-killed an otherwise healthy M1 child; it was corrected to use the fixture-aligned 30-second deadline, then all scenarios passed. No implementation deviation or reusable design discovery warrants a separate retrospective. +- 2026-09-07 16:50 UTC - Task Reviewer / GitHub Copilot - Independent acceptance review initially found M4 had no unreadable regular-file scenario, M1 incorrectly named a debug binary, T9 remained TODO, and CLI-source remediation text named only environment sources. Added a mode-`000` unreadable regular-file scenario and retained its command, file mode, exit status, and `Permission denied` result in `.tmp/issue-2151-manual/summary.txt`; added no-listener bind probes for malformed and parent-only relative sources; corrected M1 evidence; marked T9 done; and updated the guidance with a regression test. The release-binary manual suite and focused tracker tests passed after correction; all acceptance criteria now pass. ## Acceptance Criteria -- [ ] AC1: `torrust-tracker` accepts `-c` and `--config-toml-path `. -- [ ] AC2: Every row of the base-source selection table in "Proposed Source +- [x] AC1: `torrust-tracker` accepts `-c` and `--config-toml-path `. +- [x] AC2: Every row of the base-source selection table in "Proposed Source Precedence" is covered by a test and behaves as specified; in particular a CLI path selects its file even when both `TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` are set, and the ignored base sources are not merged. -- [ ] AC3: `TORRUST_TRACKER_CONFIG_OVERRIDE_*` values still override matching +- [x] AC3: `TORRUST_TRACKER_CONFIG_OVERRIDE_*` values still override matching values in a CLI-selected file; without the option, the four unchanged rows (env TOML content beats env path beats default) behave exactly as before. -- [ ] AC4: Required values remain mandatory before Rust defaults are applied; +- [x] AC4: Required values remain mandatory before Rust defaults are applied; optional defaults remain unchanged. -- [ ] AC5: An absent or supplied empty CLI value is a descriptive usage error +- [x] AC5: An absent or supplied empty CLI value is a descriptive usage error with exit code `2` and no listener. A missing, unreadable, non-file, or TOML-invalid CLI path produces an error naming the offending path with exit code `1` and no listener. A relative CLI path is never resolved through parent-directory search. -- [ ] AC6: Two tracker child processes can run concurrently with distinct CLI +- [x] AC6: Two tracker child processes can run concurrently with distinct CLI paths, isolated storage, and port-zero bindings without configuration-source environment variables. -- [ ] `linter all` exits with code `0` and relevant tests pass. -- [ ] Manual verification scenarios are executed and documented (status + evidence). -- [ ] Acceptance criteria are re-reviewed after implementation and reflect actual behavior. -- [ ] Documentation states final interfaces and precedence without contradicting implementation. +- [x] `linter all` exits with code `0` and relevant tests pass. +- [x] Manual verification scenarios are executed and documented (status + evidence). +- [x] Acceptance criteria are re-reviewed after implementation and reflect actual behavior. +- [x] Documentation states final interfaces and precedence without contradicting implementation. ## Verification Plan @@ -398,28 +400,28 @@ are the deployable feature; later tasks extend verification and documentation. Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. -| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | -| --- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | --------------- | -| M1 | CLI path only | Start the release binary with `--config-toml-path` pointing to an isolated valid file and no configuration-source variables. | The tracker reads that file, starts configured services, and exits cleanly on SIGTERM. | TODO | To be recorded. | -| M2 | CLI source precedence | Prepare three valid configurations that differ only in `health_check_api.bind_address` (three distinct fixed loopback ports). Supply one via `TORRUST_TRACKER_CONFIG_TOML`, one via `TORRUST_TRACKER_CONFIG_TOML_PATH`, and the third via `--config-toml-path`. | The `HEALTH CHECK API: Started on:` log line reports the port from the CLI-selected file. | TODO | To be recorded. | -| M3 | Per-value override | Start with `--config-toml-path` and a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_*` value. | The override wins for its path while other values come from the file. | TODO | To be recorded. | -| M4 | Invalid CLI source | Start with (a) `--config-toml-path` with no value, (b) an empty supplied path, (c) a nonexistent absolute file, (d) a directory or unreadable file, (e) malformed TOML, and (f) a relative filename that exists only in a parent directory of the CWD. | Cases (a-b) exit `2` with a descriptive usage error. Cases (c-f) exit `1` with an error naming the path; case (f) must not load the parent-directory file. No case creates a listener. | TODO | To be recorded. | -| M5 | Parallel child isolation (Unix) | Launch two binaries concurrently with different CLI paths, isolated storage, and port-zero configuration. | Both start with their own configuration; neither reads or overwrites the other's source. | TODO | To be recorded. | +| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | +| --- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| M1 | CLI path only | Start the release binary with `--config-toml-path` pointing to an isolated valid file and no configuration-source variables. | The tracker reads that file, starts configured services, and exits cleanly on SIGTERM. | DONE | `.tmp/issue-2151-manual/m1.log`; health endpoint `127.0.0.1:43151`, exit `0`. | +| M2 | CLI source precedence | Prepare three valid configurations that differ only in `health_check_api.bind_address` (three distinct fixed loopback ports). Supply one via `TORRUST_TRACKER_CONFIG_TOML`, one via `TORRUST_TRACKER_CONFIG_TOML_PATH`, and the third via `--config-toml-path`. | The `HEALTH CHECK API: Started on:` log line reports the port from the CLI-selected file. | DONE | `.tmp/issue-2151-manual/m2.log`; CLI port `43152` selected over env ports `43153` and `43154`, exit `0`. | +| M3 | Per-value override | Start with `--config-toml-path` and a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_*` value. | The override wins for its path while other values come from the file. | DONE | `.tmp/issue-2151-manual/m3.log`; override port `43156` selected over CLI file port `43155`, exit `0`. | +| M4 | Invalid CLI source | Start with (a) `--config-toml-path` with no value, (b) an empty supplied path, (c) a nonexistent absolute file, (d) a directory, (e) an unreadable regular file, (f) malformed TOML, and (g) a relative filename that exists only in a parent directory of the CWD. | Cases (a-b) exit `2` with a descriptive usage error. Cases (c-g) exit `1` with an error naming the path; case (g) must not load the parent-directory file. No case creates a listener. | DONE | `.tmp/issue-2151-manual/m4-*.log`; cases (a-b) exit `2`, cases (c-g) exit `1`, including `m4-unreadable.log` (`Permission denied`); no-listener probes passed. | +| M5 | Parallel child isolation (Unix) | Launch two binaries concurrently with different CLI paths, isolated storage, and port-zero configuration. | Both start with their own configuration; neither reads or overwrites the other's source. | DONE | `.tmp/issue-2151-manual/m5-first.log`, `m5-second.log`; distinct discovered port-zero health endpoints, workspace-local SQLite paths, and clean SIGTERM exits. | Manual verification is mandatory. Record a failing scenario and its diagnosis in the progress log before proceeding. ### Acceptance Verification -| AC ID | Status (`TODO`/`DONE`) | Evidence | -| ------------------------- | ---------------------- | ------------------------------------------------------- | -| AC1 | TODO | Parser and executable tests. | -| AC2 | TODO | Configuration/bootstrap tests and M2. | -| AC3 | TODO | Configuration tests and M3. | -| AC4 | TODO | Configuration tests. | -| AC5 | TODO | Executable test and M4. | -| AC6 | TODO | Native child-process test and M5. | -| Quality and documentation | TODO | Linter, relevant test output, and documentation review. | +| AC ID | Status (`TODO`/`DONE`) | Evidence | +| ------------------------- | ---------------------- | -------------------------------------------------------------------------------------- | +| AC1 | DONE | Parser tests; `cargo test --package torrust-tracker --bin torrust-tracker` (7 passed). | +| AC2 | DONE | T1/T4 configuration and bootstrap tests; M2. | +| AC3 | DONE | T1/T2/T6 configuration tests; M3. | +| AC4 | DONE | T6 table-driven mandatory/default tests (129 configuration tests passed). | +| AC5 | DONE | Parser/configuration tests; M4 release-binary command, mode, exit, diagnostic, and no-listener evidence in `.tmp/issue-2151-manual/summary.txt`. | +| AC6 | DONE | `cargo test --test lifecycle-signals` (8 passed); M5. | +| Quality and documentation | DONE | Pre-commit gate, test runs, T8 review, and manual evidence. | ## Risks and Trade-offs @@ -442,11 +444,13 @@ After implementation, compare the result with this specification and record invalidated assumptions, material design changes, unexpected validation findings, and reusable lessons. -- Retrospective: `Not yet assessed` +- Retrospective: `Not needed` - If needed, create `implementation-retrospective.md` from `docs/templates/IMPLEMENTATION-RETROSPECTIVE.md` in this issue directory. -- If no retrospective is needed, add a concise progress-log entry explaining why - the work had no material discovery. +- No separate retrospective was needed: the implementation followed the source + precedence and ownership decisions in this specification. The only discovery + was a disposable verifier deadline mismatch, corrected without changing the + product design; the progress log records it. ## References diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index a21e9d348..5052bfcaf 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -11,7 +11,7 @@ use torrust_tracker_configuration::v3_0_0::Configuration; #[derive(Debug, thiserror::Error)] pub enum Error { #[error( - "Could not prepare the tracker configuration source. Check `TORRUST_TRACKER_CONFIG_TOML_PATH` or `TORRUST_TRACKER_CONFIG_TOML`: {source}" + "Could not prepare the tracker configuration source. Check `--config-toml-path`, `TORRUST_TRACKER_CONFIG_TOML_PATH`, or `TORRUST_TRACKER_CONFIG_TOML`: {source}" )] Source { source: torrust_tracker_configuration::Error }, @@ -164,6 +164,23 @@ mod tests { assert!(matches!(result, Err(Error::Load { .. }))); } + #[test] + fn it_should_name_the_cli_argument_when_an_explicit_configuration_source_cannot_be_prepared() { + // Arrange + let _environment_lock = ENVIRONMENT_LOCK.lock().expect("lock environment access"); + let missing_path = tempfile::tempdir() + .expect("create temporary directory") + .path() + .join("missing-tracker-config.toml"); + + // Act + let error = initialize_configuration(Some(missing_path)) + .expect_err("missing explicit configuration source should fail before loading"); + + // Assert + assert!(error.to_string().contains("--config-toml-path")); + } + #[test] fn it_should_load_an_explicit_path_without_mutating_environment_sources() { // Arrange From 5fac1917b61df6ed261c841a7fd8acac82058800 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 18:08:47 +0100 Subject: [PATCH 08/44] docs(issues): preserve #2151 manual verifier --- .../ISSUE.md | 37 ++- .../manual-verification.py | 239 ++++++++++++++++++ 2 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 6e9a14669..253fae6bf 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,11 +8,12 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 16:30 +last-updated-utc: 2026-09-07 17:00 semantic-links: skill-links: - create-issue related-artifacts: + - docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py - src/main.rs - src/app.rs - src/bootstrap/app.rs @@ -352,8 +353,9 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 15:20 UTC - GitHub Copilot - Completed T6. The existing explicit-file override test remains the regression for per-value override precedence. Added table-driven explicit-file coverage proving each mandatory option is still required before defaults are joined, plus a minimal explicit-file test proving optional values receive the unchanged Rust defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); `linter rustfmt`, `linter clippy`, and `git diff --check` passed. - 2026-09-07 15:40 UTC - GitHub Copilot - Completed T7. Native executable fixtures now pass the isolated configuration through `--config-toml-path` and explicitly remove both inherited base-source environment variables. A concurrent child-process scenario starts two port-zero trackers, waits within each fixture deadline, asserts distinct PIDs, health-check addresses, CLI paths, and workspace-local storage paths, then sends SIGTERM and reaps both children. `cargo test --test lifecycle-signals` passed (8 tests); `cargo test --package torrust-tracker`, Rust formatting, Clippy, and diff checks passed. - 2026-09-07 15:55 UTC - GitHub Copilot - Completed T8. Updated the README, container and benchmarking commands, profiling clarification, root/configuration API docs, source and test guidance, and `run-tracker-locally` skill. The CLI path is documented as the main-binary primary source; environment examples retain their compatible behavior. `validate-skill-links.sh`, `linter markdown`, `linter cspell`, and `git diff --check` passed. -- 2026-09-07 16:30 UTC - GitHub Copilot - Completed T9 verification. The pre-commit gate passed; configuration tests passed (129), tracker tests passed, and lifecycle-signals passed (8). The release binary was built and manual scenarios M1-M5 passed with disposable logs under `.tmp/issue-2151-manual/`. The first verifier run used a 10-second shutdown wait and force-killed an otherwise healthy M1 child; it was corrected to use the fixture-aligned 30-second deadline, then all scenarios passed. No implementation deviation or reusable design discovery warrants a separate retrospective. +- 2026-09-07 16:30 UTC - GitHub Copilot - Completed T9 verification. The pre-commit gate passed; configuration tests passed (129), tracker tests passed, and lifecycle-signals passed (8). The release binary was built and manual scenarios M1-M5 passed with logs under `.tmp/issue-2151-manual/`. The first verifier run used a 10-second shutdown wait and force-killed an otherwise healthy M1 child; it was corrected to use the fixture-aligned 30-second deadline, then all scenarios passed. No implementation deviation or reusable design discovery warrants a separate retrospective. - 2026-09-07 16:50 UTC - Task Reviewer / GitHub Copilot - Independent acceptance review initially found M4 had no unreadable regular-file scenario, M1 incorrectly named a debug binary, T9 remained TODO, and CLI-source remediation text named only environment sources. Added a mode-`000` unreadable regular-file scenario and retained its command, file mode, exit status, and `Permission denied` result in `.tmp/issue-2151-manual/summary.txt`; added no-listener bind probes for malformed and parent-only relative sources; corrected M1 evidence; marked T9 done; and updated the guidance with a regression test. The release-binary manual suite and focused tracker tests passed after correction; all acceptance criteria now pass. +- 2026-09-07 17:00 UTC - Maintainer / GitHub Copilot - Preserved the reusable release-binary manual verifier as `manual-verification.py` in this issue directory. It creates only ignored runtime configurations and logs beneath `.tmp/issue-2151-manual/`, keeping source and reproducible verification procedure together without tracking transient evidence. ## Acceptance Criteria @@ -413,15 +415,15 @@ the progress log before proceeding. ### Acceptance Verification -| AC ID | Status (`TODO`/`DONE`) | Evidence | -| ------------------------- | ---------------------- | -------------------------------------------------------------------------------------- | -| AC1 | DONE | Parser tests; `cargo test --package torrust-tracker --bin torrust-tracker` (7 passed). | -| AC2 | DONE | T1/T4 configuration and bootstrap tests; M2. | -| AC3 | DONE | T1/T2/T6 configuration tests; M3. | -| AC4 | DONE | T6 table-driven mandatory/default tests (129 configuration tests passed). | +| AC ID | Status (`TODO`/`DONE`) | Evidence | +| ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| AC1 | DONE | Parser tests; `cargo test --package torrust-tracker --bin torrust-tracker` (7 passed). | +| AC2 | DONE | T1/T4 configuration and bootstrap tests; M2. | +| AC3 | DONE | T1/T2/T6 configuration tests; M3. | +| AC4 | DONE | T6 table-driven mandatory/default tests (129 configuration tests passed). | | AC5 | DONE | Parser/configuration tests; M4 release-binary command, mode, exit, diagnostic, and no-listener evidence in `.tmp/issue-2151-manual/summary.txt`. | -| AC6 | DONE | `cargo test --test lifecycle-signals` (8 passed); M5. | -| Quality and documentation | DONE | Pre-commit gate, test runs, T8 review, and manual evidence. | +| AC6 | DONE | `cargo test --test lifecycle-signals` (8 passed); M5. | +| Quality and documentation | DONE | Pre-commit gate, test runs, T8 review, and manual evidence. | ## Risks and Trade-offs @@ -438,6 +440,21 @@ the progress log before proceeding. | A refactor exposes complete TOML content in diagnostics. | Preserve redaction behavior and add no secret-bearing logs without an explicit security decision. | | The issue grows into general configuration redesign. | Limit it to a file-path argument and source-selection plumbing. | +## Reusable Manual Verifier + +[`manual-verification.py`](manual-verification.py) reproduces M1-M5 with the +release `torrust-tracker` binary. Build the binary first, then run the script +from the repository root: + +```text +cargo build --release --bin torrust-tracker +python3 docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py +``` + +The script creates configurations, SQLite storage, process logs, and its concise +summary only under `.tmp/issue-2151-manual/`. Those runtime artifacts are +deliberately git-ignored; the script is the durable, reviewed evidence procedure. + ## Implementation Completion Review After implementation, compare the result with this specification and record diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py b/docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py new file mode 100644 index 000000000..45fbefcb0 --- /dev/null +++ b/docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +"""Reproducibly verify the release-binary scenarios for issue #2151. + +Build the binary first with: + cargo build --release --bin torrust-tracker + +The script writes temporary configurations and evidence only to the repository's +ignored `.tmp/issue-2151-manual/` directory. +""" + +from __future__ import annotations + +import os +import re +import shutil +import signal +import socket +import subprocess +import time +import urllib.request +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[4] +WORK = ROOT / ".tmp" / "issue-2151-manual" +BINARY = ROOT / "target" / "release" / "torrust-tracker" +SOURCE_ENV = ("TORRUST_TRACKER_CONFIG_TOML", "TORRUST_TRACKER_CONFIG_TOML_PATH") +SUMMARY = WORK / "summary.txt" + + +def record(message: str) -> None: + with SUMMARY.open("a", encoding="utf-8") as summary: + summary.write(f"{message}\n") + + +def config(health_port: int, storage_path: Path | None = None) -> str: + database = "" + if storage_path: + storage_path.mkdir(parents=True, exist_ok=True) + database = f'''\n[core.database]\ndriver = "sqlite3"\npath = "{storage_path / "sqlite3.db"}"\n''' + + return f'''[metadata] +app = "torrust-tracker" +purpose = "configuration" +schema_version = "3.0.0" + +[logging] +trace_filter = "info" + +[core] +listed = false +private = false +{database} +[health_check_api] +bind_address = "127.0.0.1:{health_port}" +''' + + +def clean_environment(extra: dict[str, str] | None = None) -> dict[str, str]: + environment = os.environ.copy() + for name in SOURCE_ENV: + environment.pop(name, None) + if extra: + environment.update(extra) + return environment + + +def start(name: str, arguments: list[str], extra: dict[str, str] | None = None) -> subprocess.Popen[str]: + log = (WORK / f"{name}.log").open("w", encoding="utf-8") + process = subprocess.Popen( + [str(BINARY), *arguments], + cwd=ROOT, + env=clean_environment(extra), + stdout=log, + stderr=subprocess.STDOUT, + text=True, + ) + process._issue_2151_log = log # type: ignore[attr-defined] + return process + + +def finish(process: subprocess.Popen[str], timeout: float = 30) -> tuple[int, str]: + if process.poll() is None: + process.send_signal(signal.SIGTERM) + try: + code = process.wait(timeout) + except subprocess.TimeoutExpired: + process.kill() + code = process.wait(5) + raise AssertionError(f"process timed out and was killed with {code}") + process._issue_2151_log.close() # type: ignore[attr-defined] + return code, Path(process._issue_2151_log.name).read_text(encoding="utf-8") # type: ignore[attr-defined] + + +def wait_for_health(port: int, process: subprocess.Popen[str]) -> None: + deadline = time.monotonic() + 10 + url = f"http://127.0.0.1:{port}/health_check" + while time.monotonic() < deadline: + if process.poll() is not None: + _, output = finish(process) + raise AssertionError(f"process exited before health endpoint was ready:\n{output}") + try: + with urllib.request.urlopen(url, timeout=0.5) as response: + if response.status == 200: + return + except OSError: + pass + time.sleep(0.05) + _, output = finish(process) + raise AssertionError(f"timed out waiting for {url}:\n{output}") + + +def wait_for_port_zero_health(process: subprocess.Popen[str]) -> int: + deadline = time.monotonic() + 10 + pattern = re.compile(r"HEALTH CHECK API.*Started on: http://127\.0\.0\.1:(\d+)") + while time.monotonic() < deadline: + if process.poll() is not None: + _, output = finish(process) + raise AssertionError(f"process exited before readiness:\n{output}") + log_path = Path(process._issue_2151_log.name) # type: ignore[attr-defined] + match = pattern.search(log_path.read_text(encoding="utf-8")) + if match: + port = int(match.group(1)) + wait_for_health(port, process) + return port + time.sleep(0.05) + _, output = finish(process) + raise AssertionError(f"timed out discovering port-zero health endpoint:\n{output}") + + +def assert_exits(name: str, arguments: list[str], expected_code: int, expected_text: str, cwd: Path = ROOT) -> None: + result = subprocess.run( + [str(BINARY), *arguments], + cwd=cwd, + env=clean_environment(), + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + timeout=10, + ) + (WORK / f"{name}.log").write_text(result.stdout, encoding="utf-8") + assert result.returncode == expected_code, (name, result.returncode, result.stdout) + assert expected_text in result.stdout, (name, expected_text, result.stdout) + record( + f"{name}: command={BINARY} {' '.join(arguments)!r}; cwd={cwd}; " + f"exit={result.returncode}; expected-text={expected_text!r}; PASS" + ) + + +def assert_port_is_available(port: int) -> None: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener: + listener.bind(("127.0.0.1", port)) + record(f"listener-probe: bind 127.0.0.1:{port} after failed startup; PASS (port available)") + + +def main() -> None: + if not BINARY.is_file(): + raise SystemExit(f"missing release binary; run `cargo build --release --bin torrust-tracker`: {BINARY}") + shutil.rmtree(WORK, ignore_errors=True) + WORK.mkdir(parents=True) + + # M1: CLI-only source. + m1 = WORK / "m1.toml" + m1.write_text(config(43151), encoding="utf-8") + process = start("m1", ["--config-toml-path", str(m1)]) + wait_for_health(43151, process) + code, output = finish(process) + assert code == 0 and "successfully shutdown" in output, output + + # M2: CLI path outranks both environment base sources. + cli = WORK / "m2-cli.toml" + env_path = WORK / "m2-env-path.toml" + cli.write_text(config(43152), encoding="utf-8") + env_path.write_text(config(43153), encoding="utf-8") + process = start( + "m2", + ["--config-toml-path", str(cli)], + {"TORRUST_TRACKER_CONFIG_TOML": config(43154), "TORRUST_TRACKER_CONFIG_TOML_PATH": str(env_path)}, + ) + wait_for_health(43152, process) + code, output = finish(process) + assert code == 0, output + + # M3: override applies over a CLI-selected base file. + m3 = WORK / "m3.toml" + m3.write_text(config(43155), encoding="utf-8") + process = start( + "m3", + ["--config-toml-path", str(m3)], + {"TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS": "127.0.0.1:43156"}, + ) + wait_for_health(43156, process) + code, output = finish(process) + assert code == 0, output + + # M4: usage and strict source errors; no source is valid or reaches a listener. + assert_exits("m4-missing-value", ["--config-toml-path"], 2, "a value is required") + assert_exits("m4-empty-value", ["--config-toml-path", ""], 2, "must not be empty") + missing = WORK / "does-not-exist.toml" + assert_exits("m4-missing-file", ["--config-toml-path", str(missing)], 1, str(missing)) + assert_exits("m4-directory", ["--config-toml-path", str(WORK)], 1, str(WORK)) + malformed = WORK / "malformed.toml" + malformed.write_text(f"{config(43158)}" "malformed_key = [", encoding="utf-8") + assert_exits("m4-malformed", ["--config-toml-path", str(malformed)], 1, str(malformed)) + assert_port_is_available(43158) + unreadable = WORK / "unreadable.toml" + unreadable.write_text(config(43159), encoding="utf-8") + unreadable.chmod(0) + try: + record(f"m4-unreadable: path={unreadable}; regular-file={unreadable.is_file()}; mode={unreadable.stat().st_mode & 0o777:o}") + assert_exits("m4-unreadable", ["--config-toml-path", str(unreadable)], 1, str(unreadable)) + finally: + unreadable.chmod(0o600) + parent = WORK / "parent" + child = parent / "child" + child.mkdir(parents=True) + (parent / "tracker.toml").write_text(config(43157), encoding="utf-8") + assert_exits("m4-parent-only-relative", ["--config-toml-path", "tracker.toml"], 1, "tracker.toml", child) + assert_port_is_available(43157) + + # M5: two port-zero CLI-selected children, independent paths and endpoints. + first = WORK / "m5-first.toml" + second = WORK / "m5-second.toml" + first.write_text(config(0, WORK / "m5-first-storage"), encoding="utf-8") + second.write_text(config(0, WORK / "m5-second-storage"), encoding="utf-8") + first_process = start("m5-first", ["-c", str(first)]) + second_process = start("m5-second", ["-c", str(second)]) + first_port = wait_for_port_zero_health(first_process) + second_port = wait_for_port_zero_health(second_process) + assert first_port != second_port, (first_port, second_port) + first_code, first_output = finish(first_process) + second_code, second_output = finish(second_process) + assert first_code == second_code == 0 + assert "successfully shutdown" in first_output and "successfully shutdown" in second_output + + print(f"Manual verification passed; evidence: {WORK}") + + +if __name__ == "__main__": + main() From 2d142e53e72ab87e0ef499d6668c47d03ba5c046 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Mon, 7 Sep 2026 18:12:55 +0100 Subject: [PATCH 09/44] docs(issues): rename #2151 CLI verifier --- .../2151-add-tracker-config-path-argument/ISSUE.md | 14 +++++++------- ...verification.py => release-cli-verification.py} | 0 2 files changed, 7 insertions(+), 7 deletions(-) rename docs/issues/open/2151-add-tracker-config-path-argument/{manual-verification.py => release-cli-verification.py} (100%) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 253fae6bf..f0e21e56c 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -13,7 +13,7 @@ semantic-links: skill-links: - create-issue related-artifacts: - - docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py + - docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py - src/main.rs - src/app.rs - src/bootstrap/app.rs @@ -355,7 +355,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 15:55 UTC - GitHub Copilot - Completed T8. Updated the README, container and benchmarking commands, profiling clarification, root/configuration API docs, source and test guidance, and `run-tracker-locally` skill. The CLI path is documented as the main-binary primary source; environment examples retain their compatible behavior. `validate-skill-links.sh`, `linter markdown`, `linter cspell`, and `git diff --check` passed. - 2026-09-07 16:30 UTC - GitHub Copilot - Completed T9 verification. The pre-commit gate passed; configuration tests passed (129), tracker tests passed, and lifecycle-signals passed (8). The release binary was built and manual scenarios M1-M5 passed with logs under `.tmp/issue-2151-manual/`. The first verifier run used a 10-second shutdown wait and force-killed an otherwise healthy M1 child; it was corrected to use the fixture-aligned 30-second deadline, then all scenarios passed. No implementation deviation or reusable design discovery warrants a separate retrospective. - 2026-09-07 16:50 UTC - Task Reviewer / GitHub Copilot - Independent acceptance review initially found M4 had no unreadable regular-file scenario, M1 incorrectly named a debug binary, T9 remained TODO, and CLI-source remediation text named only environment sources. Added a mode-`000` unreadable regular-file scenario and retained its command, file mode, exit status, and `Permission denied` result in `.tmp/issue-2151-manual/summary.txt`; added no-listener bind probes for malformed and parent-only relative sources; corrected M1 evidence; marked T9 done; and updated the guidance with a regression test. The release-binary manual suite and focused tracker tests passed after correction; all acceptance criteria now pass. -- 2026-09-07 17:00 UTC - Maintainer / GitHub Copilot - Preserved the reusable release-binary manual verifier as `manual-verification.py` in this issue directory. It creates only ignored runtime configurations and logs beneath `.tmp/issue-2151-manual/`, keeping source and reproducible verification procedure together without tracking transient evidence. +- 2026-09-07 17:00 UTC - Maintainer / GitHub Copilot - Preserved the reusable release-binary CLI verifier as `release-cli-verification.py` in this issue directory. It creates only ignored runtime configurations and logs beneath `.tmp/issue-2151-manual/`, keeping source and reproducible verification procedure together without tracking transient evidence. ## Acceptance Criteria @@ -440,15 +440,15 @@ the progress log before proceeding. | A refactor exposes complete TOML content in diagnostics. | Preserve redaction behavior and add no secret-bearing logs without an explicit security decision. | | The issue grows into general configuration redesign. | Limit it to a file-path argument and source-selection plumbing. | -## Reusable Manual Verifier +## Reusable Release CLI Verifier -[`manual-verification.py`](manual-verification.py) reproduces M1-M5 with the -release `torrust-tracker` binary. Build the binary first, then run the script -from the repository root: +[`release-cli-verification.py`](release-cli-verification.py) automatically +reproduces M1-M5 with the release `torrust-tracker` binary. Build the binary +first, then run the script from the repository root: ```text cargo build --release --bin torrust-tracker -python3 docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py +python3 docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py ``` The script creates configurations, SQLite storage, process logs, and its concise diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py b/docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py similarity index 100% rename from docs/issues/open/2151-add-tracker-config-path-argument/manual-verification.py rename to docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py From ed1f1c2ef0e394e768655f16c1625182d14c72d4 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 09:40:57 +0100 Subject: [PATCH 10/44] docs(issues): plan Rust coverage for #2151 --- .../ISSUE.md | 22 ++- .../rust-executable-test-plan.md | 128 ++++++++++++++++++ 2 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index f0e21e56c..acd10d566 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,12 +8,13 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-07 17:00 +last-updated-utc: 2026-09-08 09:00 semantic-links: skill-links: - create-issue related-artifacts: - docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py + - docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md - src/main.rs - src/app.rs - src/bootstrap/app.rs @@ -206,6 +207,9 @@ TORRUST_TRACKER_CONFIG_TOML_PATH=b.toml \ do not mutate process environment variables to emulate the option. - Add unit, executable-level integration, and manual coverage for precedence, default behavior, and diagnostics. +- Keep all tracked repository test code in Rust. The release CLI Python harness + is temporary evidence only and must be removed after the approved + `rust-executable-test-plan.md` preserves its behavior in Rust tests. - Review the affected operational and native-test documentation. ### Out of Scope @@ -312,6 +316,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | | T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | | T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Manual M1-M5 release-binary scenarios passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks. Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | +| T10 | TODO | Complete Rust executable coverage | Implement the approved `rust-executable-test-plan.md`: preserve the relevant release CLI harness behavior in Rust executable-boundary tests, document the Rust-only tracked test-code policy, remove the Python harness, then repeat final validation and acceptance review. | Each task must be independently buildable and tested. T1 is a behavior-preserving safety-net change; T2 is a configuration refactor; T3-T4 @@ -356,6 +361,8 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 16:30 UTC - GitHub Copilot - Completed T9 verification. The pre-commit gate passed; configuration tests passed (129), tracker tests passed, and lifecycle-signals passed (8). The release binary was built and manual scenarios M1-M5 passed with logs under `.tmp/issue-2151-manual/`. The first verifier run used a 10-second shutdown wait and force-killed an otherwise healthy M1 child; it was corrected to use the fixture-aligned 30-second deadline, then all scenarios passed. No implementation deviation or reusable design discovery warrants a separate retrospective. - 2026-09-07 16:50 UTC - Task Reviewer / GitHub Copilot - Independent acceptance review initially found M4 had no unreadable regular-file scenario, M1 incorrectly named a debug binary, T9 remained TODO, and CLI-source remediation text named only environment sources. Added a mode-`000` unreadable regular-file scenario and retained its command, file mode, exit status, and `Permission denied` result in `.tmp/issue-2151-manual/summary.txt`; added no-listener bind probes for malformed and parent-only relative sources; corrected M1 evidence; marked T9 done; and updated the guidance with a regression test. The release-binary manual suite and focused tracker tests passed after correction; all acceptance criteria now pass. - 2026-09-07 17:00 UTC - Maintainer / GitHub Copilot - Preserved the reusable release-binary CLI verifier as `release-cli-verification.py` in this issue directory. It creates only ignored runtime configurations and logs beneath `.tmp/issue-2151-manual/`, keeping source and reproducible verification procedure together without tracking transient evidence. +- 2026-09-07 17:25 UTC - Maintainer / GitHub Copilot - Reclassified the tracked Python verifier as temporary evidence: repository test code must be Rust. Added `rust-executable-test-plan.md` for maintainer review before implementation. T10 will preserve appropriate executable behavior in Rust tests, document the policy, remove the Python harness, and repeat completion validation. +- 2026-09-08 09:00 UTC - Maintainer / GitHub Copilot - Refined the pending Rust test plan: move the reusable native child-process fixture from `tests/lifecycle/` to `tests/common/`; create `tests/configuration/cli_configuration.rs` for executable configuration contracts; retain `tests/lifecycle/signals.rs` for OS-signal contracts only. The shared fixture must offer narrowly configured child commands without duplicating process lifecycle ownership. ## Acceptance Criteria @@ -440,11 +447,16 @@ the progress log before proceeding. | A refactor exposes complete TOML content in diagnostics. | Preserve redaction behavior and add no secret-bearing logs without an explicit security decision. | | The issue grows into general configuration redesign. | Limit it to a file-path argument and source-selection plumbing. | -## Reusable Release CLI Verifier +## Temporary Release CLI Evidence -[`release-cli-verification.py`](release-cli-verification.py) automatically -reproduces M1-M5 with the release `torrust-tracker` binary. Build the binary -first, then run the script from the repository root: +[`release-cli-verification.py`](release-cli-verification.py) recorded the initial +release-binary evidence for M1-M5. It is not a durable repository test because +tracked test code must be Rust. The approved replacement plan is +[`rust-executable-test-plan.md`](rust-executable-test-plan.md); T10 removes this +Python artifact only after its relevant behavior is covered by Rust tests. + +Until T10 is complete, build the binary first, then run the temporary evidence +procedure from the repository root: ```text cargo build --release --bin torrust-tracker diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md new file mode 100644 index 000000000..b4b07e815 --- /dev/null +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -0,0 +1,128 @@ +--- +semantic-links: + skill-links: + - write-unit-test + related-artifacts: + - docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md + - docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py + - docs/testing.md + - tests/AGENTS.md + - tests/common/native_tracker.rs + - tests/configuration/cli_configuration.rs + - tests/lifecycle/signals.rs + - .github/skills/dev/testing/write-unit-test/SKILL.md +--- + +# Rust Executable-Test Completion Plan + +## Purpose + +Replace the issue-local Python release CLI verification harness with maintained +Rust tests before completing issue #2151. The Python harness records useful +release-binary scenarios, but repository tests must be Rust. Do not add Python +for test code in this repository. + +The existing Rust configuration and bootstrap tests remain the lowest-cost tests +for source-selection semantics. This plan adds only the executable-boundary +coverage that the Python harness currently supplies, then removes the Python +artifact after its behavior is preserved. + +## Test-Layer Decisions + +The reusable native tracker fixture belongs in `tests/common/`, not the +signal-specific `tests/lifecycle/` directory. The executable configuration +scenarios belong in `tests/configuration/`, while `tests/lifecycle/` retains only +operating-system signal behavior: + +```text +tests/ +├── common/ +│ └── native_tracker.rs # Child process, workspace, output, readiness, cleanup +├── configuration/ +│ └── cli_configuration.rs # Tracker CLI source-selection process contracts +└── lifecycle/ + └── signals.rs # SIGINT, SIGTERM, and drop-path contracts +``` + +Each top-level test source remains a separate Cargo integration-test executable. +Both `configuration/cli_configuration.rs` and `lifecycle/signals.rs` include the +shared fixture through `#[path = "../common/native_tracker.rs"] mod native_tracker;`. + +| Behavior | Test layer | Location | Reason | +| ----------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CLI parsing, usage errors, and exit code `2` | Executable-boundary integration | `tests/configuration/cli_configuration.rs` | A parser unit test cannot prove the compiled tracker process exits without starting. | +| CLI path wins over inherited complete-TOML and path environment sources | Executable-boundary integration | `tests/configuration/cli_configuration.rs` | Configuration and bootstrap tests already prove selection; a child process proves the main binary carries the parsed CLI path through all layers. | +| Per-value override wins over a CLI base file | Executable-boundary integration | `tests/configuration/cli_configuration.rs` | Configuration tests already prove the merge; a child process covers the user-facing process contract. | +| Missing, directory, malformed, and parent-only-relative CLI sources | Executable-boundary integration | `tests/configuration/cli_configuration.rs` | Must prove process exit `1`, contextual diagnostics, and no listener at the main-binary boundary. | +| Unreadable regular-file CLI source | Unix executable-boundary integration | `tests/configuration/cli_configuration.rs` | Permission semantics are platform-specific. Gate it on Unix and avoid assuming privileged runners cannot read mode-`000` files. | +| Two independent CLI-configured tracker children | Existing executable-boundary integration | `tests/lifecycle/signals.rs` | Already covered; retain as the regression for process, workspace, storage, port-zero, and shutdown isolation. | +| Release-profile artifact behavior | Manual release verification | Concise commands/evidence in `ISSUE.md` | Cargo test binaries are the maintained automated regression layer. Building and executing `target/release` belongs to final manual validation, not a second scripted test implementation. | + +Container E2E is not required: this feature changes argument parsing and native +startup selection, not container composition or BitTorrent-client +interoperability. The container receives arguments through the existing +entrypoint forwarding contract and is covered by container-image CI. + +## Implementation Plan + +Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. + +| ID | Status | Task | Expected result | +| --- | ------ | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R1 | TODO | Extract common native fixture | Move `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs`. Preserve its child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup ownership. Add only the narrow child-command options required for configuration tests; default lifecycle use must remain isolated. | +| R2 | TODO | Add configuration CLI test target | Add and register `tests/configuration/cli_configuration.rs`. Reuse the fixture through an explicit path module declaration; do not duplicate child-process management. | +| R3 | TODO | Add CLI precedence process tests | Add separate AAA scenarios to `cli_configuration.rs` for CLI-only configuration, CLI plus both inherited base environment sources, and CLI plus a per-value override. Use separate valid config files with observable health-check ports; verify readiness and graceful shutdown. | +| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | +| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | +| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | +| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | +| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | + +## Scenario Contracts + +Each executable-boundary scenario uses an isolated `TempDir`, child-specific +configuration, port-zero bindings unless a fixed candidate port is necessary to +prove no listener, and the fixture's existing absolute deadlines. + +- **Precedence:** set conflicting valid `TORRUST_TRACKER_CONFIG_TOML` and + `TORRUST_TRACKER_CONFIG_TOML_PATH` only in the child `Command`; the health + endpoint from the CLI file must become ready. Do not mutate test-process + environment variables. +- **Override:** child command sets a distinguishable + `TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS`; the override + endpoint becomes ready, not the CLI file endpoint. +- **Failure:** each child is reaped before assertions complete. A fixed + loopback candidate port is bound/probed after failure only when the invalid + source was otherwise capable of providing that port (malformed and + parent-only-relative cases). +- **Unreadable file:** Unix-specific test setup and cleanup own the file mode. + The assertion must account for a privileged runner that bypasses permission + bits and report an explicit skip rather than masking a platform constraint. + +## Validation + +During development: + +```text +cargo test --test lifecycle-signals +cargo test --package torrust-tracker +linter rustfmt +linter clippy +``` + +Before committing the completed coverage increment: + +```text +./contrib/dev-tools/git/hooks/pre-commit.sh --format=json +``` + +## Completion Conditions + +- Every executable behavior formerly asserted by `release-cli-verification.py` + has a Rust test at the selected layer, or has a documented manual-only reason. +- No tracked Python test code remains. +- The Rust-only test policy is documented in the canonical testing guidance and + reflected in the root integration-test guidance. +- The issue spec is updated with final test evidence before implementation PR + creation. From d4bf500704f1222a7d924bea74a7477215359627 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 09:56:22 +0100 Subject: [PATCH 11/44] test(integration): extract native tracker fixture --- .../ISSUE.md | 3 ++- .../rust-executable-test-plan.md | 22 +++++++++---------- tests/{lifecycle => common}/native_tracker.rs | 0 tests/lifecycle/signals.rs | 1 + 4 files changed, 14 insertions(+), 12 deletions(-) rename tests/{lifecycle => common}/native_tracker.rs (100%) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index acd10d566..8e1b3d8fe 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-08 09:00 +last-updated-utc: 2026-09-08 09:15 semantic-links: skill-links: - create-issue @@ -363,6 +363,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 17:00 UTC - Maintainer / GitHub Copilot - Preserved the reusable release-binary CLI verifier as `release-cli-verification.py` in this issue directory. It creates only ignored runtime configurations and logs beneath `.tmp/issue-2151-manual/`, keeping source and reproducible verification procedure together without tracking transient evidence. - 2026-09-07 17:25 UTC - Maintainer / GitHub Copilot - Reclassified the tracked Python verifier as temporary evidence: repository test code must be Rust. Added `rust-executable-test-plan.md` for maintainer review before implementation. T10 will preserve appropriate executable behavior in Rust tests, document the policy, remove the Python harness, and repeat completion validation. - 2026-09-08 09:00 UTC - Maintainer / GitHub Copilot - Refined the pending Rust test plan: move the reusable native child-process fixture from `tests/lifecycle/` to `tests/common/`; create `tests/configuration/cli_configuration.rs` for executable configuration contracts; retain `tests/lifecycle/signals.rs` for OS-signal contracts only. The shared fixture must offer narrowly configured child commands without duplicating process lifecycle ownership. +- 2026-09-08 09:15 UTC - GitHub Copilot - Completed R1. Moved the native child-process fixture to `tests/common/native_tracker.rs` and updated `tests/lifecycle/signals.rs` to import it through an explicit path module declaration. The signal suite passed unchanged (8 tests). ## Acceptance Criteria diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index b4b07e815..d54bc8fb7 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -67,17 +67,17 @@ entrypoint forwarding contract and is covered by container-image CI. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| --- | ------ | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| R1 | TODO | Extract common native fixture | Move `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs`. Preserve its child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup ownership. Add only the narrow child-command options required for configuration tests; default lifecycle use must remain isolated. | -| R2 | TODO | Add configuration CLI test target | Add and register `tests/configuration/cli_configuration.rs`. Reuse the fixture through an explicit path module declaration; do not duplicate child-process management. | -| R3 | TODO | Add CLI precedence process tests | Add separate AAA scenarios to `cli_configuration.rs` for CLI-only configuration, CLI plus both inherited base environment sources, and CLI plus a per-value override. Use separate valid config files with observable health-check ports; verify readiness and graceful shutdown. | -| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | -| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | -| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | -| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | -| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | -| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | +| ID | Status | Task | Expected result | +| --- | ------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | +| R2 | TODO | Add configuration CLI test target | Add and register `tests/configuration/cli_configuration.rs`. Reuse the fixture through an explicit path module declaration; do not duplicate child-process management. | +| R3 | TODO | Add CLI precedence process tests | Add separate AAA scenarios to `cli_configuration.rs` for CLI-only configuration, CLI plus both inherited base environment sources, and CLI plus a per-value override. Use separate valid config files with observable health-check ports; verify readiness and graceful shutdown. | +| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | +| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | +| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | +| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | +| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | ## Scenario Contracts diff --git a/tests/lifecycle/native_tracker.rs b/tests/common/native_tracker.rs similarity index 100% rename from tests/lifecycle/native_tracker.rs rename to tests/common/native_tracker.rs diff --git a/tests/lifecycle/signals.rs b/tests/lifecycle/signals.rs index a5395fbc4..96d029974 100644 --- a/tests/lifecycle/signals.rs +++ b/tests/lifecycle/signals.rs @@ -3,6 +3,7 @@ #![cfg_attr(not(unix), allow(dead_code, unused_imports))] #[cfg(unix)] +#[path = "../common/native_tracker.rs"] mod native_tracker; #[cfg(unix)] From ff006bce1746ec2aa5183aaaf5e9718257711e70 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 10:13:24 +0100 Subject: [PATCH 12/44] docs(issues): refine #2151 Rust test plan --- .../ISSUE.md | 3 ++- .../rust-executable-test-plan.md | 22 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 8e1b3d8fe..bb97412f3 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-08 09:15 +last-updated-utc: 2026-09-08 09:35 semantic-links: skill-links: - create-issue @@ -364,6 +364,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-07 17:25 UTC - Maintainer / GitHub Copilot - Reclassified the tracked Python verifier as temporary evidence: repository test code must be Rust. Added `rust-executable-test-plan.md` for maintainer review before implementation. T10 will preserve appropriate executable behavior in Rust tests, document the policy, remove the Python harness, and repeat completion validation. - 2026-09-08 09:00 UTC - Maintainer / GitHub Copilot - Refined the pending Rust test plan: move the reusable native child-process fixture from `tests/lifecycle/` to `tests/common/`; create `tests/configuration/cli_configuration.rs` for executable configuration contracts; retain `tests/lifecycle/signals.rs` for OS-signal contracts only. The shared fixture must offer narrowly configured child commands without duplicating process lifecycle ownership. - 2026-09-08 09:15 UTC - GitHub Copilot - Completed R1. Moved the native child-process fixture to `tests/common/native_tracker.rs` and updated `tests/lifecycle/signals.rs` to import it through an explicit path module declaration. The signal suite passed unchanged (8 tests). +- 2026-09-08 09:35 UTC - GitHub Copilot - Created a provisional `cli-configuration` target and validated its shared-fixture import. Review found its sole scenario duplicated SIGTERM lifecycle coverage without asserting a configuration contract, so the uncommitted target was removed. R2 remains pending and must begin with a configuration-specific executable-boundary scenario. ## Acceptance Criteria diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index d54bc8fb7..bf03bbca6 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -67,17 +67,17 @@ entrypoint forwarding contract and is covered by container-image CI. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| --- | ------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | -| R2 | TODO | Add configuration CLI test target | Add and register `tests/configuration/cli_configuration.rs`. Reuse the fixture through an explicit path module declaration; do not duplicate child-process management. | -| R3 | TODO | Add CLI precedence process tests | Add separate AAA scenarios to `cli_configuration.rs` for CLI-only configuration, CLI plus both inherited base environment sources, and CLI plus a per-value override. Use separate valid config files with observable health-check ports; verify readiness and graceful shutdown. | -| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | -| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | -| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | -| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | -| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | -| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | +| ID | Status | Task | Expected result | +| --- | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | +| R2 | TODO | Add configuration CLI test target | Add and register `tests/configuration/cli_configuration.rs` only with its first configuration-specific scenario. Reuse the fixture through an explicit path module declaration; do not duplicate child-process management or add lifecycle-only coverage. | +| R3 | TODO | Add CLI precedence process tests | Add separate AAA scenarios to `cli_configuration.rs` for CLI-only configuration, CLI plus both inherited base environment sources, and CLI plus a per-value override. Use separate valid config files with observable health-check ports; verify readiness and graceful shutdown. | +| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | +| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | +| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | +| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | +| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | ## Scenario Contracts From e4dc4b268f8ff95fa503d18371328e0aa1c65cda Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 11:08:23 +0100 Subject: [PATCH 13/44] test(integration): configure native tracker sources --- tests/common/native_tracker.rs | 130 ++++++++++++++++++++++++++++++--- 1 file changed, 120 insertions(+), 10 deletions(-) diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index 7c2a295ed..6d276b97a 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -12,6 +12,8 @@ use std::process::Stdio; use std::sync::Arc; use std::time::Duration; +use nix::sys::signal::{Signal, kill}; +use nix::unistd::Pid; use tokio::io::{AsyncBufReadExt as _, AsyncRead, BufReader}; use tokio::process::{Child, Command}; use tokio::sync::{Mutex, oneshot}; @@ -47,7 +49,7 @@ bind_address = "127.0.0.1:0" tracker_usage_statistics = false [health_check_api] -bind_address = "127.0.0.1:0" +bind_address = "127.0.0.1:{HEALTH_CHECK_PORT}" "#; /// A running tracker executable isolated in a temporary workspace. @@ -60,22 +62,79 @@ pub struct NativeTracker { drop_cleanup_observer: Option>>, } +/// Base configuration sources supplied to one tracker child. +/// +/// The fixture writes all corresponding files into its temporary workspace. +/// Ports are the only configurable values because executable configuration +/// tests need no other child-process configuration surface. +#[derive(Clone, Copy)] +// This shared module is compiled by signal-only and configuration test binaries; +// the former does not use configuration-specific source builders. +#[allow(dead_code)] +pub struct NativeTrackerConfigurationSources { + cli: u16, + environment_path: Option, + environment_toml: Option, +} + +impl NativeTrackerConfigurationSources { + /// Creates sources whose CLI-selected configuration listens on `port`. + pub const fn with_cli_health_check_port(port: u16) -> Self { + Self { + cli: port, + environment_path: None, + environment_toml: None, + } + } + + /// Adds a child-only environment path source with its own health-check port. + // See the type-level allowance: signal-only test binaries do not use it. + #[allow(dead_code)] + pub const fn with_environment_path_health_check_port(mut self, port: u16) -> Self { + self.environment_path = Some(port); + self + } + + /// Adds a child-only complete-TOML environment source with its own health-check port. + // See the type-level allowance: signal-only test binaries do not use it. + #[allow(dead_code)] + pub const fn with_environment_toml_health_check_port(mut self, port: u16) -> Self { + self.environment_toml = Some(port); + self + } +} + /// An isolated workspace and configuration for one tracker child process. struct NativeTrackerWorkspace { _workspace: tempfile::TempDir, configuration_path: PathBuf, storage_path: PathBuf, + environment_configuration_path: Option, + environment_configuration_toml: Option, } impl NativeTrackerWorkspace { fn new() -> Self { + Self::with_configuration_sources(NativeTrackerConfigurationSources::with_cli_health_check_port(0)) + } + + fn with_configuration_sources(sources: NativeTrackerConfigurationSources) -> Self { let workspace = tempfile::tempdir().expect("create temporary tracker workspace"); - let (configuration_path, storage_path) = write_configuration(&workspace); + let (configuration_path, storage_path) = write_configuration(&workspace, "cli", sources.cli); + let environment_configuration_path = sources + .environment_path + .map(|port| write_configuration(&workspace, "environment-path", port).0); + let environment_configuration_toml = sources.environment_toml.map(|port| { + let (configuration_path, _) = write_configuration(&workspace, "environment-toml", port); + std::fs::read_to_string(configuration_path).expect("read environment TOML configuration") + }); Self { _workspace: workspace, configuration_path, storage_path, + environment_configuration_path, + environment_configuration_toml, } } @@ -86,6 +145,14 @@ impl NativeTrackerWorkspace { fn storage_path(&self) -> &std::path::Path { &self.storage_path } + + fn environment_configuration_path(&self) -> Option<&std::path::Path> { + self.environment_configuration_path.as_deref() + } + + fn environment_configuration_toml(&self) -> Option<&str> { + self.environment_configuration_toml.as_deref() + } } /// Concurrently drains and retains a tracker child's output for readiness and diagnostics. @@ -173,7 +240,23 @@ impl NativeTracker { /// Spawns the Cargo-built tracker binary with an isolated CLI configuration and port-zero bindings. pub fn start() -> Self { let workspace = NativeTrackerWorkspace::new(); - let mut command = tracker_command(workspace.configuration_path()); + Self::start_in_workspace(workspace) + } + + /// Spawns a tracker child with fixture-owned CLI and optional environment base sources. + // This shared module is also compiled by signal-only test binaries. + #[allow(dead_code)] + pub fn start_with_configuration_sources(sources: NativeTrackerConfigurationSources) -> Self { + let workspace = NativeTrackerWorkspace::with_configuration_sources(sources); + Self::start_in_workspace(workspace) + } + + fn start_in_workspace(workspace: NativeTrackerWorkspace) -> Self { + let mut command = tracker_command( + workspace.configuration_path(), + workspace.environment_configuration_path(), + workspace.environment_configuration_toml(), + ); let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); let stdout = child.stdout.take().expect("tracker child stdout is piped"); @@ -269,6 +352,21 @@ impl NativeTracker { .map_err(|message| format!("{message}\ntracker output:\n{output}")) } + /// Delivers SIGTERM to the child and waits for its graceful shutdown. + // Configuration tests use this convenience; signal tests verify delivery directly. + #[allow(dead_code)] + pub async fn gracefully_shutdown(self) -> Result { + let pid = self.pid()?; + kill( + Pid::from_raw( + i32::try_from(pid).map_err(|error| Self::failure_message_sync(&format!("convert tracker PID: {error}")))?, + ), + Signal::SIGTERM, + ) + .map_err(|error| Self::failure_message_sync(&format!("deliver SIGTERM to tracker child: {error}")))?; + self.shutdown().await + } + /// Returns an observer for the signal that terminated the reaped drop-path child. pub const fn take_drop_cleanup_observer(&mut self) -> oneshot::Receiver> { self.drop_cleanup_observer @@ -434,11 +532,13 @@ fn parse_health_check_address(line: &str) -> Option { address.parse().ok() } -fn write_configuration(workspace: &tempfile::TempDir) -> (PathBuf, PathBuf) { - let storage_path = workspace.path().join("storage"); +fn write_configuration(workspace: &tempfile::TempDir, name: &str, health_check_port: u16) -> (PathBuf, PathBuf) { + let storage_path = workspace.path().join(format!("{name}-storage")); std::fs::create_dir_all(&storage_path).expect("create tracker storage directory"); - let config_path = workspace.path().join("tracker.toml"); - let config = CONFIGURATION.replace("{STORAGE_PATH}", &storage_path.to_string_lossy()); + let config_path = workspace.path().join(format!("{name}-tracker.toml")); + let config = CONFIGURATION + .replace("{STORAGE_PATH}", &storage_path.to_string_lossy()) + .replace("{HEALTH_CHECK_PORT}", &health_check_port.to_string()); std::fs::write(&config_path, config).expect("write tracker configuration"); (config_path, storage_path) } @@ -447,7 +547,11 @@ fn write_configuration(workspace: &tempfile::TempDir) -> (PathBuf, PathBuf) { /// /// The two legacy base-source variables are explicitly removed so inherited /// environment state cannot override or obscure a fixture's CLI-selected file. -fn tracker_command(configuration_path: &std::path::Path) -> Command { +fn tracker_command( + configuration_path: &std::path::Path, + environment_configuration_path: Option<&std::path::Path>, + environment_configuration_toml: Option<&str>, +) -> Command { let mut command = Command::new(tracker_binary()); command .arg("--config-toml-path") @@ -459,6 +563,12 @@ fn tracker_command(configuration_path: &std::path::Path) -> Command { .kill_on_drop(true) .stdout(Stdio::piped()) .stderr(Stdio::piped()); + if let Some(path) = environment_configuration_path { + command.env("TORRUST_TRACKER_CONFIG_TOML_PATH", path); + } + if let Some(toml) = environment_configuration_toml { + command.env("TORRUST_TRACKER_CONFIG_TOML", toml); + } command } @@ -481,7 +591,7 @@ mod tests { let configuration_path = Path::new("/workspace/tracker.toml"); // Act - let command = tracker_command(configuration_path); + let command = tracker_command(configuration_path, None, None); let arguments = command.as_std().get_args().collect::>(); let environment = command.as_std().get_envs().collect::>(); @@ -506,7 +616,7 @@ mod tests { let workspace = tempfile::tempdir().expect("create temporary tracker workspace"); // Act - let (config_path, storage_path) = write_configuration(&workspace); + let (config_path, storage_path) = write_configuration(&workspace, "test", 0); let configuration = std::fs::read_to_string(&config_path).expect("read tracker configuration"); // Assert From 1609f92758ecd7e92797c97d69ae9b6b1fe30f9f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 11:17:38 +0100 Subject: [PATCH 14/44] test(configuration): verify CLI source precedence --- Cargo.toml | 4 ++ tests/configuration/cli_configuration.rs | 52 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/configuration/cli_configuration.rs diff --git a/Cargo.toml b/Cargo.toml index ad171e379..cf633dbc5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,10 @@ path = "tests/banning/udp_shared_connection_id_error_limit_reverse_order.rs" name = "lifecycle-signals" path = "tests/lifecycle/signals.rs" +[[test]] +name = "cli-configuration" +path = "tests/configuration/cli_configuration.rs" + [lints] workspace = true diff --git a/tests/configuration/cli_configuration.rs b/tests/configuration/cli_configuration.rs new file mode 100644 index 000000000..934d094bf --- /dev/null +++ b/tests/configuration/cli_configuration.rs @@ -0,0 +1,52 @@ +//! Executable-boundary configuration contracts for the tracker CLI. + +#![cfg_attr(not(unix), allow(dead_code, unused_imports))] + +#[cfg(unix)] +#[path = "../common/native_tracker.rs"] +#[allow(dead_code)] +mod native_tracker; + +#[cfg(unix)] +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +#[cfg(unix)] +use native_tracker::{NativeTracker, NativeTrackerConfigurationSources}; + +#[cfg(unix)] +#[tokio::test] +async fn it_should_select_the_cli_configuration_when_both_child_environment_base_sources_are_set() { + // Arrange + let cli_port = 43152; + let environment_path_port = 43153; + let environment_toml_port = 43154; + let sources = NativeTrackerConfigurationSources::with_cli_health_check_port(cli_port) + .with_environment_path_health_check_port(environment_path_port) + .with_environment_toml_health_check_port(environment_toml_port); + let mut tracker = NativeTracker::start_with_configuration_sources(sources); + + // Act + tracker + .wait_until_ready() + .await + .expect("tracker should become ready using its CLI configuration"); + let health_check_address = tracker + .health_check_address() + .expect("ready tracker should expose its health-check address"); + let shutdown = tracker.gracefully_shutdown().await; + + // Assert + assert_eq!( + health_check_address, + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), cli_port) + ); + assert_ne!(health_check_address.port(), environment_path_port); + assert_ne!(health_check_address.port(), environment_toml_port); + shutdown.expect("tracker should gracefully shut down after the configuration scenario"); +} + +#[cfg(not(unix))] +#[test] +fn it_should_skip_native_cli_configuration_scenarios_on_non_unix_platforms() { + // The shared native fixture currently uses Unix process exit-status extensions. +} From a109f189fe7e51e12e3e6fb0b9537cd95253fa59 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 11:36:22 +0100 Subject: [PATCH 15/44] test(configuration): cover CLI override precedence --- .../ISSUE.md | 3 +- .../rust-executable-test-plan.md | 22 +++---- tests/common/native_tracker.rs | 59 +++++++++++++++++-- tests/configuration/cli_configuration.rs | 26 ++++++++ 4 files changed, 93 insertions(+), 17 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index bb97412f3..46d7f1052 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-08 09:35 +last-updated-utc: 2026-09-08 10:00 semantic-links: skill-links: - create-issue @@ -365,6 +365,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-08 09:00 UTC - Maintainer / GitHub Copilot - Refined the pending Rust test plan: move the reusable native child-process fixture from `tests/lifecycle/` to `tests/common/`; create `tests/configuration/cli_configuration.rs` for executable configuration contracts; retain `tests/lifecycle/signals.rs` for OS-signal contracts only. The shared fixture must offer narrowly configured child commands without duplicating process lifecycle ownership. - 2026-09-08 09:15 UTC - GitHub Copilot - Completed R1. Moved the native child-process fixture to `tests/common/native_tracker.rs` and updated `tests/lifecycle/signals.rs` to import it through an explicit path module declaration. The signal suite passed unchanged (8 tests). - 2026-09-08 09:35 UTC - GitHub Copilot - Created a provisional `cli-configuration` target and validated its shared-fixture import. Review found its sole scenario duplicated SIGTERM lifecycle coverage without asserting a configuration contract, so the uncommitted target was removed. R2 remains pending and must begin with a configuration-specific executable-boundary scenario. +- 2026-09-08 10:00 UTC - GitHub Copilot - Completed R2 and R3. Added the `cli-configuration` target with executable assertions that a CLI file wins over both child-only environment base sources and that a child-only per-value health-check override wins over the CLI file. The shared fixture owns the narrow child-only override configuration and continues to remove inherited source values by default. `cargo test --test cli-configuration` passed (7 tests); `cargo test --test lifecycle-signals` passed (9 tests); Rust formatting, Clippy, and diff checks passed. ## Acceptance Criteria diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index bf03bbca6..9206f2427 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -67,17 +67,17 @@ entrypoint forwarding contract and is covered by container-image CI. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| --- | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | -| R2 | TODO | Add configuration CLI test target | Add and register `tests/configuration/cli_configuration.rs` only with its first configuration-specific scenario. Reuse the fixture through an explicit path module declaration; do not duplicate child-process management or add lifecycle-only coverage. | -| R3 | TODO | Add CLI precedence process tests | Add separate AAA scenarios to `cli_configuration.rs` for CLI-only configuration, CLI plus both inherited base environment sources, and CLI plus a per-value override. Use separate valid config files with observable health-check ports; verify readiness and graceful shutdown. | -| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | -| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | -| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | -| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | -| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | -| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | +| ID | Status | Task | Expected result | +| --- | ------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | +| R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | +| R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | +| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | +| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | +| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | +| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | +| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | ## Scenario Contracts diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index 6d276b97a..6ecb2e458 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -65,8 +65,9 @@ pub struct NativeTracker { /// Base configuration sources supplied to one tracker child. /// /// The fixture writes all corresponding files into its temporary workspace. -/// Ports are the only configurable values because executable configuration -/// tests need no other child-process configuration surface. +/// Health-check ports and a child-only bind-address override are configurable +/// because executable configuration tests need no other child-process +/// configuration surface. #[derive(Clone, Copy)] // This shared module is compiled by signal-only and configuration test binaries; // the former does not use configuration-specific source builders. @@ -75,6 +76,7 @@ pub struct NativeTrackerConfigurationSources { cli: u16, environment_path: Option, environment_toml: Option, + health_check_api_bind_address_override: Option, } impl NativeTrackerConfigurationSources { @@ -84,6 +86,7 @@ impl NativeTrackerConfigurationSources { cli: port, environment_path: None, environment_toml: None, + health_check_api_bind_address_override: None, } } @@ -102,6 +105,14 @@ impl NativeTrackerConfigurationSources { self.environment_toml = Some(port); self } + + /// Adds a child-only health-check API bind-address override. + // See the type-level allowance: signal-only test binaries do not use it. + #[allow(dead_code)] + pub const fn with_health_check_api_bind_address_override(mut self, address: SocketAddr) -> Self { + self.health_check_api_bind_address_override = Some(address); + self + } } /// An isolated workspace and configuration for one tracker child process. @@ -111,6 +122,7 @@ struct NativeTrackerWorkspace { storage_path: PathBuf, environment_configuration_path: Option, environment_configuration_toml: Option, + health_check_api_bind_address_override: Option, } impl NativeTrackerWorkspace { @@ -135,6 +147,7 @@ impl NativeTrackerWorkspace { storage_path, environment_configuration_path, environment_configuration_toml, + health_check_api_bind_address_override: sources.health_check_api_bind_address_override, } } @@ -153,6 +166,10 @@ impl NativeTrackerWorkspace { fn environment_configuration_toml(&self) -> Option<&str> { self.environment_configuration_toml.as_deref() } + + const fn health_check_api_bind_address_override(&self) -> Option { + self.health_check_api_bind_address_override + } } /// Concurrently drains and retains a tracker child's output for readiness and diagnostics. @@ -256,6 +273,7 @@ impl NativeTracker { workspace.configuration_path(), workspace.environment_configuration_path(), workspace.environment_configuration_toml(), + workspace.health_check_api_bind_address_override(), ); let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); @@ -551,6 +569,7 @@ fn tracker_command( configuration_path: &std::path::Path, environment_configuration_path: Option<&std::path::Path>, environment_configuration_toml: Option<&str>, + health_check_api_bind_address_override: Option, ) -> Command { let mut command = Command::new(tracker_binary()); command @@ -558,6 +577,7 @@ fn tracker_command( .arg(configuration_path) .env_remove("TORRUST_TRACKER_CONFIG_TOML") .env_remove("TORRUST_TRACKER_CONFIG_TOML_PATH") + .env_remove("TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS") // `shutdown` reaps normal and expected-error paths. This kills a // panicking test's child so it cannot outlive its temporary workspace. .kill_on_drop(true) @@ -569,6 +589,12 @@ fn tracker_command( if let Some(toml) = environment_configuration_toml { command.env("TORRUST_TRACKER_CONFIG_TOML", toml); } + if let Some(address) = health_check_api_bind_address_override { + command.env( + "TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS", + address.to_string(), + ); + } command } @@ -580,7 +606,8 @@ fn tracker_binary() -> PathBuf { #[cfg(test)] mod tests { - use std::ffi::OsStr; + use std::ffi::{OsStr, OsString}; + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::Path; use super::{parse_health_check_address, tracker_command, write_configuration}; @@ -591,7 +618,7 @@ mod tests { let configuration_path = Path::new("/workspace/tracker.toml"); // Act - let command = tracker_command(configuration_path, None, None); + let command = tracker_command(configuration_path, None, None, None); let arguments = command.as_std().get_args().collect::>(); let environment = command.as_std().get_envs().collect::>(); @@ -600,7 +627,11 @@ mod tests { arguments, vec![OsStr::new("--config-toml-path"), configuration_path.as_os_str()] ); - for variable in ["TORRUST_TRACKER_CONFIG_TOML", "TORRUST_TRACKER_CONFIG_TOML_PATH"] { + for variable in [ + "TORRUST_TRACKER_CONFIG_TOML", + "TORRUST_TRACKER_CONFIG_TOML_PATH", + "TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS", + ] { assert!( environment .iter() @@ -610,6 +641,24 @@ mod tests { } } + #[test] + fn it_should_set_the_child_only_health_check_bind_address_override_after_removing_the_inherited_value() { + // Arrange + let configuration_path = Path::new("/workspace/tracker.toml"); + let override_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 43156); + let override_value = OsString::from(override_address.to_string()); + + // Act + let command = tracker_command(configuration_path, None, None, Some(override_address)); + let environment = command.as_std().get_envs().collect::>(); + + // Assert + assert!(environment.iter().any(|(name, value)| { + *name == OsStr::new("TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS") + && *value == Some(override_value.as_os_str()) + })); + } + #[test] fn it_should_write_a_port_zero_configuration_with_workspace_local_sqlite_storage() { // Arrange diff --git a/tests/configuration/cli_configuration.rs b/tests/configuration/cli_configuration.rs index 934d094bf..8a5eeeff4 100644 --- a/tests/configuration/cli_configuration.rs +++ b/tests/configuration/cli_configuration.rs @@ -45,6 +45,32 @@ async fn it_should_select_the_cli_configuration_when_both_child_environment_base shutdown.expect("tracker should gracefully shut down after the configuration scenario"); } +#[cfg(unix)] +#[tokio::test] +async fn it_should_select_the_child_only_health_check_bind_address_override_over_the_cli_configuration() { + // Arrange + let cli_port = 43155; + let override_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 43156); + let sources = NativeTrackerConfigurationSources::with_cli_health_check_port(cli_port) + .with_health_check_api_bind_address_override(override_address); + let mut tracker = NativeTracker::start_with_configuration_sources(sources); + + // Act + tracker + .wait_until_ready() + .await + .expect("tracker should become ready using its health-check bind-address override"); + let health_check_address = tracker + .health_check_address() + .expect("ready tracker should expose its health-check address"); + let shutdown = tracker.gracefully_shutdown().await; + + // Assert + assert_eq!(health_check_address, override_address); + assert_ne!(health_check_address.port(), cli_port); + shutdown.expect("tracker should gracefully shut down after the configuration scenario"); +} + #[cfg(not(unix))] #[test] fn it_should_skip_native_cli_configuration_scenarios_on_non_unix_platforms() { From 705f0035c48dcdba51a1c03458add7ebf0e47d58 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 12:08:11 +0100 Subject: [PATCH 16/44] test(configuration): group CLI source contracts --- .../ISSUE.md | 4 +- .../rust-executable-test-plan.md | 15 ++++- packages/configuration/src/lib.rs | 26 +++++++- tests/configuration/cli_configuration.rs | 64 ++----------------- .../base_source_precedence.rs | 36 +++++++++++ .../cli_configuration/per_value_overrides.rs | 33 ++++++++++ 6 files changed, 112 insertions(+), 66 deletions(-) create mode 100644 tests/configuration/cli_configuration/base_source_precedence.rs create mode 100644 tests/configuration/cli_configuration/per_value_overrides.rs diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 46d7f1052..5f83e6b85 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-08 10:00 +last-updated-utc: 2026-09-08 10:30 semantic-links: skill-links: - create-issue @@ -366,6 +366,8 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-08 09:15 UTC - GitHub Copilot - Completed R1. Moved the native child-process fixture to `tests/common/native_tracker.rs` and updated `tests/lifecycle/signals.rs` to import it through an explicit path module declaration. The signal suite passed unchanged (8 tests). - 2026-09-08 09:35 UTC - GitHub Copilot - Created a provisional `cli-configuration` target and validated its shared-fixture import. Review found its sole scenario duplicated SIGTERM lifecycle coverage without asserting a configuration contract, so the uncommitted target was removed. R2 remains pending and must begin with a configuration-specific executable-boundary scenario. - 2026-09-08 10:00 UTC - GitHub Copilot - Completed R2 and R3. Added the `cli-configuration` target with executable assertions that a CLI file wins over both child-only environment base sources and that a child-only per-value health-check override wins over the CLI file. The shared fixture owns the narrow child-only override configuration and continues to remove inherited source values by default. `cargo test --test cli-configuration` passed (7 tests); `cargo test --test lifecycle-signals` passed (9 tests); Rust formatting, Clippy, and diff checks passed. +- 2026-09-08 10:15 UTC - Maintainer / GitHub Copilot - Refined the R3 test bodies so Arrange defines source state, Act starts the child and waits for readiness, Teardown reaps it before assertions, and Assert compares a named observed endpoint with a named expected endpoint. Added R3a to prove at configuration-package level that ignored base sources are exclusive, not merely overridden on a conflicting key. +- 2026-09-08 10:30 UTC - Maintainer / GitHub Copilot - Grouped executable configuration scenarios by their contracts: `base_source_precedence` and `per_value_overrides`. The Cargo test entry point retains only target-level configuration and the shared-fixture import; `invalid_sources` remains the planned next module. ## Acceptance Criteria diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index 9206f2427..f1314ef2a 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -39,14 +39,20 @@ tests/ ├── common/ │ └── native_tracker.rs # Child process, workspace, output, readiness, cleanup ├── configuration/ -│ └── cli_configuration.rs # Tracker CLI source-selection process contracts +│ ├── cli_configuration.rs # Cargo test entry point and shared fixture import +│ └── cli_configuration/ +│ ├── base_source_precedence.rs # CLI versus environment base sources +│ ├── per_value_overrides.rs # Override versus CLI file +│ └── invalid_sources.rs # Planned CLI source failure contracts └── lifecycle/ - └── signals.rs # SIGINT, SIGTERM, and drop-path contracts + └── signals.rs # SIGINT, SIGTERM, and drop-path contracts ``` Each top-level test source remains a separate Cargo integration-test executable. Both `configuration/cli_configuration.rs` and `lifecycle/signals.rs` include the shared fixture through `#[path = "../common/native_tracker.rs"] mod native_tracker;`. +Within the configuration target, modules group scenarios by the configuration +contract they prove; the Cargo entry point owns the shared fixture import. | Behavior | Test layer | Location | Reason | | ----------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -72,6 +78,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | | R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | | R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | +| R3a | TODO | Prove base-source exclusivity | Add a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. Assert neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. | | R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | | R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | | R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | @@ -89,6 +96,10 @@ prove no listener, and the fixture's existing absolute deadlines. `TORRUST_TRACKER_CONFIG_TOML_PATH` only in the child `Command`; the health endpoint from the CLI file must become ready. Do not mutate test-process environment variables. +- **Base-source exclusivity:** test this separately at the configuration-package + layer, where loaded optional sections are directly observable. An + executable-boundary port assertion proves which selected source is live but + cannot alone distinguish exclusive source selection from a coincidental merge. - **Override:** child command sets a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS`; the override endpoint becomes ready, not the CLI file endpoint. diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 6ccd0f996..d5f424592 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -476,13 +476,25 @@ mod tests { #[test] #[allow(clippy::result_large_err)] - fn it_should_select_an_explicit_file_over_complete_toml_and_path_environment_sources() { + fn it_should_select_an_explicit_file_without_merging_complete_toml_or_path_environment_sources() { Jail::expect_with(|jail| { // Arrange jail.clear_env(); jail.create_file("explicit.toml", &configuration_with_health_check_port(41009))?; - jail.create_file("path.toml", &configuration_with_health_check_port(41010))?; - jail.set_env(ENV_VAR_CONFIG_TOML, configuration_with_health_check_port(41011)); + jail.create_file( + "path.toml", + &format!( + "{}\n[[udp_trackers]]\nbind_address = \"127.0.0.1:41010\"", + configuration_with_health_check_port(41010) + ), + )?; + jail.set_env( + ENV_VAR_CONFIG_TOML, + format!( + "{}\n[http_api]\nbind_address = \"127.0.0.1:41011\"", + configuration_with_health_check_port(41011) + ), + ); jail.set_env(ENV_VAR_CONFIG_TOML_PATH, "path.toml"); // Act @@ -491,6 +503,14 @@ mod tests { // Assert assert_eq!(configuration.health_check_api.bind_address, health_check_address(41009)); + assert!( + configuration.udp_trackers.is_none(), + "ignored environment path source must not be merged" + ); + assert!( + configuration.http_api.is_none(), + "ignored complete TOML environment source must not be merged" + ); Ok(()) }); diff --git a/tests/configuration/cli_configuration.rs b/tests/configuration/cli_configuration.rs index 8a5eeeff4..78a55aef4 100644 --- a/tests/configuration/cli_configuration.rs +++ b/tests/configuration/cli_configuration.rs @@ -8,68 +8,12 @@ mod native_tracker; #[cfg(unix)] -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +#[path = "cli_configuration/base_source_precedence.rs"] +mod base_source_precedence; #[cfg(unix)] -use native_tracker::{NativeTracker, NativeTrackerConfigurationSources}; - -#[cfg(unix)] -#[tokio::test] -async fn it_should_select_the_cli_configuration_when_both_child_environment_base_sources_are_set() { - // Arrange - let cli_port = 43152; - let environment_path_port = 43153; - let environment_toml_port = 43154; - let sources = NativeTrackerConfigurationSources::with_cli_health_check_port(cli_port) - .with_environment_path_health_check_port(environment_path_port) - .with_environment_toml_health_check_port(environment_toml_port); - let mut tracker = NativeTracker::start_with_configuration_sources(sources); - - // Act - tracker - .wait_until_ready() - .await - .expect("tracker should become ready using its CLI configuration"); - let health_check_address = tracker - .health_check_address() - .expect("ready tracker should expose its health-check address"); - let shutdown = tracker.gracefully_shutdown().await; - - // Assert - assert_eq!( - health_check_address, - SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), cli_port) - ); - assert_ne!(health_check_address.port(), environment_path_port); - assert_ne!(health_check_address.port(), environment_toml_port); - shutdown.expect("tracker should gracefully shut down after the configuration scenario"); -} - -#[cfg(unix)] -#[tokio::test] -async fn it_should_select_the_child_only_health_check_bind_address_override_over_the_cli_configuration() { - // Arrange - let cli_port = 43155; - let override_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 43156); - let sources = NativeTrackerConfigurationSources::with_cli_health_check_port(cli_port) - .with_health_check_api_bind_address_override(override_address); - let mut tracker = NativeTracker::start_with_configuration_sources(sources); - - // Act - tracker - .wait_until_ready() - .await - .expect("tracker should become ready using its health-check bind-address override"); - let health_check_address = tracker - .health_check_address() - .expect("ready tracker should expose its health-check address"); - let shutdown = tracker.gracefully_shutdown().await; - - // Assert - assert_eq!(health_check_address, override_address); - assert_ne!(health_check_address.port(), cli_port); - shutdown.expect("tracker should gracefully shut down after the configuration scenario"); -} +#[path = "cli_configuration/per_value_overrides.rs"] +mod per_value_overrides; #[cfg(not(unix))] #[test] diff --git a/tests/configuration/cli_configuration/base_source_precedence.rs b/tests/configuration/cli_configuration/base_source_precedence.rs new file mode 100644 index 000000000..4d0df15ac --- /dev/null +++ b/tests/configuration/cli_configuration/base_source_precedence.rs @@ -0,0 +1,36 @@ +//! Executable-boundary base-source precedence contracts for the tracker CLI. + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +use crate::native_tracker::{NativeTracker, NativeTrackerConfigurationSources}; + +#[tokio::test] +async fn it_should_select_the_cli_configuration_when_both_child_environment_base_sources_are_set() { + // Arrange + let cli_port = 43152; + let environment_path_port = 43153; + let environment_toml_port = 43154; + let expected_health_check_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), cli_port); + // All three base sources set `health_check_api.bind_address` differently. + // The CLI file must win over both environment base sources. + let sources = NativeTrackerConfigurationSources::with_cli_health_check_port(cli_port) + .with_environment_path_health_check_port(environment_path_port) + .with_environment_toml_health_check_port(environment_toml_port); + + // Act + let mut tracker = NativeTracker::start_with_configuration_sources(sources); + tracker + .wait_until_ready() + .await + .expect("tracker should become ready using its CLI configuration"); + let actual_health_check_address = tracker + .health_check_address() + .expect("ready tracker should expose its health-check address"); + + // Teardown + let shutdown = tracker.gracefully_shutdown().await; + + // Assert + assert_eq!(actual_health_check_address, expected_health_check_address); + shutdown.expect("tracker should gracefully shut down after the configuration scenario"); +} diff --git a/tests/configuration/cli_configuration/per_value_overrides.rs b/tests/configuration/cli_configuration/per_value_overrides.rs new file mode 100644 index 000000000..99d5ec742 --- /dev/null +++ b/tests/configuration/cli_configuration/per_value_overrides.rs @@ -0,0 +1,33 @@ +//! Executable-boundary per-value override contracts for the tracker CLI. + +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +use crate::native_tracker::{NativeTracker, NativeTrackerConfigurationSources}; + +#[tokio::test] +async fn it_should_select_the_health_check_bind_address_override_over_the_cli_configuration() { + // Arrange + let cli_port = 43155; + let expected_health_check_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 43156); + // The CLI file and per-value override both set `health_check_api.bind_address`. + // The override must win. + let sources = NativeTrackerConfigurationSources::with_cli_health_check_port(cli_port) + .with_health_check_api_bind_address_override(expected_health_check_address); + + // Act + let mut tracker = NativeTracker::start_with_configuration_sources(sources); + tracker + .wait_until_ready() + .await + .expect("tracker should become ready using its health-check bind-address override"); + let actual_health_check_address = tracker + .health_check_address() + .expect("ready tracker should expose its health-check address"); + + // Teardown + let shutdown = tracker.gracefully_shutdown().await; + + // Assert + assert_eq!(actual_health_check_address, expected_health_check_address); + shutdown.expect("tracker should gracefully shut down after the configuration scenario"); +} From 264693dbe77887126782f1e41022adc10a9b40f1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 12:47:47 +0100 Subject: [PATCH 17/44] test(configuration): cover invalid CLI sources --- .../ISSUE.md | 3 +- .../rust-executable-test-plan.md | 24 +- tests/common/native_tracker.rs | 297 +++++++++++++++++- tests/configuration/cli_configuration.rs | 4 + .../cli_configuration/invalid_sources.rs | 126 ++++++++ 5 files changed, 429 insertions(+), 25 deletions(-) create mode 100644 tests/configuration/cli_configuration/invalid_sources.rs diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 5f83e6b85..c5a04dcaa 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-08 10:30 +last-updated-utc: 2026-09-08 11:00 semantic-links: skill-links: - create-issue @@ -368,6 +368,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-08 10:00 UTC - GitHub Copilot - Completed R2 and R3. Added the `cli-configuration` target with executable assertions that a CLI file wins over both child-only environment base sources and that a child-only per-value health-check override wins over the CLI file. The shared fixture owns the narrow child-only override configuration and continues to remove inherited source values by default. `cargo test --test cli-configuration` passed (7 tests); `cargo test --test lifecycle-signals` passed (9 tests); Rust formatting, Clippy, and diff checks passed. - 2026-09-08 10:15 UTC - Maintainer / GitHub Copilot - Refined the R3 test bodies so Arrange defines source state, Act starts the child and waits for readiness, Teardown reaps it before assertions, and Assert compares a named observed endpoint with a named expected endpoint. Added R3a to prove at configuration-package level that ignored base sources are exclusive, not merely overridden on a conflicting key. - 2026-09-08 10:30 UTC - Maintainer / GitHub Copilot - Grouped executable configuration scenarios by their contracts: `base_source_precedence` and `per_value_overrides`. The Cargo test entry point retains only target-level configuration and the shared-fixture import; `invalid_sources` remains the planned next module. +- 2026-09-08 11:00 UTC - GitHub Copilot - Completed R3a and R4. R3a proves ignored base sources are not merged at the configuration-package layer. R4 adds compiled-child invalid-source contracts for parser errors and explicit-source failures, with fixture-owned workspaces, child-only environment isolation, deadline-bounded wait/reap/output handling, and post-reap candidate-port probes. Independent review initially found expected-failure drop and timeout cleanup gaps; they were corrected and the re-review approved the lifecycle. Focused configuration (129), CLI configuration (15), and lifecycle (10) tests passed. ## Acceptance Criteria diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index f1314ef2a..f6ecea679 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -73,18 +73,18 @@ entrypoint forwarding contract and is covered by container-image CI. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| --- | ------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | -| R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | -| R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | -| R3a | TODO | Prove base-source exclusivity | Add a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. Assert neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. | -| R4 | TODO | Add invalid-source process matrix | Add executable tests to `cli_configuration.rs` for absent option value, empty value, missing file, directory, malformed TOML, and parent-only relative file. Assert exit `2` for parser usage errors and exit `1` for source failures, path-bearing diagnostics where applicable, and no listener at a configured candidate port. | -| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | -| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | -| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | -| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | -| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | +| ID | Status | Task | Expected result | +| --- | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | +| R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | +| R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | +| R3a | DONE | Prove base-source exclusivity | Added a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. It asserts neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests). | +| R4 | DONE | Add invalid-source process matrix | Added `invalid_sources.rs` with compiled-child contracts for missing/empty option values, missing file, directory, malformed TOML, and parent-only relative path. They assert real exit codes and stable diagnostic fragments; malformed and parent-only sources prove their candidate ports are bindable after child reaping. The expected-failure fixture bounds waiting, forced reaping, and output draining. `cargo test --test cli-configuration` passed (15 tests). | +| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | +| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | +| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | +| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | ## Scenario Contracts diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index 6ecb2e458..6489578e7 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -22,6 +22,9 @@ use torrust_tracker_axum_health_check_api_server::resources::{Report, Status}; const STARTUP_DEADLINE: Duration = Duration::from_secs(10); const SHUTDOWN_DEADLINE: Duration = Duration::from_secs(30); +// The signal-only test binary imports this shared fixture but has no expected-failure scenarios. +#[allow(dead_code)] +const FAILURE_DEADLINE: Duration = Duration::from_secs(10); const RETRY_INTERVAL: Duration = Duration::from_millis(50); const HEALTH_CHECK_STARTUP_PREFIX: &str = "Started on: http://"; const HEALTH_CHECK_LOG_TARGET: &str = "HEALTH CHECK API"; @@ -62,6 +65,73 @@ pub struct NativeTracker { drop_cleanup_observer: Option>>, } +/// Invalid CLI configuration sources supported by the expected-failure fixture. +/// +/// This deliberately exposes configuration cases rather than raw commands so +/// executable tests cannot bypass the fixture's environment and cleanup rules. +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum NativeTrackerInvalidCliSource { + /// Omits the value following `--config-toml-path`. + MissingOptionValue, + /// Supplies an empty value for `--config-toml-path`. + EmptyOptionValue, + /// Supplies an absolute path that does not exist. + MissingFile, + /// Supplies a directory where a configuration file is required. + Directory, + /// Supplies malformed TOML which would otherwise configure this health port. + MalformedToml { candidate_health_port: u16 }, + /// Supplies `tracker.toml` from a child directory while it exists only in its parent. + ParentOnlyRelativeFile { candidate_health_port: u16 }, +} + +/// A fixture that owns a tracker process expected to fail before startup. +#[allow(dead_code)] +pub struct NativeTrackerExpectedFailure { + child: Option, + output: Option, + _workspace: tempfile::TempDir, + source_path: Option, + candidate_port: Option, +} + +/// Stable evidence retained after an expected-failure child has been reaped. +#[allow(dead_code)] +pub struct NativeTrackerFailure { + exit_code: i32, + output: String, + candidate_port: Option, +} + +#[allow(dead_code)] +impl NativeTrackerFailure { + /// Returns the process exit code captured after the child was reaped. + pub const fn exit_code(&self) -> i32 { + self.exit_code + } + + /// Returns the combined stdout and stderr captured while the child ran. + pub fn output(&self) -> &str { + &self.output + } + + /// Returns the configured port that was eligible for a post-reap bind probe. + pub const fn candidate_port(&self) -> Option { + self.candidate_port + } + + /// Proves a candidate health port is not left bound after child reaping. + pub fn assert_candidate_port_is_bindable(&self) -> Result<(), String> { + let port = self + .candidate_port + .ok_or_else(|| "this failure source has no candidate health port".to_owned())?; + std::net::TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, port)) + .map(drop) + .map_err(|error| format!("bind candidate health port {port} after child reaping: {error}")) + } +} + /// Base configuration sources supplied to one tracker child. /// /// The fixture writes all corresponding files into its temporary workspace. @@ -495,6 +565,114 @@ impl NativeTracker { } } +#[allow(dead_code)] +impl NativeTrackerExpectedFailure { + /// Spawns a child with a deliberately invalid CLI configuration source. + pub fn start(source: NativeTrackerInvalidCliSource) -> Self { + let workspace = tempfile::tempdir().expect("create temporary invalid-source workspace"); + let (mut command, source_path, candidate_port) = invalid_source_command(&workspace, source); + let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); + let stdout = child.stdout.take().expect("tracker child stdout is piped"); + let stderr = child.stderr.take().expect("tracker child stderr is piped"); + + Self { + child: Some(child), + output: Some(TrackerOutputCapture::new(stdout, stderr)), + _workspace: workspace, + source_path, + candidate_port, + } + } + + /// Returns the fixture-owned source path when the case has one. + pub fn source_path(&self) -> Option { + self.source_path.clone() + } + + /// Waits for the expected startup failure and reaps the child in the normal path. + /// + /// The initial wait, forced reaping, and output-reader completion are all + /// deadline-bounded. On a timeout, this method force-kills and attempts to + /// reap the child before returning diagnostics. `Drop` is only a best-effort + /// fallback when an active Tokio runtime exists; it cannot guarantee async + /// reaping. + pub async fn wait(mut self) -> Result { + let mut child = self.child.take().expect("invalid-source tracker child must be available"); + let mut output_capture = self.output.take().expect("invalid-source output capture must be available"); + let status = match tokio::time::timeout(FAILURE_DEADLINE, child.wait()).await { + Ok(Ok(status)) => Ok(status), + Ok(Err(error)) => Err(format!("wait for invalid-source tracker child: {error}")), + Err(_) => Self::kill_and_reap_after_timeout(&mut child).await, + }; + let reader_result = Self::wait_for_output_readers(&mut output_capture).await; + let output = output_capture.contents().await; + let status = status.map_err(|message| format!("{message}\ntracker output:\n{output}"))?; + reader_result.map_err(|message| format!("{message}\ntracker output:\n{output}"))?; + let exit_code = status + .code() + .ok_or_else(|| format!("invalid-source tracker child exited without a code: {status}\ntracker output:\n{output}"))?; + + Ok(NativeTrackerFailure { + exit_code, + output, + candidate_port: self.candidate_port, + }) + } + + async fn kill_and_reap_after_timeout(child: &mut Child) -> Result { + let kill_result = child.start_kill(); + + match tokio::time::timeout(FAILURE_DEADLINE, child.wait()).await { + Ok(Ok(status)) => match kill_result { + Ok(()) => Err(format!( + "invalid-source tracker child did not exit within {FAILURE_DEADLINE:?}; force-killed and reaped with {status}" + )), + Err(error) => Err(format!( + "invalid-source tracker child did not exit within {FAILURE_DEADLINE:?}; force-kill failed: {error}; reaped with {status}" + )), + }, + Ok(Err(error)) => Err(format!("reap force-killed invalid-source tracker child: {error}")), + Err(_) => match kill_result { + Ok(()) => Err(format!( + "invalid-source tracker child did not exit within {FAILURE_DEADLINE:?}, and did not reap within an additional {FAILURE_DEADLINE:?} after force-kill" + )), + Err(error) => Err(format!( + "invalid-source tracker child did not exit within {FAILURE_DEADLINE:?}; force-kill failed: {error}; and it did not reap within an additional {FAILURE_DEADLINE:?}" + )), + }, + } + } + + async fn wait_for_output_readers(output_capture: &mut TrackerOutputCapture) -> Result<(), String> { + tokio::time::timeout(FAILURE_DEADLINE, output_capture.wait_for_readers()) + .await + .map_err(|_| format!("timed out waiting {FAILURE_DEADLINE:?} for invalid-source tracker output readers")) + } +} + +#[allow(dead_code)] +impl Drop for NativeTrackerExpectedFailure { + fn drop(&mut self) { + let Some(mut child) = self.child.take() else { + return; + }; + let output = self.output.take(); + + // Drop cannot await cleanup. Do not panic while unwinding or outside a + // Tokio runtime; `kill_on_drop(true)` remains the fallback termination policy. + let Ok(runtime) = tokio::runtime::Handle::try_current() else { + return; + }; + drop(runtime.spawn(async move { + drop(child.start_kill()); + drop(child.wait().await); + if let Some(mut output) = output { + output.wait_for_readers().await; + } + })); + } +} + impl Drop for NativeTracker { fn drop(&mut self) { let Some(mut child) = self.child.take() else { @@ -572,17 +750,8 @@ fn tracker_command( health_check_api_bind_address_override: Option, ) -> Command { let mut command = Command::new(tracker_binary()); - command - .arg("--config-toml-path") - .arg(configuration_path) - .env_remove("TORRUST_TRACKER_CONFIG_TOML") - .env_remove("TORRUST_TRACKER_CONFIG_TOML_PATH") - .env_remove("TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS") - // `shutdown` reaps normal and expected-error paths. This kills a - // panicking test's child so it cannot outlive its temporary workspace. - .kill_on_drop(true) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + configure_tracker_command(&mut command); + command.arg("--config-toml-path").arg(configuration_path); if let Some(path) = environment_configuration_path { command.env("TORRUST_TRACKER_CONFIG_TOML_PATH", path); } @@ -598,6 +767,83 @@ fn tracker_command( command } +/// Applies the mandatory child isolation and output capture policy. +fn configure_tracker_command(command: &mut Command) { + command + .env_remove("TORRUST_TRACKER_CONFIG_TOML") + .env_remove("TORRUST_TRACKER_CONFIG_TOML_PATH") + .env_remove("TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS") + // Normal fixture waits reap children. In a no-runtime drop path this + // remains best-effort termination, not guaranteed asynchronous reaping. + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); +} + +#[allow(dead_code)] +fn invalid_source_command( + workspace: &tempfile::TempDir, + source: NativeTrackerInvalidCliSource, +) -> (Command, Option, Option) { + let mut command = Command::new(tracker_binary()); + configure_tracker_command(&mut command); + + match source { + NativeTrackerInvalidCliSource::MissingOptionValue => { + command.arg("--config-toml-path"); + (command, None, None) + } + NativeTrackerInvalidCliSource::EmptyOptionValue => { + command.arg("--config-toml-path").arg(""); + (command, None, None) + } + NativeTrackerInvalidCliSource::MissingFile => { + let path = workspace.path().join("does-not-exist.toml"); + command.arg("--config-toml-path").arg(&path); + (command, Some(path), None) + } + NativeTrackerInvalidCliSource::Directory => { + let path = workspace.path().to_path_buf(); + command.arg("--config-toml-path").arg(&path); + (command, Some(path), None) + } + NativeTrackerInvalidCliSource::MalformedToml { candidate_health_port } => { + let (path, _) = write_configuration(workspace, "malformed", candidate_health_port); + std::fs::write( + &path, + format!( + "{}\nmalformed_key = [", + std::fs::read_to_string(&path).expect("read configuration") + ), + ) + .expect("write malformed tracker configuration"); + command.arg("--config-toml-path").arg(&path); + (command, Some(path), Some(candidate_health_port)) + } + NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { candidate_health_port } => { + let parent = workspace.path().join("parent"); + let child = parent.join("child"); + std::fs::create_dir_all(&child).expect("create child working directory"); + let (parent_configuration, _) = write_configuration_in_directory(&parent, candidate_health_port); + assert_eq!(parent_configuration.file_name(), Some(std::ffi::OsStr::new("tracker.toml"))); + command.current_dir(child).arg("--config-toml-path").arg("tracker.toml"); + (command, Some(parent_configuration), Some(candidate_health_port)) + } + } +} + +#[allow(dead_code)] +fn write_configuration_in_directory(directory: &std::path::Path, health_check_port: u16) -> (PathBuf, PathBuf) { + let storage_path = directory.join("storage"); + std::fs::create_dir_all(&storage_path).expect("create tracker storage directory"); + let config_path = directory.join("tracker.toml"); + let config = CONFIGURATION + .replace("{STORAGE_PATH}", &storage_path.to_string_lossy()) + .replace("{HEALTH_CHECK_PORT}", &health_check_port.to_string()); + std::fs::write(&config_path, config).expect("write tracker configuration"); + (config_path, storage_path) +} + fn tracker_binary() -> PathBuf { std::env::var_os("NEXTEST_BIN_EXE_torrust-tracker") .or_else(|| std::env::var_os("CARGO_BIN_EXE_torrust-tracker")) @@ -610,7 +856,9 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::Path; - use super::{parse_health_check_address, tracker_command, write_configuration}; + use super::{ + NativeTrackerInvalidCliSource, invalid_source_command, parse_health_check_address, tracker_command, write_configuration, + }; #[test] fn it_should_select_its_configuration_with_the_cli_and_remove_legacy_base_source_variables() { @@ -704,6 +952,31 @@ mod tests { ); } + #[test] + fn it_should_construct_a_parent_only_relative_source_from_the_child_working_directory() { + // Arrange + let workspace = tempfile::tempdir().expect("create temporary tracker workspace"); + let candidate_port = 43157; + + // Act + let (command, source_path, observed_port) = invalid_source_command( + &workspace, + NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { + candidate_health_port: candidate_port, + }, + ); + + // Assert + let source_path = source_path.expect("parent-only source should retain its parent file path"); + assert_eq!(source_path.file_name(), Some(OsStr::new("tracker.toml"))); + assert_eq!( + source_path.parent().map(|parent| parent.join("child")).as_deref(), + command.as_std().get_current_dir() + ); + assert_eq!(command.as_std().get_args().last(), Some(OsStr::new("tracker.toml"))); + assert_eq!(observed_port, Some(candidate_port)); + } + #[test] fn it_should_reject_non_health_check_startup_logs() { // Arrange diff --git a/tests/configuration/cli_configuration.rs b/tests/configuration/cli_configuration.rs index 78a55aef4..92baae12e 100644 --- a/tests/configuration/cli_configuration.rs +++ b/tests/configuration/cli_configuration.rs @@ -15,6 +15,10 @@ mod base_source_precedence; #[path = "cli_configuration/per_value_overrides.rs"] mod per_value_overrides; +#[cfg(unix)] +#[path = "cli_configuration/invalid_sources.rs"] +mod invalid_sources; + #[cfg(not(unix))] #[test] fn it_should_skip_native_cli_configuration_scenarios_on_non_unix_platforms() { diff --git a/tests/configuration/cli_configuration/invalid_sources.rs b/tests/configuration/cli_configuration/invalid_sources.rs new file mode 100644 index 000000000..e0e726ebf --- /dev/null +++ b/tests/configuration/cli_configuration/invalid_sources.rs @@ -0,0 +1,126 @@ +//! Executable-boundary invalid CLI configuration-source contracts. + +use crate::native_tracker::{NativeTrackerExpectedFailure, NativeTrackerInvalidCliSource}; + +#[tokio::test] +async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_missing() { + // Arrange + let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MissingOptionValue); + + // Act + let failure = fixture.wait().await.expect("tracker should exit for a missing option value"); + + // Assert + assert_eq!(failure.exit_code(), 2); + assert!(failure.output().contains("a value is required")); +} + +#[tokio::test] +async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_empty() { + // Arrange + let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::EmptyOptionValue); + + // Act + let failure = fixture.wait().await.expect("tracker should exit for an empty option value"); + + // Assert + assert_eq!(failure.exit_code(), 2); + assert!(failure.output().contains("must not be empty")); +} + +#[tokio::test] +async fn it_should_exit_without_starting_when_the_cli_configuration_file_is_missing() { + // Arrange + let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MissingFile); + let expected_path = fixture + .source_path() + .expect("missing-file fixture should expose its source path") + .to_string_lossy() + .into_owned(); + + // Act + let failure = fixture.wait().await.expect("tracker should exit for a missing file"); + + // Assert + assert_eq!(failure.exit_code(), 1); + assert!(failure.output().contains("Unable to load explicit configuration file")); + assert!(failure.output().contains(&expected_path)); +} + +#[tokio::test] +async fn it_should_exit_without_starting_when_the_cli_configuration_source_is_a_directory() { + // Arrange + let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::Directory); + let expected_path = fixture + .source_path() + .expect("directory fixture should expose its source path") + .to_string_lossy() + .into_owned(); + + // Act + let failure = fixture.wait().await.expect("tracker should exit for a directory source"); + + // Assert + assert_eq!(failure.exit_code(), 1); + assert!(failure.output().contains("Unable to load explicit configuration file")); + assert!(failure.output().contains(&expected_path)); +} + +#[tokio::test] +async fn it_should_exit_without_starting_when_the_cli_configuration_toml_is_malformed() { + // Arrange + let candidate_health_port = 43158; + let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MalformedToml { candidate_health_port }); + let expected_path = fixture + .source_path() + .expect("malformed-TOML fixture should expose its source path") + .to_string_lossy() + .into_owned(); + + // Act + let failure = fixture.wait().await.expect("tracker should exit for malformed TOML"); + + // Assert + assert_eq!(failure.exit_code(), 1); + assert!(failure.output().contains("Unable to process explicit configuration file")); + assert!(failure.output().contains(&expected_path)); + assert_eq!(failure.candidate_port(), Some(candidate_health_port)); + failure + .assert_candidate_port_is_bindable() + .expect("malformed configuration must not leave its candidate health port bound"); +} + +#[tokio::test] +async fn it_should_not_search_parent_directories_for_a_relative_cli_configuration_file() { + // Arrange + let candidate_health_port = 43157; + let fixture = + NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { candidate_health_port }); + + // Act + let failure = fixture + .wait() + .await + .expect("tracker should exit rather than load the parent configuration file"); + + // Assert + assert_eq!(failure.exit_code(), 1); + assert!(failure.output().contains("Unable to load explicit configuration file")); + assert!(failure.output().contains("tracker.toml")); + assert_eq!(failure.candidate_port(), Some(candidate_health_port)); + failure + .assert_candidate_port_is_bindable() + .expect("parent-only configuration must not leave its candidate health port bound"); +} + +#[tokio::test] +async fn it_should_not_panic_when_an_expected_failure_fixture_is_dropped_without_a_tokio_runtime() { + // Arrange + let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MissingFile); + + // Act + let result = std::thread::spawn(move || drop(fixture)).join(); + + // Assert + assert!(result.is_ok(), "dropping the fixture outside Tokio must not panic"); +} From 8244a68c0fb29fc9daabdf3b175b31ee3a1fe9f8 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 12:53:12 +0100 Subject: [PATCH 18/44] refactor(test): clarify failed tracker startup --- tests/common/native_tracker.rs | 26 ++++---- .../cli_configuration/invalid_sources.rs | 59 ++++++++++++------- 2 files changed, 50 insertions(+), 35 deletions(-) diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index 6489578e7..d617c865f 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -22,7 +22,7 @@ use torrust_tracker_axum_health_check_api_server::resources::{Report, Status}; const STARTUP_DEADLINE: Duration = Duration::from_secs(10); const SHUTDOWN_DEADLINE: Duration = Duration::from_secs(30); -// The signal-only test binary imports this shared fixture but has no expected-failure scenarios. +// The signal-only test binary imports this shared fixture but has no failed-start scenarios. #[allow(dead_code)] const FAILURE_DEADLINE: Duration = Duration::from_secs(10); const RETRY_INTERVAL: Duration = Duration::from_millis(50); @@ -65,7 +65,7 @@ pub struct NativeTracker { drop_cleanup_observer: Option>>, } -/// Invalid CLI configuration sources supported by the expected-failure fixture. +/// Invalid CLI configuration sources supported by the failed-start fixture. /// /// This deliberately exposes configuration cases rather than raw commands so /// executable tests cannot bypass the fixture's environment and cleanup rules. @@ -86,9 +86,9 @@ pub enum NativeTrackerInvalidCliSource { ParentOnlyRelativeFile { candidate_health_port: u16 }, } -/// A fixture that owns a tracker process expected to fail before startup. +/// A tracker child process expected to fail before completing startup. #[allow(dead_code)] -pub struct NativeTrackerExpectedFailure { +pub struct NativeTrackerFailedStart { child: Option, output: Option, _workspace: tempfile::TempDir, @@ -96,16 +96,16 @@ pub struct NativeTrackerExpectedFailure { candidate_port: Option, } -/// Stable evidence retained after an expected-failure child has been reaped. +/// Stable evidence retained after a failed-start child has been reaped. #[allow(dead_code)] -pub struct NativeTrackerFailure { +pub struct NativeTrackerFailedStartResult { exit_code: i32, output: String, candidate_port: Option, } #[allow(dead_code)] -impl NativeTrackerFailure { +impl NativeTrackerFailedStartResult { /// Returns the process exit code captured after the child was reaped. pub const fn exit_code(&self) -> i32 { self.exit_code @@ -566,9 +566,9 @@ impl NativeTracker { } #[allow(dead_code)] -impl NativeTrackerExpectedFailure { +impl NativeTrackerFailedStart { /// Spawns a child with a deliberately invalid CLI configuration source. - pub fn start(source: NativeTrackerInvalidCliSource) -> Self { + pub fn spawn(source: NativeTrackerInvalidCliSource) -> Self { let workspace = tempfile::tempdir().expect("create temporary invalid-source workspace"); let (mut command, source_path, candidate_port) = invalid_source_command(&workspace, source); let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); @@ -589,14 +589,14 @@ impl NativeTrackerExpectedFailure { self.source_path.clone() } - /// Waits for the expected startup failure and reaps the child in the normal path. + /// Waits for the child process to exit and reaps it in the normal path. /// /// The initial wait, forced reaping, and output-reader completion are all /// deadline-bounded. On a timeout, this method force-kills and attempts to /// reap the child before returning diagnostics. `Drop` is only a best-effort /// fallback when an active Tokio runtime exists; it cannot guarantee async /// reaping. - pub async fn wait(mut self) -> Result { + pub async fn wait_for_exit(mut self) -> Result { let mut child = self.child.take().expect("invalid-source tracker child must be available"); let mut output_capture = self.output.take().expect("invalid-source output capture must be available"); let status = match tokio::time::timeout(FAILURE_DEADLINE, child.wait()).await { @@ -612,7 +612,7 @@ impl NativeTrackerExpectedFailure { .code() .ok_or_else(|| format!("invalid-source tracker child exited without a code: {status}\ntracker output:\n{output}"))?; - Ok(NativeTrackerFailure { + Ok(NativeTrackerFailedStartResult { exit_code, output, candidate_port: self.candidate_port, @@ -651,7 +651,7 @@ impl NativeTrackerExpectedFailure { } #[allow(dead_code)] -impl Drop for NativeTrackerExpectedFailure { +impl Drop for NativeTrackerFailedStart { fn drop(&mut self) { let Some(mut child) = self.child.take() else { return; diff --git a/tests/configuration/cli_configuration/invalid_sources.rs b/tests/configuration/cli_configuration/invalid_sources.rs index e0e726ebf..811c9c021 100644 --- a/tests/configuration/cli_configuration/invalid_sources.rs +++ b/tests/configuration/cli_configuration/invalid_sources.rs @@ -1,14 +1,17 @@ //! Executable-boundary invalid CLI configuration-source contracts. -use crate::native_tracker::{NativeTrackerExpectedFailure, NativeTrackerInvalidCliSource}; +use crate::native_tracker::{NativeTrackerFailedStart, NativeTrackerInvalidCliSource}; #[tokio::test] async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_missing() { // Arrange - let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MissingOptionValue); + let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MissingOptionValue); // Act - let failure = fixture.wait().await.expect("tracker should exit for a missing option value"); + let failure = failed_start + .wait_for_exit() + .await + .expect("tracker should exit for a missing option value"); // Assert assert_eq!(failure.exit_code(), 2); @@ -18,10 +21,13 @@ async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_mi #[tokio::test] async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_empty() { // Arrange - let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::EmptyOptionValue); + let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::EmptyOptionValue); // Act - let failure = fixture.wait().await.expect("tracker should exit for an empty option value"); + let failure = failed_start + .wait_for_exit() + .await + .expect("tracker should exit for an empty option value"); // Assert assert_eq!(failure.exit_code(), 2); @@ -31,15 +37,18 @@ async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_em #[tokio::test] async fn it_should_exit_without_starting_when_the_cli_configuration_file_is_missing() { // Arrange - let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MissingFile); - let expected_path = fixture + let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MissingFile); + let expected_path = failed_start .source_path() .expect("missing-file fixture should expose its source path") .to_string_lossy() .into_owned(); // Act - let failure = fixture.wait().await.expect("tracker should exit for a missing file"); + let failure = failed_start + .wait_for_exit() + .await + .expect("tracker should exit for a missing file"); // Assert assert_eq!(failure.exit_code(), 1); @@ -50,15 +59,18 @@ async fn it_should_exit_without_starting_when_the_cli_configuration_file_is_miss #[tokio::test] async fn it_should_exit_without_starting_when_the_cli_configuration_source_is_a_directory() { // Arrange - let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::Directory); - let expected_path = fixture + let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::Directory); + let expected_path = failed_start .source_path() .expect("directory fixture should expose its source path") .to_string_lossy() .into_owned(); // Act - let failure = fixture.wait().await.expect("tracker should exit for a directory source"); + let failure = failed_start + .wait_for_exit() + .await + .expect("tracker should exit for a directory source"); // Assert assert_eq!(failure.exit_code(), 1); @@ -70,15 +82,18 @@ async fn it_should_exit_without_starting_when_the_cli_configuration_source_is_a_ async fn it_should_exit_without_starting_when_the_cli_configuration_toml_is_malformed() { // Arrange let candidate_health_port = 43158; - let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MalformedToml { candidate_health_port }); - let expected_path = fixture + let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MalformedToml { candidate_health_port }); + let expected_path = failed_start .source_path() .expect("malformed-TOML fixture should expose its source path") .to_string_lossy() .into_owned(); // Act - let failure = fixture.wait().await.expect("tracker should exit for malformed TOML"); + let failure = failed_start + .wait_for_exit() + .await + .expect("tracker should exit for malformed TOML"); // Assert assert_eq!(failure.exit_code(), 1); @@ -94,12 +109,12 @@ async fn it_should_exit_without_starting_when_the_cli_configuration_toml_is_malf async fn it_should_not_search_parent_directories_for_a_relative_cli_configuration_file() { // Arrange let candidate_health_port = 43157; - let fixture = - NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { candidate_health_port }); + let failed_start = + NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { candidate_health_port }); // Act - let failure = fixture - .wait() + let failure = failed_start + .wait_for_exit() .await .expect("tracker should exit rather than load the parent configuration file"); @@ -114,13 +129,13 @@ async fn it_should_not_search_parent_directories_for_a_relative_cli_configuratio } #[tokio::test] -async fn it_should_not_panic_when_an_expected_failure_fixture_is_dropped_without_a_tokio_runtime() { +async fn it_should_not_panic_when_a_failed_start_is_dropped_without_a_tokio_runtime() { // Arrange - let fixture = NativeTrackerExpectedFailure::start(NativeTrackerInvalidCliSource::MissingFile); + let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MissingFile); // Act - let result = std::thread::spawn(move || drop(fixture)).join(); + let result = std::thread::spawn(move || drop(failed_start)).join(); // Assert - assert!(result.is_ok(), "dropping the fixture outside Tokio must not panic"); + assert!(result.is_ok(), "dropping a failed start outside Tokio must not panic"); } From 2583253faf39a45f65100019c4ec4a25382fb017 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 14:33:44 +0100 Subject: [PATCH 19/44] test(configuration): cover unreadable CLI source --- .../ISSUE.md | 3 +- .../rust-executable-test-plan.md | 24 +-- tests/common/native_tracker.rs | 168 +++++++++++++++++- .../cli_configuration/invalid_sources.rs | 60 ++++++- 4 files changed, 234 insertions(+), 21 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index c5a04dcaa..7851f08b8 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -8,7 +8,7 @@ github-issue: 2151 spec-path: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md branch: "2151-add-tracker-config-path-argument" related-pr: 2153 -last-updated-utc: 2026-09-08 11:00 +last-updated-utc: 2026-09-08 12:00 semantic-links: skill-links: - create-issue @@ -369,6 +369,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-08 10:15 UTC - Maintainer / GitHub Copilot - Refined the R3 test bodies so Arrange defines source state, Act starts the child and waits for readiness, Teardown reaps it before assertions, and Assert compares a named observed endpoint with a named expected endpoint. Added R3a to prove at configuration-package level that ignored base sources are exclusive, not merely overridden on a conflicting key. - 2026-09-08 10:30 UTC - Maintainer / GitHub Copilot - Grouped executable configuration scenarios by their contracts: `base_source_precedence` and `per_value_overrides`. The Cargo test entry point retains only target-level configuration and the shared-fixture import; `invalid_sources` remains the planned next module. - 2026-09-08 11:00 UTC - GitHub Copilot - Completed R3a and R4. R3a proves ignored base sources are not merged at the configuration-package layer. R4 adds compiled-child invalid-source contracts for parser errors and explicit-source failures, with fixture-owned workspaces, child-only environment isolation, deadline-bounded wait/reap/output handling, and post-reap candidate-port probes. Independent review initially found expected-failure drop and timeout cleanup gaps; they were corrected and the re-review approved the lifecycle. Focused configuration (129), CLI configuration (15), and lifecycle (10) tests passed. +- 2026-09-08 12:00 UTC - GitHub Copilot - Completed R5. Added the Unix unreadable regular-file executable contract. The fixture creates a valid mode-`000` source and probes effective permission enforcement before spawning: normal users exercise a path-bearing `Permission denied` exit, while privileged runners explicitly skip without starting a child. Independent review found restoration could panic or be lost before workspace cleanup; restoration is now fallible in normal cleanup, non-panicking and synchronous in drop cleanup, and covered by regressions. CLI configuration (17) and lifecycle (11) tests passed; re-review approved the cleanup design. ## Acceptance Criteria diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index f6ecea679..8c265378d 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -73,18 +73,18 @@ entrypoint forwarding contract and is covered by container-image CI. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| --- | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | -| R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | -| R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | -| R3a | DONE | Prove base-source exclusivity | Added a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. It asserts neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests). | -| R4 | DONE | Add invalid-source process matrix | Added `invalid_sources.rs` with compiled-child contracts for missing/empty option values, missing file, directory, malformed TOML, and parent-only relative path. They assert real exit codes and stable diagnostic fragments; malformed and parent-only sources prove their candidate ports are bindable after child reaping. The expected-failure fixture bounds waiting, forced reaping, and output draining. `cargo test --test cli-configuration` passed (15 tests). | -| R5 | TODO | Add Unix unreadable-file process test | Create a regular `mode 000` file, attempt a child start as the current user, and assert the permission error only when the platform enforces it. If a privileged runner can read it, explicitly skip with documented rationale rather than asserting a false failure. Restore file permissions during cleanup. | -| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | -| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | -| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | -| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | +| ID | Status | Task | Expected result | +| --- | ------ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | +| R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | +| R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | +| R3a | DONE | Prove base-source exclusivity | Added a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. It asserts neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests). | +| R4 | DONE | Add invalid-source process matrix | Added `invalid_sources.rs` with compiled-child contracts for missing/empty option values, missing file, directory, malformed TOML, and parent-only relative path. They assert real exit codes and stable diagnostic fragments; malformed and parent-only sources prove their candidate ports are bindable after child reaping. The expected-failure fixture bounds waiting, forced reaping, and output draining. `cargo test --test cli-configuration` passed (15 tests). | +| R5 | DONE | Add Unix unreadable-file process test | Added a fixture-owned valid mode-`000` regular file scenario. When permissions are enforced, the compiled child exits `1` with a path-bearing permission diagnostic and leaves its candidate port bindable after reaping; privileged runners report an explicit skip without spawning a child. Permission restoration is fallible in normal cleanup, non-panicking in drop cleanup, and verified after `wait_for_exit`. `cargo test --test cli-configuration` passed (17 tests). | +| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | +| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | +| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | ## Scenario Contracts diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index d617c865f..069d91ad7 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -6,6 +6,7 @@ //! even when graceful shutdown exceeds the scenario deadline. use std::net::SocketAddr; +use std::os::unix::fs::PermissionsExt as _; use std::os::unix::process::ExitStatusExt; use std::path::PathBuf; use std::process::Stdio; @@ -86,22 +87,66 @@ pub enum NativeTrackerInvalidCliSource { ParentOnlyRelativeFile { candidate_health_port: u16 }, } +/// Result of preparing an unreadable regular-file CLI source. +/// +/// Permission bits are not enforced for privileged users or processes with +/// filesystem capabilities. The fixture probes that platform behavior before +/// spawning so the executable contract is only asserted when meaningful. +#[allow(dead_code)] +pub enum NativeTrackerUnreadableCliSource { + /// The operating system denied a read and the child was spawned to prove its failure contract. + Enforced(Box), + /// The current process can read mode-`000` files, so no child was spawned. + NotEnforced { reason: String }, +} + /// A tracker child process expected to fail before completing startup. #[allow(dead_code)] pub struct NativeTrackerFailedStart { child: Option, output: Option, - _workspace: tempfile::TempDir, + // This must be dropped before the fixture workspace, so its file remains + // present if explicit restoration was not possible. + permission_restore: Option, + workspace: Option, source_path: Option, candidate_port: Option, } +/// Restores the original Unix mode of a fixture-owned configuration file. +struct NativeTrackerPermissionRestore { + path: PathBuf, + mode: Option, +} + +impl NativeTrackerPermissionRestore { + fn restore(&mut self) -> std::io::Result<()> { + let Some(mode) = self.mode else { + return Ok(()); + }; + std::fs::set_permissions(&self.path, std::fs::Permissions::from_mode(mode))?; + self.mode = None; + Ok(()) + } +} + +impl Drop for NativeTrackerPermissionRestore { + fn drop(&mut self) { + // Drop can run while another panic is unwinding and must never replace + // that panic. A normal wait reports this error with child diagnostics. + drop(self.restore()); + } +} + /// Stable evidence retained after a failed-start child has been reaped. #[allow(dead_code)] pub struct NativeTrackerFailedStartResult { exit_code: i32, output: String, candidate_port: Option, + source_path: Option, + source_mode: Option, + _workspace: Option, } #[allow(dead_code)] @@ -121,6 +166,16 @@ impl NativeTrackerFailedStartResult { self.candidate_port } + /// Returns the fixture-owned source path while this result is retained. + pub fn source_path(&self) -> Option<&std::path::Path> { + self.source_path.as_deref() + } + + /// Returns the original source permissions when the fixture changed them. + pub const fn source_mode(&self) -> Option { + self.source_mode + } + /// Proves a candidate health port is not left bound after child reaping. pub fn assert_candidate_port_is_bindable(&self) -> Result<(), String> { let port = self @@ -578,12 +633,58 @@ impl NativeTrackerFailedStart { Self { child: Some(child), output: Some(TrackerOutputCapture::new(stdout, stderr)), - _workspace: workspace, + permission_restore: None, + workspace: Some(workspace), source_path, candidate_port, } } + /// Prepares a valid regular configuration file that is unreadable by normal Unix permission checks. + pub fn spawn_with_unreadable_regular_file(candidate_health_port: u16) -> NativeTrackerUnreadableCliSource { + let workspace = tempfile::tempdir().expect("create temporary unreadable-source workspace"); + let (source_path, _) = write_configuration(&workspace, "unreadable", candidate_health_port); + let original_mode = std::fs::metadata(&source_path) + .expect("read fixture-owned configuration file metadata") + .permissions() + .mode(); + std::fs::set_permissions(&source_path, std::fs::Permissions::from_mode(0o000)) + .expect("make fixture-owned configuration file unreadable"); + // Create the guard immediately after changing permissions. Every + // subsequent early return or panic, including command setup failure, + // restores the fixture-owned file synchronously. + let permission_restore = NativeTrackerPermissionRestore { + path: source_path.clone(), + mode: Some(original_mode), + }; + + match std::fs::read_to_string(&source_path) { + Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => { + let mut command = tracker_command(&source_path, None, None, None); + let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); + let stdout = child.stdout.take().expect("tracker child stdout is piped"); + let stderr = child.stderr.take().expect("tracker child stderr is piped"); + + NativeTrackerUnreadableCliSource::Enforced(Box::new(Self { + child: Some(child), + output: Some(TrackerOutputCapture::new(stdout, stderr)), + permission_restore: Some(permission_restore), + workspace: Some(workspace), + source_path: Some(source_path), + candidate_port: Some(candidate_health_port), + })) + } + Ok(_) => { + NativeTrackerUnreadableCliSource::NotEnforced { + reason: "the current process can read a mode-000 regular file (for example, it is privileged or has a filesystem capability)".to_owned(), + } + } + Err(error) => { + panic!("probe fixture-owned unreadable configuration file: {error}"); + } + } + } + /// Returns the fixture-owned source path when the case has one. pub fn source_path(&self) -> Option { self.source_path.clone() @@ -606,6 +707,13 @@ impl NativeTrackerFailedStart { }; let reader_result = Self::wait_for_output_readers(&mut output_capture).await; let output = output_capture.contents().await; + let source_mode = self.permission_restore.as_ref().and_then(|restore| restore.mode); + let restore_result = self + .permission_restore + .as_mut() + .map_or(Ok(()), NativeTrackerPermissionRestore::restore) + .map_err(|error| format!("restore fixture-owned configuration file permissions: {error}")); + restore_result.map_err(|message| format!("{message}\ntracker output:\n{output}"))?; let status = status.map_err(|message| format!("{message}\ntracker output:\n{output}"))?; reader_result.map_err(|message| format!("{message}\ntracker output:\n{output}"))?; let exit_code = status @@ -616,6 +724,9 @@ impl NativeTrackerFailedStart { exit_code, output, candidate_port: self.candidate_port, + source_path: self.source_path.take(), + source_mode, + _workspace: self.workspace.take(), }) } @@ -657,18 +768,25 @@ impl Drop for NativeTrackerFailedStart { return; }; let output = self.output.take(); + // Restore synchronously before a runtime cleanup task can release the + // workspace. Drop must not panic while unwinding, so this is best effort. + if let Some(mut permission_restore) = self.permission_restore.take() { + drop(permission_restore.restore()); + } - // Drop cannot await cleanup. Do not panic while unwinding or outside a - // Tokio runtime; `kill_on_drop(true)` remains the fallback termination policy. + // `kill_on_drop(true)` remains the no-runtime fallback. With a runtime, + // retain the workspace until child and output cleanup finishes. let Ok(runtime) = tokio::runtime::Handle::try_current() else { return; }; + let workspace = self.workspace.take(); drop(runtime.spawn(async move { drop(child.start_kill()); - drop(child.wait().await); + drop(tokio::time::timeout(FAILURE_DEADLINE, child.wait()).await); if let Some(mut output) = output { - output.wait_for_readers().await; + drop(Self::wait_for_output_readers(&mut output).await); } + drop(workspace); })); } } @@ -854,12 +972,48 @@ fn tracker_binary() -> PathBuf { mod tests { use std::ffi::{OsStr, OsString}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + use std::os::unix::fs::PermissionsExt as _; use std::path::Path; use super::{ - NativeTrackerInvalidCliSource, invalid_source_command, parse_health_check_address, tracker_command, write_configuration, + NativeTrackerInvalidCliSource, NativeTrackerPermissionRestore, invalid_source_command, parse_health_check_address, + tracker_command, write_configuration, }; + #[test] + fn it_should_restore_permissions_when_the_restore_guard_is_dropped_without_a_tokio_runtime() { + // Arrange + let workspace = tempfile::tempdir().expect("create temporary permission-restore workspace"); + let path = workspace.path().join("configuration.toml"); + std::fs::write(&path, "configuration").expect("write fixture-owned configuration file"); + let original_mode = std::fs::metadata(&path) + .expect("read fixture-owned configuration file metadata") + .permissions() + .mode(); + std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o000)) + .expect("make fixture-owned configuration file unreadable"); + let restore = NativeTrackerPermissionRestore { + path: path.clone(), + mode: Some(original_mode), + }; + + // Act + let thread_result = std::thread::spawn(move || drop(restore)).join(); + + // Assert + assert!( + thread_result.is_ok(), + "dropping the restoration guard outside Tokio must not panic" + ); + assert_eq!( + std::fs::metadata(path) + .expect("read restored fixture-owned configuration file metadata") + .permissions() + .mode(), + original_mode + ); + } + #[test] fn it_should_select_its_configuration_with_the_cli_and_remove_legacy_base_source_variables() { // Arrange diff --git a/tests/configuration/cli_configuration/invalid_sources.rs b/tests/configuration/cli_configuration/invalid_sources.rs index 811c9c021..f3561e0e0 100644 --- a/tests/configuration/cli_configuration/invalid_sources.rs +++ b/tests/configuration/cli_configuration/invalid_sources.rs @@ -1,6 +1,9 @@ //! Executable-boundary invalid CLI configuration-source contracts. -use crate::native_tracker::{NativeTrackerFailedStart, NativeTrackerInvalidCliSource}; +use std::io::Write as _; +use std::os::unix::fs::PermissionsExt as _; + +use crate::native_tracker::{NativeTrackerFailedStart, NativeTrackerInvalidCliSource, NativeTrackerUnreadableCliSource}; #[tokio::test] async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_missing() { @@ -128,6 +131,61 @@ async fn it_should_not_search_parent_directories_for_a_relative_cli_configuratio .expect("parent-only configuration must not leave its candidate health port bound"); } +#[tokio::test] +async fn it_should_exit_without_starting_when_the_cli_configuration_file_is_an_unreadable_regular_file() { + // Arrange + let candidate_health_port = 43159; + + // Act + let source = NativeTrackerFailedStart::spawn_with_unreadable_regular_file(candidate_health_port); + + // Assert + match source { + NativeTrackerUnreadableCliSource::Enforced(failed_start) => { + let expected_path = failed_start + .source_path() + .expect("unreadable-file fixture should expose its source path") + .to_string_lossy() + .into_owned(); + let failure = failed_start + .wait_for_exit() + .await + .expect("tracker should exit for an unreadable regular file"); + + assert_eq!(failure.exit_code(), 1); + assert!(failure.output().contains("Unable to load explicit configuration file")); + assert!(failure.output().contains(&expected_path)); + assert!(failure.output().contains("Permission denied")); + assert_eq!(failure.candidate_port(), Some(candidate_health_port)); + assert_eq!( + std::fs::metadata( + failure + .source_path() + .expect("unreadable-file result should retain its source path"), + ) + .expect("read restored unreadable-file metadata") + .permissions() + .mode() + & 0o777, + failure + .source_mode() + .expect("unreadable-file result should retain the original source mode") + & 0o777, + "wait_for_exit must restore the unreadable file permissions" + ); + failure + .assert_candidate_port_is_bindable() + .expect("unreadable configuration must not leave its candidate health port bound"); + } + NativeTrackerUnreadableCliSource::NotEnforced { reason } => { + drop(writeln!( + std::io::stderr(), + "skipping unreadable regular-file assertion: {reason}" + )); + } + } +} + #[tokio::test] async fn it_should_not_panic_when_a_failed_start_is_dropped_without_a_tokio_runtime() { // Arrange From aa029ac4232119a6b5d904ebbf9f7e5be9daf31f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 17:09:57 +0100 Subject: [PATCH 20/44] test(configuration): simplify invalid CLI source contracts Make each executable-boundary failure test fail for one reason and remove the suite's only fixed loopback ports. - Add fixture assertion helpers (assert_usage_error, assert_startup_failure, assert_diagnostic_names_source_path) that print the captured child output when they fail. - Drop the post-failure candidate-port probe: a tracker that wrongly started never exits, so the bounded wait_for_exit deadline already proves no partial startup. All configurations now use port zero. - Drop the OS-owned "Permission denied" fragment; the tracker contract is its own diagnostic plus the source path. - Add enforced_or_report_skip() so the unreadable-file test reads as plain Arrange/Act/Assert. - Move permission-restoration and drop-without-runtime checks into the fixture's unit tests; they verify fixture behaviour, not the CLI. - Repoint links broken by the tests/common fixture move (R1). - Record the R6 review outcome in the executable test plan. --- .../implementation-retrospective.md | 2 +- .../native-tracker-refactor-plan.md | 2 +- .../2138-document-testing-strategy/ISSUE.md | 2 +- .../rust-executable-test-plan.md | 17 +- docs/testing.md | 2 +- tests/common/native_tracker.rs | 308 +++++++++++------- .../cli_configuration/invalid_sources.rs | 167 +++------- 7 files changed, 261 insertions(+), 239 deletions(-) diff --git a/docs/issues/open/2132-add-sigterm-to-main/implementation-retrospective.md b/docs/issues/open/2132-add-sigterm-to-main/implementation-retrospective.md index 145bdb0ad..5a6dbbd43 100644 --- a/docs/issues/open/2132-add-sigterm-to-main/implementation-retrospective.md +++ b/docs/issues/open/2132-add-sigterm-to-main/implementation-retrospective.md @@ -139,7 +139,7 @@ outcome classification. - [Issue specification](ISSUE.md) - [Native executable shutdown test plan](native-shutdown-test-plan.md) - [Native tracker fixture incremental refactor plan](native-tracker-refactor-plan.md) -- [`tests/lifecycle/native_tracker.rs`](../../../../tests/lifecycle/native_tracker.rs) +- [`tests/lifecycle/native_tracker.rs`](../../../../tests/common/native_tracker.rs) (moved to `tests/common/` by #2151) - [`tests/lifecycle/signals.rs`](../../../../tests/lifecycle/signals.rs) The original native fixture entered history in `92fc32ac`. The completed diff --git a/docs/issues/open/2132-add-sigterm-to-main/native-tracker-refactor-plan.md b/docs/issues/open/2132-add-sigterm-to-main/native-tracker-refactor-plan.md index d100e3a43..48c0a0f8d 100644 --- a/docs/issues/open/2132-add-sigterm-to-main/native-tracker-refactor-plan.md +++ b/docs/issues/open/2132-add-sigterm-to-main/native-tracker-refactor-plan.md @@ -466,6 +466,6 @@ split the streams, or add stream-order assertions. - [Issue specification](ISSUE.md) - [Manual verification evidence](verification.md) - [Integration-test guidelines](../../../../tests/AGENTS.md) -- [Native tracker fixture](../../../../tests/lifecycle/native_tracker.rs) +- [Native tracker fixture](../../../../tests/common/native_tracker.rs) (moved to `tests/common/` by #2151) - [Lifecycle signal scenarios](../../../../tests/lifecycle/signals.rs) - [Shutdown EPIC](../1488-overhaul-tracker-shutdown/ISSUE.md) diff --git a/docs/issues/open/2138-document-testing-strategy/ISSUE.md b/docs/issues/open/2138-document-testing-strategy/ISSUE.md index c77eead06..73cc35169 100644 --- a/docs/issues/open/2138-document-testing-strategy/ISSUE.md +++ b/docs/issues/open/2138-document-testing-strategy/ISSUE.md @@ -389,7 +389,7 @@ findings, and reusable lessons. - [Root repository instructions](../../../../AGENTS.md) - [Package instructions](../../../../packages/AGENTS.md) - [Root integration-test instructions](../../../../tests/AGENTS.md) -- [Executable-boundary lifecycle test](../../../../tests/lifecycle/native_tracker.rs) +- [Executable-boundary lifecycle test](../../../../tests/common/native_tracker.rs) - [E2E tools package](../../../../packages/e2e-tools/README.md) - [Test-writing skill](../../../../.github/skills/dev/testing/write-unit-test/SKILL.md) - [Pre-commit validation skill](../../../../.github/skills/dev/git-workflow/run-pre-commit-checks/SKILL.md) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index 8c265378d..34366b6bb 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -81,7 +81,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | R3a | DONE | Prove base-source exclusivity | Added a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. It asserts neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests). | | R4 | DONE | Add invalid-source process matrix | Added `invalid_sources.rs` with compiled-child contracts for missing/empty option values, missing file, directory, malformed TOML, and parent-only relative path. They assert real exit codes and stable diagnostic fragments; malformed and parent-only sources prove their candidate ports are bindable after child reaping. The expected-failure fixture bounds waiting, forced reaping, and output draining. `cargo test --test cli-configuration` passed (15 tests). | | R5 | DONE | Add Unix unreadable-file process test | Added a fixture-owned valid mode-`000` regular file scenario. When permissions are enforced, the compiled child exits `1` with a path-bearing permission diagnostic and leaves its candidate port bindable after reaping; privileged runners report an explicit skip without spawning a child. Permission restoration is fallible in normal cleanup, non-panicking in drop cleanup, and verified after `wait_for_exit`. `cargo test --test cli-configuration` passed (17 tests). | -| R6 | TODO | Review test design increment | After each behavior-focused increment, run the relevant target (`cli-configuration` or `lifecycle-signals`) and review responsibility, ownership, absolute readiness deadlines, output retention, and panic/drop cleanup before adding the next scenario. Stop for maintainer review after R5. | +| R6 | DONE | Review test design increment | Maintainer review of `invalid_sources.rs` found multi-contract asserts, an unreadable-file test that mixed fixture checks into the Assert step, and the suite's only fixed loopback ports (43157-43159). Resolved by: fixture assertion helpers (`assert_usage_error`, `assert_startup_failure`, `assert_diagnostic_names_source_path`) that print child output on failure; one contract per test; `enforced_or_report_skip()` so the unreadable-file test reads as plain AAA; moving permission-restoration and drop-without-runtime checks into the fixture's unit tests; dropping the candidate-port probe (the bounded exit wait already proves no start) so every configuration uses port zero. `cargo test --test cli-configuration` passed (18 tests, 3 consecutive runs). | | R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | | R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | | R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | @@ -89,8 +89,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. ## Scenario Contracts Each executable-boundary scenario uses an isolated `TempDir`, child-specific -configuration, port-zero bindings unless a fixed candidate port is necessary to -prove no listener, and the fixture's existing absolute deadlines. +configuration, port-zero bindings, and the fixture's existing absolute deadlines. - **Precedence:** set conflicting valid `TORRUST_TRACKER_CONFIG_TOML` and `TORRUST_TRACKER_CONFIG_TOML_PATH` only in the child `Command`; the health @@ -103,13 +102,17 @@ prove no listener, and the fixture's existing absolute deadlines. - **Override:** child command sets a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS`; the override endpoint becomes ready, not the CLI file endpoint. -- **Failure:** each child is reaped before assertions complete. A fixed - loopback candidate port is bound/probed after failure only when the invalid - source was otherwise capable of providing that port (malformed and - parent-only-relative cases). +- **Failure:** each child is reaped before assertions complete. Each test + asserts one contract: the exit code plus the tracker-owned diagnostic (and + the source path when the source has one) through fixture assertion helpers + that print the captured child output on failure. A tracker that wrongly + started would never exit, so the bounded `wait_for_exit` deadline is the + proof of no partial startup; no fixed candidate ports are used. - **Unreadable file:** Unix-specific test setup and cleanup own the file mode. The assertion must account for a privileged runner that bypasses permission bits and report an explicit skip rather than masking a platform constraint. + Fixture behaviour (permission restoration, drop without a runtime) is tested + in the fixture's own unit tests, not in the executable contract tests. ## Validation diff --git a/docs/testing.md b/docs/testing.md index 4db1deeb6..29314d4eb 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -55,7 +55,7 @@ package-level tests, as supported by [EPIC #1347](https://github.com/torrust/tor | Unit and documentation tests | A package type, function, or module has behavior that can run without I/O or a deployed service. | The focused behavior is correct in isolation. | Cross-package wiring, process behavior, or a packaged runtime. | [`Driver` tests](../packages/primitives/src/driver.rs) | [Unit-test skill](../.github/skills/dev/testing/write-unit-test/SKILL.md); [package testing guidance](../packages/AGENTS.md#testing-packages) | | Package-level in-process integration | A package boundary needs real collaborators, such as a server and its handler, without the complete application. | The package's components work together through its public boundary. | Application-wide service coordination or the compiled tracker executable. | [HTTP server contract tests](../packages/axum-http-server/tests/server/v1/contract/) | [Package testing guidance](../packages/AGENTS.md#testing-packages); [test refactoring patterns](testing/refactoring-patterns/README.md) | | Root application-level in-process integration | An observable behavior needs the complete application container and multiple coordinated services. | Application startup, cross-service coordination, aggregate metrics, and job or shutdown orchestration. | OS process boundaries, signals sent to the tracker executable, or container-image behavior. | [Port-zero metrics suite](../tests/metrics/port_zero.rs) | [Root integration-test guidance](../tests/AGENTS.md) | -| Executable-boundary integration | The behavior requires starting the compiled tracker as a child process, such as OS-signal handling. | The native executable starts and reacts correctly at its process boundary. | Container-image behavior or interoperability with an external BitTorrent client. | [Native tracker fixture](../tests/lifecycle/native_tracker.rs) | [Child-process configuration isolation](../tests/AGENTS.md#child-process-configuration-isolation) | +| Executable-boundary integration | The behavior requires starting the compiled tracker as a child process, such as OS-signal handling. | The native executable starts and reacts correctly at its process boundary. | Container-image behavior or interoperability with an external BitTorrent client. | [Native tracker fixture](../tests/common/native_tracker.rs) | [Child-process configuration isolation](../tests/AGENTS.md#child-process-configuration-isolation) | | Container E2E | The tracker must be exercised as the built container artifact with project-controlled clients. | The image builds and tracker behavior works through its network boundary. | Interoperability with a production BitTorrent client or every database backend. | [`e2e_tests_runner`](../packages/e2e-tools/README.md#binaries) | [E2E tools usage](../packages/e2e-tools/README.md); [container workflow](../.github/workflows/container.yaml) | | Container plus qBittorrent E2E | Compatibility must be demonstrated against a real BitTorrent client and configured database backend. | The containerized tracker interoperates with qBittorrent for the selected backend. | Isolated package behavior or exhaustive coverage of all failure paths. | [`qbittorrent_e2e_runner`](../packages/e2e-tools/README.md#binaries) | [E2E tools usage](../packages/e2e-tools/README.md); [container workflow](../.github/workflows/container.yaml) | | Database compatibility | A persistence change affects MySQL or PostgreSQL driver behavior or supported-version compatibility. | The selected tracker-core database-driver scenarios work against the workflow's version matrix. | Complete tracker container behavior or SQLite behavior not covered by the scenario. | [Database compatibility workflow](../.github/workflows/db-compatibility.yaml) | [Database compatibility workflow](../.github/workflows/db-compatibility.yaml); [package testing guidance](../packages/AGENTS.md#testing-packages) | diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index 069d91ad7..26bd3dc51 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -5,6 +5,7 @@ //! discovers the health endpoint from its startup log, and reaps the child //! even when graceful shutdown exceeds the scenario deadline. +use std::io::Write as _; use std::net::SocketAddr; use std::os::unix::fs::PermissionsExt as _; use std::os::unix::process::ExitStatusExt; @@ -81,10 +82,10 @@ pub enum NativeTrackerInvalidCliSource { MissingFile, /// Supplies a directory where a configuration file is required. Directory, - /// Supplies malformed TOML which would otherwise configure this health port. - MalformedToml { candidate_health_port: u16 }, + /// Supplies an otherwise valid configuration file with malformed TOML appended. + MalformedToml, /// Supplies `tracker.toml` from a child directory while it exists only in its parent. - ParentOnlyRelativeFile { candidate_health_port: u16 }, + ParentOnlyRelativeFile, } /// Result of preparing an unreadable regular-file CLI source. @@ -94,12 +95,104 @@ pub enum NativeTrackerInvalidCliSource { /// spawning so the executable contract is only asserted when meaningful. #[allow(dead_code)] pub enum NativeTrackerUnreadableCliSource { - /// The operating system denied a read and the child was spawned to prove its failure contract. - Enforced(Box), - /// The current process can read mode-`000` files, so no child was spawned. + /// The operating system denied a read, making the prepared startup attempt meaningful. + Enforced(Box), + /// The current process can read mode-`000` files, so no startup attempt was prepared. NotEnforced { reason: String }, } +#[allow(dead_code)] +impl NativeTrackerUnreadableCliSource { + /// Returns the prepared startup attempt when the platform enforces the unreadable mode. + /// + /// When it does not, the skip reason is reported on stderr and `None` is + /// returned so the caller can end the test as an explicit skip. + pub fn enforced_or_report_skip(self) -> Option { + match self { + Self::Enforced(failed_start) => Some(*failed_start), + Self::NotEnforced { reason } => { + drop(writeln!( + std::io::stderr(), + "skipping unreadable regular-file assertion: {reason}" + )); + None + } + } + } +} + +/// A prepared tracker process startup that has not yet launched the executable. +#[allow(dead_code)] +pub struct NativeTrackerStartAttempt { + command: Command, + permission_restore: Option, + workspace: Option, + source_path: Option, +} + +#[allow(dead_code)] +impl NativeTrackerStartAttempt { + /// Prepares a tracker startup with a deliberately invalid CLI configuration source. + pub fn with_invalid_cli_source(source: NativeTrackerInvalidCliSource) -> Self { + let workspace = tempfile::tempdir().expect("create temporary invalid-source workspace"); + let (command, source_path) = invalid_source_command(&workspace, source); + + Self { + command, + permission_restore: None, + workspace: Some(workspace), + source_path, + } + } + + /// Prepares a valid regular configuration file that is unreadable by normal Unix permission checks. + pub fn with_unreadable_regular_file() -> NativeTrackerUnreadableCliSource { + let workspace = tempfile::tempdir().expect("create temporary unreadable-source workspace"); + let (source_path, _) = write_configuration(&workspace, "unreadable", 0); + let original_mode = std::fs::metadata(&source_path) + .expect("read fixture-owned configuration file metadata") + .permissions() + .mode(); + std::fs::set_permissions(&source_path, std::fs::Permissions::from_mode(0o000)) + .expect("make fixture-owned configuration file unreadable"); + let permission_restore = NativeTrackerPermissionRestore { + path: source_path.clone(), + mode: Some(original_mode), + }; + + match std::fs::read_to_string(&source_path) { + Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => { + NativeTrackerUnreadableCliSource::Enforced(Box::new(Self { + command: tracker_command(&source_path, None, None, None), + permission_restore: Some(permission_restore), + workspace: Some(workspace), + source_path: Some(source_path), + })) + } + Ok(_) => NativeTrackerUnreadableCliSource::NotEnforced { + reason: "the current process can read a mode-000 regular file (for example, it is privileged or has a filesystem capability)" + .to_owned(), + }, + Err(error) => panic!("probe fixture-owned unreadable configuration file: {error}"), + } + } + + /// Launches the prepared tracker executable. + pub fn start(mut self) -> NativeTrackerFailedStart { + let mut child = self.command.spawn().expect("spawn Cargo-built tracker executable"); + let stdout = child.stdout.take().expect("tracker child stdout is piped"); + let stderr = child.stderr.take().expect("tracker child stderr is piped"); + + NativeTrackerFailedStart { + child: Some(child), + output: Some(TrackerOutputCapture::new(stdout, stderr)), + permission_restore: self.permission_restore.take(), + workspace: self.workspace.take(), + source_path: self.source_path.take(), + } + } +} + /// A tracker child process expected to fail before completing startup. #[allow(dead_code)] pub struct NativeTrackerFailedStart { @@ -110,7 +203,6 @@ pub struct NativeTrackerFailedStart { permission_restore: Option, workspace: Option, source_path: Option, - candidate_port: Option, } /// Restores the original Unix mode of a fixture-owned configuration file. @@ -143,12 +235,18 @@ impl Drop for NativeTrackerPermissionRestore { pub struct NativeTrackerFailedStartResult { exit_code: i32, output: String, - candidate_port: Option, source_path: Option, source_mode: Option, _workspace: Option, } +/// Exit code `clap` uses when the command line itself is invalid. +#[allow(dead_code)] +const USAGE_ERROR_EXIT_CODE: i32 = 2; +/// Exit code the tracker uses when startup fails after argument parsing. +#[allow(dead_code)] +const STARTUP_FAILURE_EXIT_CODE: i32 = 1; + #[allow(dead_code)] impl NativeTrackerFailedStartResult { /// Returns the process exit code captured after the child was reaped. @@ -161,11 +259,6 @@ impl NativeTrackerFailedStartResult { &self.output } - /// Returns the configured port that was eligible for a post-reap bind probe. - pub const fn candidate_port(&self) -> Option { - self.candidate_port - } - /// Returns the fixture-owned source path while this result is retained. pub fn source_path(&self) -> Option<&std::path::Path> { self.source_path.as_deref() @@ -176,14 +269,48 @@ impl NativeTrackerFailedStartResult { self.source_mode } - /// Proves a candidate health port is not left bound after child reaping. - pub fn assert_candidate_port_is_bindable(&self) -> Result<(), String> { - let port = self - .candidate_port - .ok_or_else(|| "this failure source has no candidate health port".to_owned())?; - std::net::TcpListener::bind((std::net::Ipv4Addr::LOCALHOST, port)) - .map(drop) - .map_err(|error| format!("bind candidate health port {port} after child reaping: {error}")) + /// Asserts the child was rejected by argument parsing with the given usage diagnostic. + pub fn assert_usage_error(&self, expected_diagnostic: &str) { + self.assert_exit_code(USAGE_ERROR_EXIT_CODE); + self.assert_output_contains(expected_diagnostic); + } + + /// Asserts the child failed startup with the given diagnostic. + pub fn assert_startup_failure(&self, expected_diagnostic: &str) { + self.assert_exit_code(STARTUP_FAILURE_EXIT_CODE); + self.assert_output_contains(expected_diagnostic); + } + + /// Asserts the diagnostic names the fixture-owned source path. + pub fn assert_diagnostic_names_source_path(&self) { + let path = self + .source_path() + .expect("this failure source has no fixture-owned source path") + .to_string_lossy() + .into_owned(); + self.assert_output_contains(&path); + } + + /// Asserts explicit configuration loading failed and identifies the supplied file. + pub fn assert_explicit_configuration_file_load_failure(&self) { + self.assert_startup_failure("Unable to load explicit configuration file"); + self.assert_diagnostic_names_source_path(); + } + + fn assert_exit_code(&self, expected: i32) { + assert_eq!( + self.exit_code, expected, + "unexpected exit code\ntracker output:\n{}", + self.output + ); + } + + fn assert_output_contains(&self, expected_fragment: &str) { + assert!( + self.output.contains(expected_fragment), + "tracker output does not contain {expected_fragment:?}\ntracker output:\n{}", + self.output + ); } } @@ -622,69 +749,6 @@ impl NativeTracker { #[allow(dead_code)] impl NativeTrackerFailedStart { - /// Spawns a child with a deliberately invalid CLI configuration source. - pub fn spawn(source: NativeTrackerInvalidCliSource) -> Self { - let workspace = tempfile::tempdir().expect("create temporary invalid-source workspace"); - let (mut command, source_path, candidate_port) = invalid_source_command(&workspace, source); - let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); - let stdout = child.stdout.take().expect("tracker child stdout is piped"); - let stderr = child.stderr.take().expect("tracker child stderr is piped"); - - Self { - child: Some(child), - output: Some(TrackerOutputCapture::new(stdout, stderr)), - permission_restore: None, - workspace: Some(workspace), - source_path, - candidate_port, - } - } - - /// Prepares a valid regular configuration file that is unreadable by normal Unix permission checks. - pub fn spawn_with_unreadable_regular_file(candidate_health_port: u16) -> NativeTrackerUnreadableCliSource { - let workspace = tempfile::tempdir().expect("create temporary unreadable-source workspace"); - let (source_path, _) = write_configuration(&workspace, "unreadable", candidate_health_port); - let original_mode = std::fs::metadata(&source_path) - .expect("read fixture-owned configuration file metadata") - .permissions() - .mode(); - std::fs::set_permissions(&source_path, std::fs::Permissions::from_mode(0o000)) - .expect("make fixture-owned configuration file unreadable"); - // Create the guard immediately after changing permissions. Every - // subsequent early return or panic, including command setup failure, - // restores the fixture-owned file synchronously. - let permission_restore = NativeTrackerPermissionRestore { - path: source_path.clone(), - mode: Some(original_mode), - }; - - match std::fs::read_to_string(&source_path) { - Err(error) if error.kind() == std::io::ErrorKind::PermissionDenied => { - let mut command = tracker_command(&source_path, None, None, None); - let mut child = command.spawn().expect("spawn Cargo-built tracker executable"); - let stdout = child.stdout.take().expect("tracker child stdout is piped"); - let stderr = child.stderr.take().expect("tracker child stderr is piped"); - - NativeTrackerUnreadableCliSource::Enforced(Box::new(Self { - child: Some(child), - output: Some(TrackerOutputCapture::new(stdout, stderr)), - permission_restore: Some(permission_restore), - workspace: Some(workspace), - source_path: Some(source_path), - candidate_port: Some(candidate_health_port), - })) - } - Ok(_) => { - NativeTrackerUnreadableCliSource::NotEnforced { - reason: "the current process can read a mode-000 regular file (for example, it is privileged or has a filesystem capability)".to_owned(), - } - } - Err(error) => { - panic!("probe fixture-owned unreadable configuration file: {error}"); - } - } - } - /// Returns the fixture-owned source path when the case has one. pub fn source_path(&self) -> Option { self.source_path.clone() @@ -723,7 +787,6 @@ impl NativeTrackerFailedStart { Ok(NativeTrackerFailedStartResult { exit_code, output, - candidate_port: self.candidate_port, source_path: self.source_path.take(), source_mode, _workspace: self.workspace.take(), @@ -899,34 +962,31 @@ fn configure_tracker_command(command: &mut Command) { } #[allow(dead_code)] -fn invalid_source_command( - workspace: &tempfile::TempDir, - source: NativeTrackerInvalidCliSource, -) -> (Command, Option, Option) { +fn invalid_source_command(workspace: &tempfile::TempDir, source: NativeTrackerInvalidCliSource) -> (Command, Option) { let mut command = Command::new(tracker_binary()); configure_tracker_command(&mut command); match source { NativeTrackerInvalidCliSource::MissingOptionValue => { command.arg("--config-toml-path"); - (command, None, None) + (command, None) } NativeTrackerInvalidCliSource::EmptyOptionValue => { command.arg("--config-toml-path").arg(""); - (command, None, None) + (command, None) } NativeTrackerInvalidCliSource::MissingFile => { let path = workspace.path().join("does-not-exist.toml"); command.arg("--config-toml-path").arg(&path); - (command, Some(path), None) + (command, Some(path)) } NativeTrackerInvalidCliSource::Directory => { let path = workspace.path().to_path_buf(); command.arg("--config-toml-path").arg(&path); - (command, Some(path), None) + (command, Some(path)) } - NativeTrackerInvalidCliSource::MalformedToml { candidate_health_port } => { - let (path, _) = write_configuration(workspace, "malformed", candidate_health_port); + NativeTrackerInvalidCliSource::MalformedToml => { + let (path, _) = write_configuration(workspace, "malformed", 0); std::fs::write( &path, format!( @@ -936,16 +996,16 @@ fn invalid_source_command( ) .expect("write malformed tracker configuration"); command.arg("--config-toml-path").arg(&path); - (command, Some(path), Some(candidate_health_port)) + (command, Some(path)) } - NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { candidate_health_port } => { + NativeTrackerInvalidCliSource::ParentOnlyRelativeFile => { let parent = workspace.path().join("parent"); let child = parent.join("child"); std::fs::create_dir_all(&child).expect("create child working directory"); - let (parent_configuration, _) = write_configuration_in_directory(&parent, candidate_health_port); + let (parent_configuration, _) = write_configuration_in_directory(&parent, 0); assert_eq!(parent_configuration.file_name(), Some(std::ffi::OsStr::new("tracker.toml"))); command.current_dir(child).arg("--config-toml-path").arg("tracker.toml"); - (command, Some(parent_configuration), Some(candidate_health_port)) + (command, Some(parent_configuration)) } } } @@ -976,8 +1036,8 @@ mod tests { use std::path::Path; use super::{ - NativeTrackerInvalidCliSource, NativeTrackerPermissionRestore, invalid_source_command, parse_health_check_address, - tracker_command, write_configuration, + NativeTrackerInvalidCliSource, NativeTrackerPermissionRestore, NativeTrackerStartAttempt, invalid_source_command, + parse_health_check_address, tracker_command, write_configuration, }; #[test] @@ -1110,15 +1170,9 @@ mod tests { fn it_should_construct_a_parent_only_relative_source_from_the_child_working_directory() { // Arrange let workspace = tempfile::tempdir().expect("create temporary tracker workspace"); - let candidate_port = 43157; // Act - let (command, source_path, observed_port) = invalid_source_command( - &workspace, - NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { - candidate_health_port: candidate_port, - }, - ); + let (command, source_path) = invalid_source_command(&workspace, NativeTrackerInvalidCliSource::ParentOnlyRelativeFile); // Assert let source_path = source_path.expect("parent-only source should retain its parent file path"); @@ -1128,7 +1182,43 @@ mod tests { command.as_std().get_current_dir() ); assert_eq!(command.as_std().get_args().last(), Some(OsStr::new("tracker.toml"))); - assert_eq!(observed_port, Some(candidate_port)); + } + + #[tokio::test] + async fn it_should_not_panic_when_a_failed_start_is_dropped_without_a_tokio_runtime() { + // Arrange: spawning needs a runtime; the drop below happens outside one. + let failed_start = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::MissingFile).start(); + + // Act + let result = std::thread::spawn(move || drop(failed_start)).join(); + + // Assert + assert!(result.is_ok(), "dropping a failed start outside Tokio must not panic"); + } + + #[tokio::test] + async fn it_should_restore_the_unreadable_source_permissions_after_waiting_for_exit() { + // Arrange + let Some(start_attempt) = NativeTrackerStartAttempt::with_unreadable_regular_file().enforced_or_report_skip() else { + return; + }; + + // Act + let failure = start_attempt + .start() + .wait_for_exit() + .await + .expect("tracker should exit for an unreadable regular file"); + + // Assert + let restored_mode = std::fs::metadata(failure.source_path().expect("unreadable-file result should retain its path")) + .expect("read restored unreadable-file metadata") + .permissions() + .mode(); + let original_mode = failure + .source_mode() + .expect("unreadable-file result should retain the original source mode"); + assert_eq!(restored_mode & 0o777, original_mode & 0o777); } #[test] diff --git a/tests/configuration/cli_configuration/invalid_sources.rs b/tests/configuration/cli_configuration/invalid_sources.rs index f3561e0e0..e45b4fdc0 100644 --- a/tests/configuration/cli_configuration/invalid_sources.rs +++ b/tests/configuration/cli_configuration/invalid_sources.rs @@ -1,199 +1,128 @@ //! Executable-boundary invalid CLI configuration-source contracts. +//! +//! Every scenario spawns the compiled tracker with one deliberately invalid +//! `--config-toml-path` source and asserts the exit code and diagnostic. A +//! tracker that wrongly started would never exit, so the bounded wait in +//! `wait_for_exit` is itself the proof that no service was started. -use std::io::Write as _; -use std::os::unix::fs::PermissionsExt as _; +use crate::native_tracker::{NativeTrackerInvalidCliSource, NativeTrackerStartAttempt}; -use crate::native_tracker::{NativeTrackerFailedStart, NativeTrackerInvalidCliSource, NativeTrackerUnreadableCliSource}; +const USAGE_ERROR_MISSING_VALUE: &str = "a value is required"; +const USAGE_ERROR_EMPTY_VALUE: &str = "must not be empty"; +const UNABLE_TO_LOAD_EXPLICIT_FILE: &str = "Unable to load explicit configuration file"; +const UNABLE_TO_PROCESS_EXPLICIT_FILE: &str = "Unable to process explicit configuration file"; #[tokio::test] async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_missing() { // Arrange - let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MissingOptionValue); + let start_attempt = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::MissingOptionValue); // Act - let failure = failed_start + let failure = start_attempt + .start() .wait_for_exit() .await .expect("tracker should exit for a missing option value"); // Assert - assert_eq!(failure.exit_code(), 2); - assert!(failure.output().contains("a value is required")); + failure.assert_usage_error(USAGE_ERROR_MISSING_VALUE); } #[tokio::test] async fn it_should_exit_with_a_usage_error_when_the_config_toml_path_value_is_empty() { // Arrange - let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::EmptyOptionValue); + let start_attempt = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::EmptyOptionValue); // Act - let failure = failed_start + let failure = start_attempt + .start() .wait_for_exit() .await .expect("tracker should exit for an empty option value"); // Assert - assert_eq!(failure.exit_code(), 2); - assert!(failure.output().contains("must not be empty")); + failure.assert_usage_error(USAGE_ERROR_EMPTY_VALUE); } #[tokio::test] -async fn it_should_exit_without_starting_when_the_cli_configuration_file_is_missing() { +async fn it_should_fail_startup_naming_the_path_when_the_cli_configuration_file_is_missing() { // Arrange - let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MissingFile); - let expected_path = failed_start - .source_path() - .expect("missing-file fixture should expose its source path") - .to_string_lossy() - .into_owned(); + let start_attempt = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::MissingFile); // Act - let failure = failed_start + let failure = start_attempt + .start() .wait_for_exit() .await .expect("tracker should exit for a missing file"); // Assert - assert_eq!(failure.exit_code(), 1); - assert!(failure.output().contains("Unable to load explicit configuration file")); - assert!(failure.output().contains(&expected_path)); + failure.assert_explicit_configuration_file_load_failure(); } #[tokio::test] -async fn it_should_exit_without_starting_when_the_cli_configuration_source_is_a_directory() { +async fn it_should_fail_startup_naming_the_path_when_the_cli_configuration_source_is_a_directory() { // Arrange - let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::Directory); - let expected_path = failed_start - .source_path() - .expect("directory fixture should expose its source path") - .to_string_lossy() - .into_owned(); + let start_attempt = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::Directory); // Act - let failure = failed_start + let failure = start_attempt + .start() .wait_for_exit() .await .expect("tracker should exit for a directory source"); // Assert - assert_eq!(failure.exit_code(), 1); - assert!(failure.output().contains("Unable to load explicit configuration file")); - assert!(failure.output().contains(&expected_path)); + failure.assert_explicit_configuration_file_load_failure(); } #[tokio::test] -async fn it_should_exit_without_starting_when_the_cli_configuration_toml_is_malformed() { +async fn it_should_fail_startup_naming_the_path_when_the_cli_configuration_toml_is_malformed() { // Arrange - let candidate_health_port = 43158; - let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MalformedToml { candidate_health_port }); - let expected_path = failed_start - .source_path() - .expect("malformed-TOML fixture should expose its source path") - .to_string_lossy() - .into_owned(); + let start_attempt = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::MalformedToml); // Act - let failure = failed_start + let failure = start_attempt + .start() .wait_for_exit() .await .expect("tracker should exit for malformed TOML"); // Assert - assert_eq!(failure.exit_code(), 1); - assert!(failure.output().contains("Unable to process explicit configuration file")); - assert!(failure.output().contains(&expected_path)); - assert_eq!(failure.candidate_port(), Some(candidate_health_port)); - failure - .assert_candidate_port_is_bindable() - .expect("malformed configuration must not leave its candidate health port bound"); + failure.assert_startup_failure(UNABLE_TO_PROCESS_EXPLICIT_FILE); + failure.assert_diagnostic_names_source_path(); } #[tokio::test] async fn it_should_not_search_parent_directories_for_a_relative_cli_configuration_file() { // Arrange - let candidate_health_port = 43157; - let failed_start = - NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::ParentOnlyRelativeFile { candidate_health_port }); + let start_attempt = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::ParentOnlyRelativeFile); // Act - let failure = failed_start + let failure = start_attempt + .start() .wait_for_exit() .await .expect("tracker should exit rather than load the parent configuration file"); // Assert - assert_eq!(failure.exit_code(), 1); - assert!(failure.output().contains("Unable to load explicit configuration file")); - assert!(failure.output().contains("tracker.toml")); - assert_eq!(failure.candidate_port(), Some(candidate_health_port)); - failure - .assert_candidate_port_is_bindable() - .expect("parent-only configuration must not leave its candidate health port bound"); + failure.assert_startup_failure(UNABLE_TO_LOAD_EXPLICIT_FILE); } #[tokio::test] -async fn it_should_exit_without_starting_when_the_cli_configuration_file_is_an_unreadable_regular_file() { +async fn it_should_fail_startup_naming_the_path_when_the_cli_configuration_file_is_unreadable() { // Arrange - let candidate_health_port = 43159; + let Some(start_attempt) = NativeTrackerStartAttempt::with_unreadable_regular_file().enforced_or_report_skip() else { + return; + }; // Act - let source = NativeTrackerFailedStart::spawn_with_unreadable_regular_file(candidate_health_port); - - // Assert - match source { - NativeTrackerUnreadableCliSource::Enforced(failed_start) => { - let expected_path = failed_start - .source_path() - .expect("unreadable-file fixture should expose its source path") - .to_string_lossy() - .into_owned(); - let failure = failed_start - .wait_for_exit() - .await - .expect("tracker should exit for an unreadable regular file"); - - assert_eq!(failure.exit_code(), 1); - assert!(failure.output().contains("Unable to load explicit configuration file")); - assert!(failure.output().contains(&expected_path)); - assert!(failure.output().contains("Permission denied")); - assert_eq!(failure.candidate_port(), Some(candidate_health_port)); - assert_eq!( - std::fs::metadata( - failure - .source_path() - .expect("unreadable-file result should retain its source path"), - ) - .expect("read restored unreadable-file metadata") - .permissions() - .mode() - & 0o777, - failure - .source_mode() - .expect("unreadable-file result should retain the original source mode") - & 0o777, - "wait_for_exit must restore the unreadable file permissions" - ); - failure - .assert_candidate_port_is_bindable() - .expect("unreadable configuration must not leave its candidate health port bound"); - } - NativeTrackerUnreadableCliSource::NotEnforced { reason } => { - drop(writeln!( - std::io::stderr(), - "skipping unreadable regular-file assertion: {reason}" - )); - } - } -} - -#[tokio::test] -async fn it_should_not_panic_when_a_failed_start_is_dropped_without_a_tokio_runtime() { - // Arrange - let failed_start = NativeTrackerFailedStart::spawn(NativeTrackerInvalidCliSource::MissingFile); - - // Act - let result = std::thread::spawn(move || drop(failed_start)).join(); + let failure = start_attempt + .start() + .wait_for_exit() + .await + .expect("tracker should exit for an unreadable regular file"); // Assert - assert!(result.is_ok(), "dropping a failed start outside Tokio must not panic"); + failure.assert_explicit_configuration_file_load_failure(); } From 61ae48461e022c98beaf89da376e3dc5a6ba481b Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 17:34:11 +0100 Subject: [PATCH 21/44] docs(testing): require Rust for tracked test code --- .../rust-executable-test-plan.md | 24 +++++++++---------- docs/testing.md | 5 +++- tests/AGENTS.md | 6 +++++ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index 34366b6bb..156c9d896 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -73,18 +73,18 @@ entrypoint forwarding contract and is covered by container-image CI. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| --- | ------ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | -| R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | -| R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | -| R3a | DONE | Prove base-source exclusivity | Added a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. It asserts neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests). | -| R4 | DONE | Add invalid-source process matrix | Added `invalid_sources.rs` with compiled-child contracts for missing/empty option values, missing file, directory, malformed TOML, and parent-only relative path. They assert real exit codes and stable diagnostic fragments; malformed and parent-only sources prove their candidate ports are bindable after child reaping. The expected-failure fixture bounds waiting, forced reaping, and output draining. `cargo test --test cli-configuration` passed (15 tests). | -| R5 | DONE | Add Unix unreadable-file process test | Added a fixture-owned valid mode-`000` regular file scenario. When permissions are enforced, the compiled child exits `1` with a path-bearing permission diagnostic and leaves its candidate port bindable after reaping; privileged runners report an explicit skip without spawning a child. Permission restoration is fallible in normal cleanup, non-panicking in drop cleanup, and verified after `wait_for_exit`. `cargo test --test cli-configuration` passed (17 tests). | -| R6 | DONE | Review test design increment | Maintainer review of `invalid_sources.rs` found multi-contract asserts, an unreadable-file test that mixed fixture checks into the Assert step, and the suite's only fixed loopback ports (43157-43159). Resolved by: fixture assertion helpers (`assert_usage_error`, `assert_startup_failure`, `assert_diagnostic_names_source_path`) that print child output on failure; one contract per test; `enforced_or_report_skip()` so the unreadable-file test reads as plain AAA; moving permission-restoration and drop-without-runtime checks into the fixture's unit tests; dropping the candidate-port probe (the bounded exit wait already proves no start) so every configuration uses port zero. `cargo test --test cli-configuration` passed (18 tests, 3 consecutive runs). | -| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | -| R8 | TODO | Document Rust-only test policy | Update `docs/testing.md` and `tests/AGENTS.md` to state that tracked repository test code is Rust; use Python only for non-test external tooling when separately justified. Link this decision to the test-layer guidance without duplicating it. | -| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | +| ID | Status | Task | Expected result | +| --- | ------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| R1 | DONE | Extract common native fixture | Moved `tests/lifecycle/native_tracker.rs` to `tests/common/native_tracker.rs` and updated signal tests to import it explicitly. Child-process, workspace, output-draining, absolute-deadline, normal-shutdown, and drop-path cleanup behavior is unchanged. `cargo test --test lifecycle-signals` passed (8 tests). | +| R2 | DONE | Add configuration CLI test target | Added and registered `tests/configuration/cli_configuration.rs` with an executable CLI-precedence scenario. It imports the common fixture through an explicit path module declaration and asserts a live configuration contract rather than duplicating signal behavior. `cargo test --test cli-configuration` passed. | +| R3 | DONE | Add CLI precedence process tests | Added independent executable-boundary tests for CLI precedence over both child-only base environment sources and for a child-only health-check override over the CLI file. Each waits for the selected endpoint and uses fixture-owned graceful cleanup. `cargo test --test cli-configuration` passed (7 tests). | +| R3a | DONE | Prove base-source exclusivity | Added a configuration-package unit test where the ignored complete-TOML and environment-path sources each provide a distinct optional section absent from the explicit file. It asserts neither section appears in the loaded configuration, proving base sources are exclusive rather than merely resolved by conflicting values. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests). | +| R4 | DONE | Add invalid-source process matrix | Added `invalid_sources.rs` with compiled-child contracts for missing/empty option values, missing file, directory, malformed TOML, and parent-only relative path. They assert real exit codes and stable diagnostic fragments; malformed and parent-only sources prove their candidate ports are bindable after child reaping. The expected-failure fixture bounds waiting, forced reaping, and output draining. `cargo test --test cli-configuration` passed (15 tests). | +| R5 | DONE | Add Unix unreadable-file process test | Added a fixture-owned valid mode-`000` regular file scenario. When permissions are enforced, the compiled child exits `1` with a path-bearing permission diagnostic and leaves its candidate port bindable after reaping; privileged runners report an explicit skip without spawning a child. Permission restoration is fallible in normal cleanup, non-panicking in drop cleanup, and verified after `wait_for_exit`. `cargo test --test cli-configuration` passed (17 tests). | +| R6 | DONE | Review test design increment | Maintainer review of `invalid_sources.rs` found multi-contract asserts, an unreadable-file test that mixed fixture checks into the Assert step, and the suite's only fixed loopback ports (43157-43159). Resolved by: fixture assertion helpers (`assert_usage_error`, `assert_startup_failure`, `assert_diagnostic_names_source_path`) that print child output on failure; one contract per test; `enforced_or_report_skip()` so the unreadable-file test reads as plain AAA; moving permission-restoration and drop-without-runtime checks into the fixture's unit tests; dropping the candidate-port probe (the bounded exit wait already proves no start) so every configuration uses port zero; and separating `NativeTrackerStartAttempt` preparation from `.start()` so Arrange does not launch the child process. `cargo test --test cli-configuration` passed (18 tests, 3 consecutive runs). | +| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R8 | DONE | Document Rust-only test policy | `tests/AGENTS.md` now defines the operational policy: tracked repository test code is Rust; Python is allowed only for separately justified non-test external tooling, never test automation, fixtures, or assertions. `docs/testing.md` states the policy and links to that authoritative guidance without duplicating the exception. | +| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | ## Scenario Contracts diff --git a/docs/testing.md b/docs/testing.md index 29314d4eb..c31d30060 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -55,7 +55,7 @@ package-level tests, as supported by [EPIC #1347](https://github.com/torrust/tor | Unit and documentation tests | A package type, function, or module has behavior that can run without I/O or a deployed service. | The focused behavior is correct in isolation. | Cross-package wiring, process behavior, or a packaged runtime. | [`Driver` tests](../packages/primitives/src/driver.rs) | [Unit-test skill](../.github/skills/dev/testing/write-unit-test/SKILL.md); [package testing guidance](../packages/AGENTS.md#testing-packages) | | Package-level in-process integration | A package boundary needs real collaborators, such as a server and its handler, without the complete application. | The package's components work together through its public boundary. | Application-wide service coordination or the compiled tracker executable. | [HTTP server contract tests](../packages/axum-http-server/tests/server/v1/contract/) | [Package testing guidance](../packages/AGENTS.md#testing-packages); [test refactoring patterns](testing/refactoring-patterns/README.md) | | Root application-level in-process integration | An observable behavior needs the complete application container and multiple coordinated services. | Application startup, cross-service coordination, aggregate metrics, and job or shutdown orchestration. | OS process boundaries, signals sent to the tracker executable, or container-image behavior. | [Port-zero metrics suite](../tests/metrics/port_zero.rs) | [Root integration-test guidance](../tests/AGENTS.md) | -| Executable-boundary integration | The behavior requires starting the compiled tracker as a child process, such as OS-signal handling. | The native executable starts and reacts correctly at its process boundary. | Container-image behavior or interoperability with an external BitTorrent client. | [Native tracker fixture](../tests/common/native_tracker.rs) | [Child-process configuration isolation](../tests/AGENTS.md#child-process-configuration-isolation) | +| Executable-boundary integration | The behavior requires starting the compiled tracker as a child process, such as OS-signal handling. | The native executable starts and reacts correctly at its process boundary. | Container-image behavior or interoperability with an external BitTorrent client. | [Native tracker fixture](../tests/common/native_tracker.rs) | [Child-process configuration isolation](../tests/AGENTS.md#child-process-configuration-isolation) | | Container E2E | The tracker must be exercised as the built container artifact with project-controlled clients. | The image builds and tracker behavior works through its network boundary. | Interoperability with a production BitTorrent client or every database backend. | [`e2e_tests_runner`](../packages/e2e-tools/README.md#binaries) | [E2E tools usage](../packages/e2e-tools/README.md); [container workflow](../.github/workflows/container.yaml) | | Container plus qBittorrent E2E | Compatibility must be demonstrated against a real BitTorrent client and configured database backend. | The containerized tracker interoperates with qBittorrent for the selected backend. | Isolated package behavior or exhaustive coverage of all failure paths. | [`qbittorrent_e2e_runner`](../packages/e2e-tools/README.md#binaries) | [E2E tools usage](../packages/e2e-tools/README.md); [container workflow](../.github/workflows/container.yaml) | | Database compatibility | A persistence change affects MySQL or PostgreSQL driver behavior or supported-version compatibility. | The selected tracker-core database-driver scenarios work against the workflow's version matrix. | Complete tracker container behavior or SQLite behavior not covered by the scenario. | [Database compatibility workflow](../.github/workflows/db-compatibility.yaml) | [Database compatibility workflow](../.github/workflows/db-compatibility.yaml); [package testing guidance](../packages/AGENTS.md#testing-packages) | @@ -88,6 +88,9 @@ the offline local-link policy in `lychee.toml`. ## Writing Maintainable Tests +Tracked repository test code is Rust. The operational policy and its narrowly +defined Python exception are in the [root integration-test guidance](../tests/AGENTS.md#test-implementation-language). + The [unit-test skill](../.github/skills/dev/testing/write-unit-test/SKILL.md) is the source of truth for Test Desiderata, behavior-focused naming, visible Arrange-Act-Assert structure, deterministic clocks, isolation, and lifecycle diff --git a/tests/AGENTS.md b/tests/AGENTS.md index 94e5ad5b6..adde5c9bf 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -130,6 +130,12 @@ configuration. Scenario functions run sequentially against that shared instance. ## Test Infrastructure Requirements +## Test Implementation Language + +All tracked repository test code must be Rust. Python is permitted only for +non-test external tooling when separately justified. Do not add Python scripts +as test automation, test fixtures, or test assertions. + All integration tests at this level must: 1. **Use port `0` for bind addresses by default**: The OS assigns free ephemeral ports, From 763b09ecde6e076c4d9c6efb5fa6270c1c0dd9cd Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 18:05:56 +0100 Subject: [PATCH 22/44] docs(issues): plan verification-type guidance for AI agents Add an approved issue-local plan for issue #2151 that distinguishes three verification activities: maintained Rust automatic tests, real human-oriented manual verification recorded as evidence, and disposable issue-local verification scripts that require a written rationale (and a Rust-vs-Python justification). --- .../ai-harness-verification-plan.md | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md new file mode 100644 index 000000000..b5217d27e --- /dev/null +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md @@ -0,0 +1,141 @@ +--- +semantic-links: + skill-links: + - create-issue + - write-markdown-docs + related-artifacts: + - docs/templates/ISSUE.md + - docs/templates/MANUAL-VERIFICATION-EVIDENCE.md + - docs/testing.md + - tests/AGENTS.md + - .github/skills/dev/planning/create-issue/SKILL.md + - .github/skills/dev/testing/write-unit-test/SKILL.md + - .github/agents/ + - docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +--- + +# AI Harness Verification Guidance Plan + +## Purpose + +Define and document three distinct forms of verification so AI agents produce +durable automated tests, credible manual verification evidence, and auditable +temporary automation without conflating their roles. + +This plan is a documentation-and-guidance change. It does not alter tracker +behavior or replace issue #2151's remaining manual verification work. Review +and approve this plan before implementing any task below. + +## Definitions + +### Automatic Tests + +Maintained test code in the repository that makes repeatable claims about +product behavior. It belongs at the lowest suitable test layer, runs through +the normal Rust test toolchain, and remains as regression coverage. + +All tracked repository test code is Rust. This includes test runners, fixtures, +assertion helpers, and scripts that automate test inputs or test assertions. + +### Manual Verification + +A real, human-oriented use of the completed feature or reproduction of the +fixed bug. An agent executes the specified commands or interactions against the +real artifact and records what actually happened: prerequisites, exact steps, +commands, program output, relevant tracker logs, and the resulting status. + +Manual verification complements automated tests; it is not simulated output, +and it is not satisfied merely by running a test command. One issue may record +multiple manual-verification processes in its +`manual-verification-evidence.md` artifact. + +### Disposable Verification Scripts + +Temporary, issue-local automation used to execute or capture a verification +scenario efficiently. It is neither maintained automatic test code nor the +manual-verification evidence itself. + +An agent may create such a script only when it explains in the issue +specification why that concrete scenario is better served by temporary +automation than by a maintained Rust automatic test. The script and the +rationale must be tracked inside the issue-specification folder so later +reviewers can inspect what was verified. Python is discouraged: an agent that +selects it over Rust must record why Rust was not a suitable choice for that +specific script. + +When a disposable script proves durable product behavior, promote that behavior +to maintained Rust automatic tests when practical, then remove the script. If +the script remains, the issue must document why it is still necessary and who +owns its removal. + +## Design Decisions + +- `docs/templates/ISSUE.md` remains the source of the mandatory + manual-verification requirement and points to a standard issue-local evidence + artifact. +- `docs/templates/MANUAL-VERIFICATION-EVIDENCE.md` is the reusable format for + actual manual runs. The artifact is created only when an issue performs + manual verification; the template itself does not assert that every issue has + identical scenarios. +- `tests/AGENTS.md` remains the operational authority for the Rust-only tracked + test-code rule. `docs/testing.md` links to that authority rather than + restating its exceptions. +- The `create-issue` skill governs issue-specification structure and evidence. + The `write-unit-test` skill governs maintained automatic tests. Both must + distinguish disposable verification scripts from their primary responsibility. +- Custom agents that plan, implement, review, or commit issue work must follow + the same definitions. They should link to the repository-owned guidance, + rather than reproduce policy text independently. + +## Implementation Plan + +Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. + +| ID | Status | Task | Expected result | +| -- | ------ | ---- | --------------- | +| H1 | TODO | Add manual-evidence template | Create `docs/templates/MANUAL-VERIFICATION-EVIDENCE.md` with a concise structure for the verification purpose, environment/prerequisites, one or more scenario records, exact executed commands or interactions, actual output and relevant logs, status, and follow-up notes. The template must clearly prohibit invented output. | +| H2 | TODO | Update issue template | Update `docs/templates/ISSUE.md` so the manual-verification table requires real execution evidence in issue-local `manual-verification-evidence.md`; describe the three verification forms and require a written rationale before creating a disposable script. | +| H3 | TODO | Update testing guidance | Expand `docs/testing.md` and `tests/AGENTS.md` with concise cross-links that distinguish maintained automatic Rust tests, manual verification, and disposable scripts. Keep the Rust-only tracked-test policy authoritative in `tests/AGENTS.md`. | +| H4 | TODO | Update repository skills | Update `create-issue` and `write-unit-test` guidance so issue plans define manual scenarios, require script rationale and artifact tracking, and prefer promoting durable behavioral checks into Rust automatic tests. Do not duplicate the policy across skills. | +| H5 | TODO | Update custom agents | Identify repository custom agents that create issue plans, implement tasks, review acceptance criteria, or prepare commits. Add focused links or instructions requiring the three-way verification distinction and compliance with the governing repository guidance. | +| H6 | TODO | Apply the policy to #2151 | Add `manual-verification-evidence.md` for the actual release-style configuration-path runs. Record the existing `release-cli-verification.py` as a disposable script, its historical rationale, and its replacement by Rust tests; then remove it under R7. | +| H7 | TODO | Validate documentation | Run the relevant template/skill validation if supplied, `linter markdown`, `linter cspell`, and local link checks. Review all modified instructions for one authoritative rule and working links. | + +## Acceptance Criteria + +- [ ] The repository documents automatic tests, manual verification, and + disposable verification scripts as distinct activities with clear purposes. +- [ ] New issue specs require actual, issue-local manual-verification evidence + at `manual-verification-evidence.md` when scenarios are executed. +- [ ] A disposable verification script must be issue-local and accompanied by a + concrete rationale for using temporary automation instead of a maintained + automatic test. +- [ ] Python use in disposable scripts requires a recorded case-specific reason + for not using Rust. +- [ ] Maintained, tracked test code remains Rust-only. +- [ ] Relevant templates, repository skills, and custom-agent instructions link + to consistent, repository-owned guidance. +- [ ] Documentation linters and local link checks pass. + +## Risks and Trade-offs + +- Requiring evidence artifacts can create boilerplate. The manual-evidence + template should therefore allow one concise scenario while retaining enough + structure to distinguish observed output from an expected result. +- Forcing every temporary investigation into Rust would make some operational + checks needlessly expensive. The rationale requirement keeps the exception + available while making its cost and removal explicit. +- Repeating policy in templates, skills, and agents risks divergence. The + implementation should keep full definitions in the testing guidance and use + links plus focused workflow requirements elsewhere. + +## Validation Plan + +Before completing the implementation, create a small representative +`manual-verification-evidence.md` artifact in this issue and verify that it can +record actual commands, output, and logs for more than one scenario. Confirm +that every new or updated disposable-script instruction requires both the +automatic-test rationale and, for Python, the language-choice rationale. + +Run the applicable repository validation scripts for updated skills or agents, +then run `linter markdown`, `linter cspell`, and local link checking. From 36f77f636eac08bebbbecef5f46cff2f4387bb1e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 18:06:46 +0100 Subject: [PATCH 23/44] docs(testing): define automatic, manual, and disposable verification Implement the approved AI-harness verification plan (H1-H5, H7) so agents no longer treat scripted output as manual verification. - Add docs/templates/MANUAL-VERIFICATION-EVIDENCE.md: issue-local record of real commands, observed output, tracker logs, and conclusions; invented evidence is forbidden. - Issue template: manual scenarios must be human-oriented feature use or bug reproduction with evidence in manual-verification-evidence.md; add the disposable-script rationale, path, owner, and Rust-vs-Python justification requirements. - docs/testing.md: add "Verification Types" with concrete examples of human-style manual verification and why it catches integration and UX gaps; tests/AGENTS.md links to it. - Skills (create-issue, write-unit-test) and agents (Planner, Implementer, Task Reviewer, Committer): link to the canonical guidance instead of duplicating policy. - Issue #2151: reclassify the Python verifier output as disposable- script evidence, reset M1-M5 to TODO, and extend T10 to require a real release-binary manual run (H6 stays in progress until then). --- .github/agents/committer.agent.md | 3 + .github/agents/implementer.agent.md | 9 +++ .github/agents/planner.agent.md | 7 +++ .github/agents/task-reviewer.agent.md | 8 ++- .../skills/dev/planning/create-issue/SKILL.md | 4 +- .../dev/testing/write-unit-test/SKILL.md | 8 +++ .../ISSUE.md | 62 +++++++++++-------- .../ai-harness-verification-plan.md | 18 +++--- docs/templates/ISSUE.md | 37 ++++++++--- .../templates/MANUAL-VERIFICATION-EVIDENCE.md | 52 ++++++++++++++++ docs/testing.md | 36 +++++++++++ tests/AGENTS.md | 4 ++ 12 files changed, 203 insertions(+), 45 deletions(-) create mode 100644 docs/templates/MANUAL-VERIFICATION-EVIDENCE.md diff --git a/.github/agents/committer.agent.md b/.github/agents/committer.agent.md index 5cf685f87..e13aa2c6c 100644 --- a/.github/agents/committer.agent.md +++ b/.github/agents/committer.agent.md @@ -37,6 +37,9 @@ Treat every commit request as a review-and-verify workflow, not as a blind reque - Verify that the spec's progress notes or task list reflect the current state. - If the spec is out of date, stop and ask the caller to update it before proceeding. Do not commit with a stale spec. + - For a completion commit, verify that required manual verification is backed + by issue-local `manual-verification-evidence.md`, not only test output or a + disposable verification script. See [verification types](../../docs/testing.md#verification-types). 2. **Validate the branch name.** If the current branch name starts with an issue number prefix (e.g., `42-some-description`), verify that `docs/issues/open/` contains a matching spec (file or directory starting with that number). If no match is found: diff --git a/.github/agents/implementer.agent.md b/.github/agents/implementer.agent.md index c1712ffdd..f111c2d9a 100644 --- a/.github/agents/implementer.agent.md +++ b/.github/agents/implementer.agent.md @@ -36,6 +36,10 @@ Reference: [Beck Design Rules](https://martinfowler.com/bliki/BeckDesignRules.ht - `.github/skills/dev/testing/write-unit-test/SKILL.md` — test naming and Arrange/Act/Assert pattern. - `.github/skills/dev/rust-code-quality/handle-errors-in-code/SKILL.md` — error handling. - `.github/skills/dev/git-workflow/commit-changes/SKILL.md` — commit conventions. +- Follow the [verification types](../../docs/testing.md#verification-types): keep + durable automatic behavior checks in Rust, execute manual verification against + the finished artifact, and retain a disposable verification script only with + the issue-specification rationale required by the issue template. ### ADR Discoverability Convention @@ -136,6 +140,11 @@ panic-safe cleanup, explicitly review collaborator responsibilities, resource ownership across normal and drop-path cleanup, deadline coverage, and separation between passive infrastructure and domain interpretation. +Before independent verification, perform the issue's manual scenarios against +the finished artifact and record actual commands, output, relevant logs, and +conclusions in issue-local `manual-verification-evidence.md`. Automated test +output is not a substitute for this evidence. + ### Step 6 — Request Independent Verification When all steps are complete and tests are passing, invoke the **Task Reviewer** diff --git a/.github/agents/planner.agent.md b/.github/agents/planner.agent.md index 0106b51e3..20bfc2908 100644 --- a/.github/agents/planner.agent.md +++ b/.github/agents/planner.agent.md @@ -45,6 +45,11 @@ You plan the work. You do not perform implementation changes yourself. fixtures - A post-vertical-slice design review when those concerns make the initial implementation likely to reveal material design constraints + - Human-oriented manual verification scenarios whose actual evidence will be + recorded in issue-local `manual-verification-evidence.md` + - A rationale, issue-local path, and removal/retention owner for any proposed + disposable verification script; Python also needs a case-specific reason + Rust is unsuitable 4. Classify the issue as `task`, `bug`, or `feature`, with one-sentence justification. 5. Select an implementation strategy and explain why it fits. 6. Decompose into minimal, independently verifiable tasks. @@ -67,6 +72,8 @@ evidence-based completion review. It must either create an issue-local `implementation-retrospective.md` for reusable lessons or record why no retrospective was needed in the issue progress log. +Follow the canonical [verification types](../../docs/testing.md#verification-types). + ## Output Format When finishing a planning task, respond in this order: diff --git a/.github/agents/task-reviewer.agent.md b/.github/agents/task-reviewer.agent.md index 7a87956b5..325658e37 100644 --- a/.github/agents/task-reviewer.agent.md +++ b/.github/agents/task-reviewer.agent.md @@ -48,8 +48,12 @@ pull request is opened. lessons, material design changes, or meaningful deviations from the original plan. Otherwise require a concise issue progress-log entry explaining why no retrospective was needed. -6. Report findings with concrete remediation guidance for all `FAIL` or `PENDING` items. -7. Return an overall status: +6. Confirm that mandatory manual scenarios were executed against the finished + artifact and recorded in `manual-verification-evidence.md` with actual + commands or interactions, observed output, relevant logs, and conclusions. + Do not accept automated test or disposable-script output as manual evidence. +7. Report findings with concrete remediation guidance for all `FAIL` or `PENDING` items. +8. Return an overall status: - `REVIEW PASSED` when all required criteria pass and no blocking issues remain. - `REVIEW FAILED` when any required criterion fails or blocking issues remain. diff --git a/.github/skills/dev/planning/create-issue/SKILL.md b/.github/skills/dev/planning/create-issue/SKILL.md index 02e5856ed..42fcd9a00 100644 --- a/.github/skills/dev/planning/create-issue/SKILL.md +++ b/.github/skills/dev/planning/create-issue/SKILL.md @@ -9,6 +9,7 @@ metadata: - docs/templates/ISSUE.md - docs/templates/EPIC.md - docs/templates/IMPLEMENTATION-RETROSPECTIVE.md + - docs/templates/MANUAL-VERIFICATION-EVIDENCE.md --- # Creating Issues @@ -122,7 +123,8 @@ explicitly during implementation: The draft must also include a verification policy that is explicit and enforceable: - Automatic checks to run after implementation (`linter all`, relevant tests, pre-push checks when applicable) -- Manual verification scenarios with status + evidence tracking (mandatory) +- Mandatory manual verification scenarios that describe real human-oriented feature use or bug reproduction, with status tracked in the spec and actual commands, output, and relevant logs recorded in issue-local `manual-verification-evidence.md` +- When a disposable verification script is proposed, its issue-local path, concrete automatic-test rationale, removal/retention owner, and, for Python, why Rust is unsuitable for that script - A post-implementation acceptance criteria review step - An evidence-based implementation completion review that records reusable lessons, material design changes, or deviations from the plan. Use diff --git a/.github/skills/dev/testing/write-unit-test/SKILL.md b/.github/skills/dev/testing/write-unit-test/SKILL.md index c3ae4bf69..6bc373dee 100644 --- a/.github/skills/dev/testing/write-unit-test/SKILL.md +++ b/.github/skills/dev/testing/write-unit-test/SKILL.md @@ -44,6 +44,14 @@ Reference: and Kent Beck's original papers on The repository prefers high maintainable automated coverage. +Tracked test code is Rust. A temporary issue-local verification script is not a +test: it requires an issue-specification rationale explaining why it is better +than a maintained Rust automatic test and, when written in Python, why Rust is +unsuitable. Promote durable behavioral checks into Rust tests when practical. +Manual verification is separate real use of the finished artifact; record its +actual evidence in `manual-verification-evidence.md` as defined by +[`docs/testing.md`](../../../../../docs/testing.md#verification-types). + Practical priority order: 1. Unit tests first (fast, deterministic, low maintenance) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 7851f08b8..0518ef9c8 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -305,18 +305,18 @@ first vertical slice: Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Notes / Expected Output | -| --- | ------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | -| T2 | DONE | Introduce typed source selection | Added `Info::new_with_explicit_config_toml_path(..., Option)`. Explicit paths are eagerly read and loaded from retained contents, take precedence over environment base sources, preserve their exact `PathBuf` in diagnostics, and never use parent lookup. Legacy environment and default file behavior remains on `Toml::file`. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); Rust formatting and Clippy passed. | -| T3 | DONE | Define the CLI boundary | Added a main-owned `clap` parser for `-c` / `--config-toml-path` with no `env` binding. Parser tests cover short/long forms, missing/empty values, unknown arguments, help, exit codes, and absent environment binding. `cargo test --package torrust-tracker --bin torrust-tracker` passed (7 tests). | -| T4 | DONE | Wire startup and precedence | Threaded `Option` from `main` through `app::start_with_explicit_config_toml_path`, `bootstrap::app::setup`, and `initialize_configuration` into T2 without environment mutation. Direct bootstrap tests cover every CLI-present table row: CLI only, CLI plus full TOML, CLI plus path, and CLI plus both sources. Root library tests passed (84 tests). | -| T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | -| T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | -| T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | -| T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | -| T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Manual M1-M5 release-binary scenarios passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks. Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | -| T10 | TODO | Complete Rust executable coverage | Implement the approved `rust-executable-test-plan.md`: preserve the relevant release CLI harness behavior in Rust executable-boundary tests, document the Rust-only tracked test-code policy, remove the Python harness, then repeat final validation and acceptance review. | +| ID | Status | Task | Notes / Expected Output | +| --- | ----------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | +| T2 | DONE | Introduce typed source selection | Added `Info::new_with_explicit_config_toml_path(..., Option)`. Explicit paths are eagerly read and loaded from retained contents, take precedence over environment base sources, preserve their exact `PathBuf` in diagnostics, and never use parent lookup. Legacy environment and default file behavior remains on `Toml::file`. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); Rust formatting and Clippy passed. | +| T3 | DONE | Define the CLI boundary | Added a main-owned `clap` parser for `-c` / `--config-toml-path` with no `env` binding. Parser tests cover short/long forms, missing/empty values, unknown arguments, help, exit codes, and absent environment binding. `cargo test --package torrust-tracker --bin torrust-tracker` passed (7 tests). | +| T4 | DONE | Wire startup and precedence | Threaded `Option` from `main` through `app::start_with_explicit_config_toml_path`, `bootstrap::app::setup`, and `initialize_configuration` into T2 without environment mutation. Direct bootstrap tests cover every CLI-present table row: CLI only, CLI plus full TOML, CLI plus path, and CLI plus both sources. Root library tests passed (84 tests). | +| T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | +| T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | +| T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | +| T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | +| T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Scripted M1-M5 release-binary scenarios (disposable Python verifier) passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks; this is not human-oriented manual verification (see T10). Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | +| T10 | IN_PROGRESS | Complete Rust executable coverage and manual verification | Implement the approved `rust-executable-test-plan.md`: preserve the relevant release CLI harness behavior in Rust executable-boundary tests, document the Rust-only tracked test-code policy, remove the Python harness, then repeat final validation and acceptance review. Perform a real release-style manual verification after automatic coverage is complete; record actual commands, output, and tracker logs in `manual-verification-evidence.md`. | Each task must be independently buildable and tested. T1 is a behavior-preserving safety-net change; T2 is a configuration refactor; T3-T4 @@ -333,7 +333,7 @@ are the deployable feature; later tasks extend verification and documentation. - [x] First passing CLI-only vertical slice reviewed for ownership, cleanup, deadline, and ADR decisions - [x] Implementation completed - [x] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) -- [x] Manual verification scenarios executed and recorded (status + evidence) +- [ ] Manual verification scenarios executed and recorded in `manual-verification-evidence.md` - [x] Acceptance criteria reviewed after implementation and updated with evidence - [x] Evidence-based implementation completion review recorded: progress log states why no retrospective was needed - [x] Reviewer validated acceptance criteria and updated checkboxes @@ -370,6 +370,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-08 10:30 UTC - Maintainer / GitHub Copilot - Grouped executable configuration scenarios by their contracts: `base_source_precedence` and `per_value_overrides`. The Cargo test entry point retains only target-level configuration and the shared-fixture import; `invalid_sources` remains the planned next module. - 2026-09-08 11:00 UTC - GitHub Copilot - Completed R3a and R4. R3a proves ignored base sources are not merged at the configuration-package layer. R4 adds compiled-child invalid-source contracts for parser errors and explicit-source failures, with fixture-owned workspaces, child-only environment isolation, deadline-bounded wait/reap/output handling, and post-reap candidate-port probes. Independent review initially found expected-failure drop and timeout cleanup gaps; they were corrected and the re-review approved the lifecycle. Focused configuration (129), CLI configuration (15), and lifecycle (10) tests passed. - 2026-09-08 12:00 UTC - GitHub Copilot - Completed R5. Added the Unix unreadable regular-file executable contract. The fixture creates a valid mode-`000` source and probes effective permission enforcement before spawning: normal users exercise a path-bearing `Permission denied` exit, while privileged runners explicitly skip without starting a child. Independent review found restoration could panic or be lost before workspace cleanup; restoration is now fallible in normal cleanup, non-panicking and synchronous in drop cleanup, and covered by regressions. CLI configuration (17) and lifecycle (11) tests passed; re-review approved the cleanup design. +- 2026-09-08 17:00 UTC - Maintainer / GitHub Copilot - Approved and implemented repository guidance distinguishing Rust automatic tests, real human-oriented manual verification, and disposable issue-local verification scripts. Added the manual-evidence template and reset M1-M5: prior Python-script output is historical disposable-script evidence, not manual verification. T10 now requires real release-binary runs with actual commands, output, and tracker logs recorded in `manual-verification-evidence.md`. ## Acceptance Criteria @@ -416,15 +417,18 @@ are the deployable feature; later tasks extend verification and documentation. Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. -| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | -| --- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| M1 | CLI path only | Start the release binary with `--config-toml-path` pointing to an isolated valid file and no configuration-source variables. | The tracker reads that file, starts configured services, and exits cleanly on SIGTERM. | DONE | `.tmp/issue-2151-manual/m1.log`; health endpoint `127.0.0.1:43151`, exit `0`. | -| M2 | CLI source precedence | Prepare three valid configurations that differ only in `health_check_api.bind_address` (three distinct fixed loopback ports). Supply one via `TORRUST_TRACKER_CONFIG_TOML`, one via `TORRUST_TRACKER_CONFIG_TOML_PATH`, and the third via `--config-toml-path`. | The `HEALTH CHECK API: Started on:` log line reports the port from the CLI-selected file. | DONE | `.tmp/issue-2151-manual/m2.log`; CLI port `43152` selected over env ports `43153` and `43154`, exit `0`. | -| M3 | Per-value override | Start with `--config-toml-path` and a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_*` value. | The override wins for its path while other values come from the file. | DONE | `.tmp/issue-2151-manual/m3.log`; override port `43156` selected over CLI file port `43155`, exit `0`. | -| M4 | Invalid CLI source | Start with (a) `--config-toml-path` with no value, (b) an empty supplied path, (c) a nonexistent absolute file, (d) a directory, (e) an unreadable regular file, (f) malformed TOML, and (g) a relative filename that exists only in a parent directory of the CWD. | Cases (a-b) exit `2` with a descriptive usage error. Cases (c-g) exit `1` with an error naming the path; case (g) must not load the parent-directory file. No case creates a listener. | DONE | `.tmp/issue-2151-manual/m4-*.log`; cases (a-b) exit `2`, cases (c-g) exit `1`, including `m4-unreadable.log` (`Permission denied`); no-listener probes passed. | -| M5 | Parallel child isolation (Unix) | Launch two binaries concurrently with different CLI paths, isolated storage, and port-zero configuration. | Both start with their own configuration; neither reads or overwrites the other's source. | DONE | `.tmp/issue-2151-manual/m5-first.log`, `m5-second.log`; distinct discovered port-zero health endpoints, workspace-local SQLite paths, and clean SIGTERM exits. | - -Manual verification is mandatory. Record a failing scenario and its diagnosis in +| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | +| --- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | --------------------------------------- | +| M1 | CLI path only | Start the release binary with `--config-toml-path` pointing to an isolated valid file and no configuration-source variables. | The tracker reads that file, starts configured services, and exits cleanly on SIGTERM. | TODO | `manual-verification-evidence.md` (V1). | +| M2 | CLI source precedence | Prepare three valid configurations that differ only in `health_check_api.bind_address` (three distinct fixed loopback ports). Supply one via `TORRUST_TRACKER_CONFIG_TOML`, one via `TORRUST_TRACKER_CONFIG_TOML_PATH`, and the third via `--config-toml-path`. | The `HEALTH CHECK API: Started on:` log line reports the port from the CLI-selected file. | TODO | `manual-verification-evidence.md` (V2). | +| M3 | Per-value override | Start with `--config-toml-path` and a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_*` value. | The override wins for its path while other values come from the file. | TODO | `manual-verification-evidence.md` (V3). | +| M4 | Invalid CLI source | Start with (a) `--config-toml-path` with no value, (b) an empty supplied path, (c) a nonexistent absolute file, (d) a directory, (e) an unreadable regular file, (f) malformed TOML, and (g) a relative filename that exists only in a parent directory of the CWD. | Cases (a-b) exit `2` with a descriptive usage error. Cases (c-g) exit `1` with an error naming the path; case (g) must not load the parent-directory file. No case creates a listener. | TODO | `manual-verification-evidence.md` (V4). | +| M5 | Parallel child isolation (Unix) | Launch two binaries concurrently with different CLI paths, isolated storage, and port-zero configuration. | Both start with their own configuration; neither reads or overwrites the other's source. | TODO | `manual-verification-evidence.md` (V5). | + +Manual verification is mandatory. Execute these release-style scenarios against +the built artifact and record actual setup, commands, output, and tracker logs +in `manual-verification-evidence.md`; the earlier disposable script output does +not satisfy this requirement. Record a failing scenario and its diagnosis in the progress log before proceeding. ### Acceptance Verification @@ -457,10 +461,16 @@ the progress log before proceeding. ## Temporary Release CLI Evidence [`release-cli-verification.py`](release-cli-verification.py) recorded the initial -release-binary evidence for M1-M5. It is not a durable repository test because -tracked test code must be Rust. The approved replacement plan is -[`rust-executable-test-plan.md`](rust-executable-test-plan.md); T10 removes this -Python artifact only after its relevant behavior is covered by Rust tests. +release-binary evidence for M1-M5. It is not a durable repository test or +manual-verification evidence. It is a disposable verification script kept in +this issue folder so its historical verification can be audited. + +Temporary automation was useful while the manual matrix was being explored, +because it consistently created isolated configurations and captured multiple +child-process outputs. Its durable product-behavior claims are now covered by +Rust executable-boundary tests, so T10 removes it. Python was selected before +the Rust-only test policy existed; no current justification supports retaining +Python for this verification. Until T10 is complete, build the binary first, then run the temporary evidence procedure from the repository root: diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md index b5217d27e..789127554 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md @@ -91,15 +91,15 @@ owns its removal. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| -- | ------ | ---- | --------------- | -| H1 | TODO | Add manual-evidence template | Create `docs/templates/MANUAL-VERIFICATION-EVIDENCE.md` with a concise structure for the verification purpose, environment/prerequisites, one or more scenario records, exact executed commands or interactions, actual output and relevant logs, status, and follow-up notes. The template must clearly prohibit invented output. | -| H2 | TODO | Update issue template | Update `docs/templates/ISSUE.md` so the manual-verification table requires real execution evidence in issue-local `manual-verification-evidence.md`; describe the three verification forms and require a written rationale before creating a disposable script. | -| H3 | TODO | Update testing guidance | Expand `docs/testing.md` and `tests/AGENTS.md` with concise cross-links that distinguish maintained automatic Rust tests, manual verification, and disposable scripts. Keep the Rust-only tracked-test policy authoritative in `tests/AGENTS.md`. | -| H4 | TODO | Update repository skills | Update `create-issue` and `write-unit-test` guidance so issue plans define manual scenarios, require script rationale and artifact tracking, and prefer promoting durable behavioral checks into Rust automatic tests. Do not duplicate the policy across skills. | -| H5 | TODO | Update custom agents | Identify repository custom agents that create issue plans, implement tasks, review acceptance criteria, or prepare commits. Add focused links or instructions requiring the three-way verification distinction and compliance with the governing repository guidance. | -| H6 | TODO | Apply the policy to #2151 | Add `manual-verification-evidence.md` for the actual release-style configuration-path runs. Record the existing `release-cli-verification.py` as a disposable script, its historical rationale, and its replacement by Rust tests; then remove it under R7. | -| H7 | TODO | Validate documentation | Run the relevant template/skill validation if supplied, `linter markdown`, `linter cspell`, and local link checks. Review all modified instructions for one authoritative rule and working links. | +| ID | Status | Task | Expected result | +| --- | ----------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| H1 | DONE | Add manual-evidence template | Added `docs/templates/MANUAL-VERIFICATION-EVIDENCE.md` with purpose, environment, multiple scenario records, actual steps, observed output/logs, conclusions, and follow-up. It forbids invented evidence. | +| H2 | DONE | Update issue template | `docs/templates/ISSUE.md` now requires issue-local manual evidence, defines real human-oriented manual verification, and records the rationale, location, and ownership rules for disposable scripts. | +| H3 | DONE | Update testing guidance | `docs/testing.md` distinguishes all three verification types; `tests/AGENTS.md` retains the authoritative Rust-only test-code policy and links to that guidance. | +| H4 | DONE | Update repository skills | Updated `create-issue` and `write-unit-test` with manual-evidence, disposable-script-rationale, Rust preference, and durable-test-promotion requirements. | +| H5 | DONE | Update custom agents | Updated Planner, Implementer, Task Reviewer, and Committer: the roles that plan, perform, validate, and finalize issue work. They link to or enforce the canonical verification guidance. | +| H6 | IN_PROGRESS | Apply the policy to #2151 | The issue now records `release-cli-verification.py` as historical disposable automation and resets M1-M5 for real release-style manual runs. Create `manual-verification-evidence.md`, remove the script under R7, then record actual results. | +| H7 | DONE | Validate documentation | `validate-skill-links.sh`, `linter markdown`, `linter cspell`, `linter lychee`, and `git diff --check` passed. Reviewed the resulting guidance: `tests/AGENTS.md` remains the authoritative Rust-only test-code rule; other artifacts link to it or to `docs/testing.md` rather than defining competing exceptions. | ## Acceptance Criteria diff --git a/docs/templates/ISSUE.md b/docs/templates/ISSUE.md index 6b6486706..6c2bd2210 100644 --- a/docs/templates/ISSUE.md +++ b/docs/templates/ISSUE.md @@ -104,7 +104,7 @@ commit with GPG. - [ ] (Optional, recommended for complex issues) Spec-only PR merged into `develop` before implementation - [ ] Implementation completed - [ ] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) -- [ ] Manual verification scenarios executed and recorded (status + evidence) +- [ ] Manual verification scenarios executed and recorded in issue-local `manual-verification-evidence.md` - [ ] Acceptance criteria reviewed after implementation and updated with evidence - [ ] Evidence-based implementation completion review recorded: issue-local retrospective created for material discoveries, or progress log states why none was needed - [ ] Reviewer validated acceptance criteria and updated checkboxes @@ -124,7 +124,7 @@ Append one line per meaningful update. - [ ] AC2: {Behavior/outcome that must be true} - [ ] `linter all` exits with code `0` - [ ] Relevant tests pass -- [ ] Manual verification scenarios are executed and documented (status + evidence) +- [ ] Manual verification scenarios are executed and documented in issue-local `manual-verification-evidence.md` - [ ] Acceptance criteria are re-reviewed after implementation and reflect actual behavior - [ ] Documentation is updated when behavior/workflow changes @@ -142,16 +142,39 @@ Define verification before implementation starts and execute it before closing t Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. -| ID | Scenario | Command/Steps | Expected Result | Status | Evidence | -| --- | ----------------- | ------------------------------------ | ------------------- | ------ | ---------------------------- | -| M1 | {Manual scenario} | {Exact command or interaction steps} | {Expected behavior} | TODO | {log/output/screenshot/path} | -| M2 | {Manual scenario} | {Exact command or interaction steps} | {Expected behavior} | TODO | {log/output/screenshot/path} | +| ID | Scenario | Human-oriented command/steps | Expected Result | Status | Evidence | +| --- | ----------------- | ------------------------------------------------- | ------------------- | ------ | -------------------------------------------- | +| M1 | {Manual scenario} | {Exact command or interaction actually performed} | {Expected behavior} | TODO | `manual-verification-evidence.md` section V1 | +| M2 | {Manual scenario} | {Exact command or interaction actually performed} | {Expected behavior} | TODO | `manual-verification-evidence.md` section V2 | Notes: -- Manual verification is mandatory even when automated tests pass. +- Manual verification is mandatory even when automated tests pass. It is a + real human-oriented use of the feature or reproduction of the bug fix, not a + simulated result and not merely running automated tests. +- Create `manual-verification-evidence.md` from + `docs/templates/MANUAL-VERIFICATION-EVIDENCE.md` when executing these + scenarios. Record actual prerequisites, actions, commands, program output, + relevant tracker logs, and outcomes there. - If a scenario fails, record the failure and diagnosis in the progress log before proceeding. +### Disposable Verification Scripts + +Temporary scripts may automate an issue-local verification scenario, but they +are neither maintained automatic tests nor manual-verification evidence. Before +creating one, record in the issue specification: + +- why temporary automation is better for this concrete scenario than a + maintained Rust automatic test; +- the script's issue-local path, what it verifies, and its intended removal or + retention owner; and +- when using Python instead of Rust, why Rust is not suitable for that specific + script. + +Keep the script in the issue-specification folder so later reviewers can inspect +the verification performed. Promote durable product-behavior checks into Rust +automatic tests when practical, then remove the disposable script. + ### Acceptance Verification | AC ID | Status (`TODO`/`DONE`) | Evidence | diff --git a/docs/templates/MANUAL-VERIFICATION-EVIDENCE.md b/docs/templates/MANUAL-VERIFICATION-EVIDENCE.md new file mode 100644 index 000000000..6e9a077fe --- /dev/null +++ b/docs/templates/MANUAL-VERIFICATION-EVIDENCE.md @@ -0,0 +1,52 @@ +--- +doc-type: manual-verification-evidence +issue-spec: docs/issues/open/{number}-{short-description}/ISSUE.md +last-updated-utc: YYYY-MM-DD HH:MM +--- + +# Manual Verification Evidence + +## Purpose + +Record real, human-oriented verification of the completed behavior. This is +evidence from commands or interactions actually performed against the artifact; +do not invent commands, output, logs, or results. + +## Environment and Prerequisites + +- Date and time (UTC): +- Artifact under test: +- Operating system / environment: +- Prerequisites and setup performed: + +## Verification Processes + +Add one section per manual verification process. A process may cover one or +more issue-spec scenarios when its steps and evidence clearly identify each +result. + +### V1 - {Scenario Name} + +- Goal: +- Initial state: +- Status: `TODO` / `IN_PROGRESS` / `DONE` / `FAILED` / `BLOCKED` + +#### Steps Performed + +1. {Human-oriented action or exact command actually executed.} +2. {Next action or command.} + +#### Observed Result + +```text +{Actual relevant program output, response, or tracker log.} +``` + +#### Conclusion + +{State whether the observed result met the issue scenario's expected result.} + +## Failures and Follow-up + +Record any failed or blocked process, diagnosis, remediation, and whether the +scenario was rerun. diff --git a/docs/testing.md b/docs/testing.md index c31d30060..f4a67dc1e 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -74,6 +74,42 @@ respective responsibilities: | CI | Is the merge authority. It runs workflow-selected validation, including container and qBittorrent E2E coverage where applicable. | [Testing workflow](../.github/workflows/testing.yaml); [container workflow](../.github/workflows/container.yaml) | | Manual verification | Complements automated evidence with scenario status and recorded evidence in the relevant issue specification. | [Issue-specification workflow](issues/README.md) | +## Verification Types + +Use three distinct verification activities; they produce different evidence and +must not substitute for one another. + +| Activity | Purpose | Required evidence | +| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Automatic tests | Maintained, repeatable claims about product behavior at the lowest suitable test layer. | Rust test code executed through the repository test toolchain. | +| Manual verification | Real human-oriented use of a finished feature or reproduction of a fixed bug, especially to reveal integration and usability gaps. | Actual steps, commands, program output, relevant tracker logs, and conclusions in the issue-local `manual-verification-evidence.md`. | +| Disposable verification script | Temporary issue-local automation that efficiently drives or captures a concrete verification scenario. | The script, its automatic-test rationale, and its removal/retention owner in the issue specification. Python additionally requires a recorded reason Rust is unsuitable. | + +Automatic tests should absorb durable product-behavior checks from disposable +scripts when practical. Manual verification remains necessary after automated +tests pass because it verifies how a person actually uses the finished artifact. +It routinely exposes integration and usability gaps that tests written by the +implementer do not, such as "how am I supposed to use this?" moments, unclear +error messages, or missing documentation. + +Examples of human-oriented manual verification: + +- **Bug fix:** reproduce the original bug on the fixed build using the same + steps the reporter would follow, and confirm it no longer happens. +- **New CLI option:** build the release binary, read only the `--help` output + and the documentation, start the tracker the way an operator would, and check + the logs show the expected effect. +- **New configuration behavior:** write a real configuration file, start the + tracker, and exercise the affected service with a real client (for example + `tracker_client` or `curl`) instead of asserting on internal state. +- **Error path:** trigger the failure as a user would (wrong path, bad file, + missing permission) and judge whether the message tells the user what to fix. + +Each example produces evidence that is a recording of what really happened: +the exact commands, their output, and the relevant tracker log lines. + +For the operational requirements, see [root integration-test guidance](../tests/AGENTS.md#test-implementation-language) and the issue template's [manual verification and disposable script sections](templates/ISSUE.md#manual-verification-scenarios). + ## Advisory External Link Monitoring The [External Link Check workflow](../.github/workflows/external-link-check.yaml) runs Lychee diff --git a/tests/AGENTS.md b/tests/AGENTS.md index adde5c9bf..e6b88b418 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -136,6 +136,10 @@ All tracked repository test code must be Rust. Python is permitted only for non-test external tooling when separately justified. Do not add Python scripts as test automation, test fixtures, or test assertions. +Manual verification and disposable verification scripts are not tracked test +code. Follow [the testing strategy](../docs/testing.md#verification-types) and +the issue template for their separate evidence and rationale requirements. + All integration tests at this level must: 1. **Use port `0` for bind addresses by default**: The OS assigns free ephemeral ports, From 7ddf16a6dc22861197450b0ca6bb854a5847fc6f Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 18:14:21 +0100 Subject: [PATCH 24/44] docs(configuration): remove disposable Python verifier --- .../ISSUE.md | 34 +-- .../ai-harness-verification-plan.md | 2 +- .../release-cli-verification.py | 239 ------------------ .../rust-executable-test-plan.md | 7 +- 4 files changed, 8 insertions(+), 274 deletions(-) delete mode 100644 docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 0518ef9c8..112f20ffe 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -13,7 +13,6 @@ semantic-links: skill-links: - create-issue related-artifacts: - - docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py - docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md - src/main.rs - src/app.rs @@ -316,7 +315,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | | T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | | T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Scripted M1-M5 release-binary scenarios (disposable Python verifier) passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks; this is not human-oriented manual verification (see T10). Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | -| T10 | IN_PROGRESS | Complete Rust executable coverage and manual verification | Implement the approved `rust-executable-test-plan.md`: preserve the relevant release CLI harness behavior in Rust executable-boundary tests, document the Rust-only tracked test-code policy, remove the Python harness, then repeat final validation and acceptance review. Perform a real release-style manual verification after automatic coverage is complete; record actual commands, output, and tracker logs in `manual-verification-evidence.md`. | +| T10 | IN_PROGRESS | Complete Rust executable coverage and manual verification | The approved `rust-executable-test-plan.md` preserved the release-verifier behavior in Rust executable-boundary tests, documented the Rust-only tracked test-code policy, and removed the Python harness. Perform a real release-style manual verification; record actual commands, output, and tracker logs in `manual-verification-evidence.md`, then repeat final validation and acceptance review. | Each task must be independently buildable and tested. T1 is a behavior-preserving safety-net change; T2 is a configuration refactor; T3-T4 @@ -371,6 +370,7 @@ are the deployable feature; later tasks extend verification and documentation. - 2026-09-08 11:00 UTC - GitHub Copilot - Completed R3a and R4. R3a proves ignored base sources are not merged at the configuration-package layer. R4 adds compiled-child invalid-source contracts for parser errors and explicit-source failures, with fixture-owned workspaces, child-only environment isolation, deadline-bounded wait/reap/output handling, and post-reap candidate-port probes. Independent review initially found expected-failure drop and timeout cleanup gaps; they were corrected and the re-review approved the lifecycle. Focused configuration (129), CLI configuration (15), and lifecycle (10) tests passed. - 2026-09-08 12:00 UTC - GitHub Copilot - Completed R5. Added the Unix unreadable regular-file executable contract. The fixture creates a valid mode-`000` source and probes effective permission enforcement before spawning: normal users exercise a path-bearing `Permission denied` exit, while privileged runners explicitly skip without starting a child. Independent review found restoration could panic or be lost before workspace cleanup; restoration is now fallible in normal cleanup, non-panicking and synchronous in drop cleanup, and covered by regressions. CLI configuration (17) and lifecycle (11) tests passed; re-review approved the cleanup design. - 2026-09-08 17:00 UTC - Maintainer / GitHub Copilot - Approved and implemented repository guidance distinguishing Rust automatic tests, real human-oriented manual verification, and disposable issue-local verification scripts. Added the manual-evidence template and reset M1-M5: prior Python-script output is historical disposable-script evidence, not manual verification. T10 now requires real release-binary runs with actual commands, output, and tracker logs recorded in `manual-verification-evidence.md`. +- 2026-09-08 17:10 UTC - GitHub Copilot - Completed R7: removed the disposable Python verifier after its durable behavior checks were preserved in Rust executable-boundary tests. Real release-binary manual verification for M1-M5 remains pending in `manual-verification-evidence.md`. ## Acceptance Criteria @@ -395,7 +395,7 @@ are the deployable feature; later tasks extend verification and documentation. paths, isolated storage, and port-zero bindings without configuration-source environment variables. - [x] `linter all` exits with code `0` and relevant tests pass. -- [x] Manual verification scenarios are executed and documented (status + evidence). +- [ ] Manual verification scenarios are executed and documented (status + evidence). - [x] Acceptance criteria are re-reviewed after implementation and reflect actual behavior. - [x] Documentation states final interfaces and precedence without contradicting implementation. @@ -441,7 +441,7 @@ the progress log before proceeding. | AC4 | DONE | T6 table-driven mandatory/default tests (129 configuration tests passed). | | AC5 | DONE | Parser/configuration tests; M4 release-binary command, mode, exit, diagnostic, and no-listener evidence in `.tmp/issue-2151-manual/summary.txt`. | | AC6 | DONE | `cargo test --test lifecycle-signals` (8 passed); M5. | -| Quality and documentation | DONE | Pre-commit gate, test runs, T8 review, and manual evidence. | +| Quality and documentation | TODO | Pre-commit gate, test runs, and T8 review passed; real manual evidence remains pending. | ## Risks and Trade-offs @@ -458,32 +458,6 @@ the progress log before proceeding. | A refactor exposes complete TOML content in diagnostics. | Preserve redaction behavior and add no secret-bearing logs without an explicit security decision. | | The issue grows into general configuration redesign. | Limit it to a file-path argument and source-selection plumbing. | -## Temporary Release CLI Evidence - -[`release-cli-verification.py`](release-cli-verification.py) recorded the initial -release-binary evidence for M1-M5. It is not a durable repository test or -manual-verification evidence. It is a disposable verification script kept in -this issue folder so its historical verification can be audited. - -Temporary automation was useful while the manual matrix was being explored, -because it consistently created isolated configurations and captured multiple -child-process outputs. Its durable product-behavior claims are now covered by -Rust executable-boundary tests, so T10 removes it. Python was selected before -the Rust-only test policy existed; no current justification supports retaining -Python for this verification. - -Until T10 is complete, build the binary first, then run the temporary evidence -procedure from the repository root: - -```text -cargo build --release --bin torrust-tracker -python3 docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py -``` - -The script creates configurations, SQLite storage, process logs, and its concise -summary only under `.tmp/issue-2151-manual/`. Those runtime artifacts are -deliberately git-ignored; the script is the durable, reviewed evidence procedure. - ## Implementation Completion Review After implementation, compare the result with this specification and record diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md index 789127554..03e6cd9f5 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md @@ -98,7 +98,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | H3 | DONE | Update testing guidance | `docs/testing.md` distinguishes all three verification types; `tests/AGENTS.md` retains the authoritative Rust-only test-code policy and links to that guidance. | | H4 | DONE | Update repository skills | Updated `create-issue` and `write-unit-test` with manual-evidence, disposable-script-rationale, Rust preference, and durable-test-promotion requirements. | | H5 | DONE | Update custom agents | Updated Planner, Implementer, Task Reviewer, and Committer: the roles that plan, perform, validate, and finalize issue work. They link to or enforce the canonical verification guidance. | -| H6 | IN_PROGRESS | Apply the policy to #2151 | The issue now records `release-cli-verification.py` as historical disposable automation and resets M1-M5 for real release-style manual runs. Create `manual-verification-evidence.md`, remove the script under R7, then record actual results. | +| H6 | IN_PROGRESS | Apply the policy to #2151 | The issue records the removed Python verifier as historical disposable automation, and M1-M5 are reset for real release-style manual runs. Create `manual-verification-evidence.md` and record actual results. | | H7 | DONE | Validate documentation | `validate-skill-links.sh`, `linter markdown`, `linter cspell`, `linter lychee`, and `git diff --check` passed. Reviewed the resulting guidance: `tests/AGENTS.md` remains the authoritative Rust-only test-code rule; other artifacts link to it or to `docs/testing.md` rather than defining competing exceptions. | ## Acceptance Criteria diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py b/docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py deleted file mode 100644 index 45fbefcb0..000000000 --- a/docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/env python3 -"""Reproducibly verify the release-binary scenarios for issue #2151. - -Build the binary first with: - cargo build --release --bin torrust-tracker - -The script writes temporary configurations and evidence only to the repository's -ignored `.tmp/issue-2151-manual/` directory. -""" - -from __future__ import annotations - -import os -import re -import shutil -import signal -import socket -import subprocess -import time -import urllib.request -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[4] -WORK = ROOT / ".tmp" / "issue-2151-manual" -BINARY = ROOT / "target" / "release" / "torrust-tracker" -SOURCE_ENV = ("TORRUST_TRACKER_CONFIG_TOML", "TORRUST_TRACKER_CONFIG_TOML_PATH") -SUMMARY = WORK / "summary.txt" - - -def record(message: str) -> None: - with SUMMARY.open("a", encoding="utf-8") as summary: - summary.write(f"{message}\n") - - -def config(health_port: int, storage_path: Path | None = None) -> str: - database = "" - if storage_path: - storage_path.mkdir(parents=True, exist_ok=True) - database = f'''\n[core.database]\ndriver = "sqlite3"\npath = "{storage_path / "sqlite3.db"}"\n''' - - return f'''[metadata] -app = "torrust-tracker" -purpose = "configuration" -schema_version = "3.0.0" - -[logging] -trace_filter = "info" - -[core] -listed = false -private = false -{database} -[health_check_api] -bind_address = "127.0.0.1:{health_port}" -''' - - -def clean_environment(extra: dict[str, str] | None = None) -> dict[str, str]: - environment = os.environ.copy() - for name in SOURCE_ENV: - environment.pop(name, None) - if extra: - environment.update(extra) - return environment - - -def start(name: str, arguments: list[str], extra: dict[str, str] | None = None) -> subprocess.Popen[str]: - log = (WORK / f"{name}.log").open("w", encoding="utf-8") - process = subprocess.Popen( - [str(BINARY), *arguments], - cwd=ROOT, - env=clean_environment(extra), - stdout=log, - stderr=subprocess.STDOUT, - text=True, - ) - process._issue_2151_log = log # type: ignore[attr-defined] - return process - - -def finish(process: subprocess.Popen[str], timeout: float = 30) -> tuple[int, str]: - if process.poll() is None: - process.send_signal(signal.SIGTERM) - try: - code = process.wait(timeout) - except subprocess.TimeoutExpired: - process.kill() - code = process.wait(5) - raise AssertionError(f"process timed out and was killed with {code}") - process._issue_2151_log.close() # type: ignore[attr-defined] - return code, Path(process._issue_2151_log.name).read_text(encoding="utf-8") # type: ignore[attr-defined] - - -def wait_for_health(port: int, process: subprocess.Popen[str]) -> None: - deadline = time.monotonic() + 10 - url = f"http://127.0.0.1:{port}/health_check" - while time.monotonic() < deadline: - if process.poll() is not None: - _, output = finish(process) - raise AssertionError(f"process exited before health endpoint was ready:\n{output}") - try: - with urllib.request.urlopen(url, timeout=0.5) as response: - if response.status == 200: - return - except OSError: - pass - time.sleep(0.05) - _, output = finish(process) - raise AssertionError(f"timed out waiting for {url}:\n{output}") - - -def wait_for_port_zero_health(process: subprocess.Popen[str]) -> int: - deadline = time.monotonic() + 10 - pattern = re.compile(r"HEALTH CHECK API.*Started on: http://127\.0\.0\.1:(\d+)") - while time.monotonic() < deadline: - if process.poll() is not None: - _, output = finish(process) - raise AssertionError(f"process exited before readiness:\n{output}") - log_path = Path(process._issue_2151_log.name) # type: ignore[attr-defined] - match = pattern.search(log_path.read_text(encoding="utf-8")) - if match: - port = int(match.group(1)) - wait_for_health(port, process) - return port - time.sleep(0.05) - _, output = finish(process) - raise AssertionError(f"timed out discovering port-zero health endpoint:\n{output}") - - -def assert_exits(name: str, arguments: list[str], expected_code: int, expected_text: str, cwd: Path = ROOT) -> None: - result = subprocess.run( - [str(BINARY), *arguments], - cwd=cwd, - env=clean_environment(), - text=True, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - timeout=10, - ) - (WORK / f"{name}.log").write_text(result.stdout, encoding="utf-8") - assert result.returncode == expected_code, (name, result.returncode, result.stdout) - assert expected_text in result.stdout, (name, expected_text, result.stdout) - record( - f"{name}: command={BINARY} {' '.join(arguments)!r}; cwd={cwd}; " - f"exit={result.returncode}; expected-text={expected_text!r}; PASS" - ) - - -def assert_port_is_available(port: int) -> None: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listener: - listener.bind(("127.0.0.1", port)) - record(f"listener-probe: bind 127.0.0.1:{port} after failed startup; PASS (port available)") - - -def main() -> None: - if not BINARY.is_file(): - raise SystemExit(f"missing release binary; run `cargo build --release --bin torrust-tracker`: {BINARY}") - shutil.rmtree(WORK, ignore_errors=True) - WORK.mkdir(parents=True) - - # M1: CLI-only source. - m1 = WORK / "m1.toml" - m1.write_text(config(43151), encoding="utf-8") - process = start("m1", ["--config-toml-path", str(m1)]) - wait_for_health(43151, process) - code, output = finish(process) - assert code == 0 and "successfully shutdown" in output, output - - # M2: CLI path outranks both environment base sources. - cli = WORK / "m2-cli.toml" - env_path = WORK / "m2-env-path.toml" - cli.write_text(config(43152), encoding="utf-8") - env_path.write_text(config(43153), encoding="utf-8") - process = start( - "m2", - ["--config-toml-path", str(cli)], - {"TORRUST_TRACKER_CONFIG_TOML": config(43154), "TORRUST_TRACKER_CONFIG_TOML_PATH": str(env_path)}, - ) - wait_for_health(43152, process) - code, output = finish(process) - assert code == 0, output - - # M3: override applies over a CLI-selected base file. - m3 = WORK / "m3.toml" - m3.write_text(config(43155), encoding="utf-8") - process = start( - "m3", - ["--config-toml-path", str(m3)], - {"TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS": "127.0.0.1:43156"}, - ) - wait_for_health(43156, process) - code, output = finish(process) - assert code == 0, output - - # M4: usage and strict source errors; no source is valid or reaches a listener. - assert_exits("m4-missing-value", ["--config-toml-path"], 2, "a value is required") - assert_exits("m4-empty-value", ["--config-toml-path", ""], 2, "must not be empty") - missing = WORK / "does-not-exist.toml" - assert_exits("m4-missing-file", ["--config-toml-path", str(missing)], 1, str(missing)) - assert_exits("m4-directory", ["--config-toml-path", str(WORK)], 1, str(WORK)) - malformed = WORK / "malformed.toml" - malformed.write_text(f"{config(43158)}" "malformed_key = [", encoding="utf-8") - assert_exits("m4-malformed", ["--config-toml-path", str(malformed)], 1, str(malformed)) - assert_port_is_available(43158) - unreadable = WORK / "unreadable.toml" - unreadable.write_text(config(43159), encoding="utf-8") - unreadable.chmod(0) - try: - record(f"m4-unreadable: path={unreadable}; regular-file={unreadable.is_file()}; mode={unreadable.stat().st_mode & 0o777:o}") - assert_exits("m4-unreadable", ["--config-toml-path", str(unreadable)], 1, str(unreadable)) - finally: - unreadable.chmod(0o600) - parent = WORK / "parent" - child = parent / "child" - child.mkdir(parents=True) - (parent / "tracker.toml").write_text(config(43157), encoding="utf-8") - assert_exits("m4-parent-only-relative", ["--config-toml-path", "tracker.toml"], 1, "tracker.toml", child) - assert_port_is_available(43157) - - # M5: two port-zero CLI-selected children, independent paths and endpoints. - first = WORK / "m5-first.toml" - second = WORK / "m5-second.toml" - first.write_text(config(0, WORK / "m5-first-storage"), encoding="utf-8") - second.write_text(config(0, WORK / "m5-second-storage"), encoding="utf-8") - first_process = start("m5-first", ["-c", str(first)]) - second_process = start("m5-second", ["-c", str(second)]) - first_port = wait_for_port_zero_health(first_process) - second_port = wait_for_port_zero_health(second_process) - assert first_port != second_port, (first_port, second_port) - first_code, first_output = finish(first_process) - second_code, second_output = finish(second_process) - assert first_code == second_code == 0 - assert "successfully shutdown" in first_output and "successfully shutdown" in second_output - - print(f"Manual verification passed; evidence: {WORK}") - - -if __name__ == "__main__": - main() diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index 156c9d896..db4c8459c 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -4,7 +4,6 @@ semantic-links: - write-unit-test related-artifacts: - docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md - - docs/issues/open/2151-add-tracker-config-path-argument/release-cli-verification.py - docs/testing.md - tests/AGENTS.md - tests/common/native_tracker.rs @@ -82,7 +81,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | R4 | DONE | Add invalid-source process matrix | Added `invalid_sources.rs` with compiled-child contracts for missing/empty option values, missing file, directory, malformed TOML, and parent-only relative path. They assert real exit codes and stable diagnostic fragments; malformed and parent-only sources prove their candidate ports are bindable after child reaping. The expected-failure fixture bounds waiting, forced reaping, and output draining. `cargo test --test cli-configuration` passed (15 tests). | | R5 | DONE | Add Unix unreadable-file process test | Added a fixture-owned valid mode-`000` regular file scenario. When permissions are enforced, the compiled child exits `1` with a path-bearing permission diagnostic and leaves its candidate port bindable after reaping; privileged runners report an explicit skip without spawning a child. Permission restoration is fallible in normal cleanup, non-panicking in drop cleanup, and verified after `wait_for_exit`. `cargo test --test cli-configuration` passed (17 tests). | | R6 | DONE | Review test design increment | Maintainer review of `invalid_sources.rs` found multi-contract asserts, an unreadable-file test that mixed fixture checks into the Assert step, and the suite's only fixed loopback ports (43157-43159). Resolved by: fixture assertion helpers (`assert_usage_error`, `assert_startup_failure`, `assert_diagnostic_names_source_path`) that print child output on failure; one contract per test; `enforced_or_report_skip()` so the unreadable-file test reads as plain AAA; moving permission-restoration and drop-without-runtime checks into the fixture's unit tests; dropping the candidate-port probe (the bounded exit wait already proves no start) so every configuration uses port zero; and separating `NativeTrackerStartAttempt` preparation from `.start()` so Arrange does not launch the child process. `cargo test --test cli-configuration` passed (18 tests, 3 consecutive runs). | -| R7 | TODO | Remove Python test code | After R1-R5 pass and reviewer approval, remove `release-cli-verification.py` and its artifact references. Replace the current scripted verifier section with concise manual release commands only if final manual validation remains useful. | +| R7 | DONE | Remove Python test code | Removed `release-cli-verification.py` after R1-R5 preserved its durable product-behavior checks in Rust executable-boundary tests. The issue retains historical context in its progress log; real release-style manual verification remains separately required for M1-M5. | | R8 | DONE | Document Rust-only test policy | `tests/AGENTS.md` now defines the operational policy: tracked repository test code is Rust; Python is allowed only for separately justified non-test external tooling, never test automation, fixtures, or assertions. `docs/testing.md` states the policy and links to that authoritative guidance without duplicating the exception. | | R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | @@ -133,8 +132,8 @@ Before committing the completed coverage increment: ## Completion Conditions -- Every executable behavior formerly asserted by `release-cli-verification.py` - has a Rust test at the selected layer, or has a documented manual-only reason. +- Every executable behavior formerly covered by the disposable verifier has a + Rust test at the selected layer, or has a documented manual-only reason. - No tracked Python test code remains. - The Rust-only test policy is documented in the canonical testing guidance and reflected in the root integration-test guidance. From 6288a3c257c550c5141dbf7dfa5bd0e9a7a6a56a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 18:56:58 +0100 Subject: [PATCH 25/44] docs(configuration): record config path verification evidence --- .../ISSUE.md | 36 ++- .../ai-harness-verification-plan.md | 32 +-- .../manual-verification-evidence.md | 246 ++++++++++++++++++ .../rust-executable-test-plan.md | 2 +- project-words.txt | 1 + 5 files changed, 290 insertions(+), 27 deletions(-) create mode 100644 docs/issues/open/2151-add-tracker-config-path-argument/manual-verification-evidence.md diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 112f20ffe..309b7cfac 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -315,7 +315,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | | T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | | T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Scripted M1-M5 release-binary scenarios (disposable Python verifier) passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks; this is not human-oriented manual verification (see T10). Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | -| T10 | IN_PROGRESS | Complete Rust executable coverage and manual verification | The approved `rust-executable-test-plan.md` preserved the release-verifier behavior in Rust executable-boundary tests, documented the Rust-only tracked test-code policy, and removed the Python harness. Perform a real release-style manual verification; record actual commands, output, and tracker logs in `manual-verification-evidence.md`, then repeat final validation and acceptance review. | +| T10 | DONE | Complete Rust executable coverage and manual verification | The approved `rust-executable-test-plan.md` preserved the release-verifier behavior in Rust executable-boundary tests, documented the Rust-only tracked test-code policy, and removed the Python harness. Real release-style M1-M5 verification is recorded in `manual-verification-evidence.md`; final focused tests, Clippy, `linter all`, and the pre-commit gate passed. | Each task must be independently buildable and tested. T1 is a behavior-preserving safety-net change; T2 is a configuration refactor; T3-T4 @@ -332,15 +332,31 @@ are the deployable feature; later tasks extend verification and documentation. - [x] First passing CLI-only vertical slice reviewed for ownership, cleanup, deadline, and ADR decisions - [x] Implementation completed - [x] Automatic verification completed (`linter all`, relevant tests, and any pre-push checks) -- [ ] Manual verification scenarios executed and recorded in `manual-verification-evidence.md` +- [x] Manual verification scenarios executed and recorded in `manual-verification-evidence.md` - [x] Acceptance criteria reviewed after implementation and updated with evidence - [x] Evidence-based implementation completion review recorded: progress log states why no retrospective was needed - [x] Reviewer validated acceptance criteria and updated checkboxes -- [ ] Committer verified spec progress is up to date before commit +- [x] Committer verified spec progress is up to date before commit - [ ] Issue closed and spec moved from `docs/issues/open/` to `docs/issues/closed/` ### Progress Log +- 2026-09-08 17:34 UTC - GitHub Copilot - Completed real release-artifact + manual verification. V1-V4 recorded direct CLI-only, precedence, override, + and invalid-source runs; V5 recorded two concurrently running isolated + trackers, distinct health bindings, successful health responses, and clean + SIGTERM shutdown. Evidence is retained in + `manual-verification-evidence.md`; final automated validation and acceptance + review remain pending under T10/R9. + +- 2026-09-08 17:39 UTC - GitHub Copilot - Final validation passed: 129 + configuration-package tests, 18 `cli-configuration` executable tests, and + 13 `lifecycle-signals` tests; focused Clippy, `linter all`, and the required + pre-commit gate also passed. Re-reviewed M1-M5 evidence and all acceptance + criteria. No retrospective is warranted because the implementation and + verification-policy discoveries are recorded in the issue plans and + repository guidance. + - 2026-09-02 16:40 UTC - GitHub Copilot - Drafted the source-selection analysis locally; no tracked file, GitHub issue, or branch was created. - 2026-09-07 08:55 UTC - GitHub Copilot - Reviewed the local draft against the issue-spec workflow and copied it to this folder-style draft; corrected metadata and added ownership, deadline, vertical-slice, and completion-review requirements. - 2026-09-07 09:40 UTC - GitHub Copilot - Consistency review: documented `Toml::file` missing-file and parent-directory-search behavior (blocks AC5 as written), linked the global CLI output contract ADR, forbade clap `env` binding, specified the path type, and tightened M2/T7. @@ -395,7 +411,7 @@ are the deployable feature; later tasks extend verification and documentation. paths, isolated storage, and port-zero bindings without configuration-source environment variables. - [x] `linter all` exits with code `0` and relevant tests pass. -- [ ] Manual verification scenarios are executed and documented (status + evidence). +- [x] Manual verification scenarios are executed and documented (status + evidence). - [x] Acceptance criteria are re-reviewed after implementation and reflect actual behavior. - [x] Documentation states final interfaces and precedence without contradicting implementation. @@ -419,11 +435,11 @@ Status values: `TODO`, `IN_PROGRESS`, `DONE`, `FAILED`, `BLOCKED`. | ID | Scenario | Command/Steps | Expected Result | Status | Evidence | | --- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | --------------------------------------- | -| M1 | CLI path only | Start the release binary with `--config-toml-path` pointing to an isolated valid file and no configuration-source variables. | The tracker reads that file, starts configured services, and exits cleanly on SIGTERM. | TODO | `manual-verification-evidence.md` (V1). | -| M2 | CLI source precedence | Prepare three valid configurations that differ only in `health_check_api.bind_address` (three distinct fixed loopback ports). Supply one via `TORRUST_TRACKER_CONFIG_TOML`, one via `TORRUST_TRACKER_CONFIG_TOML_PATH`, and the third via `--config-toml-path`. | The `HEALTH CHECK API: Started on:` log line reports the port from the CLI-selected file. | TODO | `manual-verification-evidence.md` (V2). | -| M3 | Per-value override | Start with `--config-toml-path` and a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_*` value. | The override wins for its path while other values come from the file. | TODO | `manual-verification-evidence.md` (V3). | -| M4 | Invalid CLI source | Start with (a) `--config-toml-path` with no value, (b) an empty supplied path, (c) a nonexistent absolute file, (d) a directory, (e) an unreadable regular file, (f) malformed TOML, and (g) a relative filename that exists only in a parent directory of the CWD. | Cases (a-b) exit `2` with a descriptive usage error. Cases (c-g) exit `1` with an error naming the path; case (g) must not load the parent-directory file. No case creates a listener. | TODO | `manual-verification-evidence.md` (V4). | -| M5 | Parallel child isolation (Unix) | Launch two binaries concurrently with different CLI paths, isolated storage, and port-zero configuration. | Both start with their own configuration; neither reads or overwrites the other's source. | TODO | `manual-verification-evidence.md` (V5). | +| M1 | CLI path only | Start the release binary with `--config-toml-path` pointing to an isolated valid file and no configuration-source variables. | The tracker reads that file, starts configured services, and exits cleanly on SIGTERM. | DONE | `manual-verification-evidence.md` (V1). | +| M2 | CLI source precedence | Prepare three valid configurations that differ only in `health_check_api.bind_address` (three distinct fixed loopback ports). Supply one via `TORRUST_TRACKER_CONFIG_TOML`, one via `TORRUST_TRACKER_CONFIG_TOML_PATH`, and the third via `--config-toml-path`. | The `HEALTH CHECK API: Started on:` log line reports the port from the CLI-selected file. | DONE | `manual-verification-evidence.md` (V2). | +| M3 | Per-value override | Start with `--config-toml-path` and a distinguishable `TORRUST_TRACKER_CONFIG_OVERRIDE_*` value. | The override wins for its path while other values come from the file. | DONE | `manual-verification-evidence.md` (V3). | +| M4 | Invalid CLI source | Start with (a) `--config-toml-path` with no value, (b) an empty supplied path, (c) a nonexistent absolute file, (d) a directory, (e) an unreadable regular file, (f) malformed TOML, and (g) a relative filename that exists only in a parent directory of the CWD. | Cases (a-b) exit `2` with a descriptive usage error. Cases (c-g) exit `1` with an error naming the path; case (g) must not load the parent-directory file. No case creates a listener. | DONE | `manual-verification-evidence.md` (V4). | +| M5 | Parallel child isolation (Unix) | Launch two binaries concurrently with different CLI paths, isolated storage, and port-zero configuration. | Both start with their own configuration; neither reads or overwrites the other's source. | DONE | `manual-verification-evidence.md` (V5). | Manual verification is mandatory. Execute these release-style scenarios against the built artifact and record actual setup, commands, output, and tracker logs @@ -441,7 +457,7 @@ the progress log before proceeding. | AC4 | DONE | T6 table-driven mandatory/default tests (129 configuration tests passed). | | AC5 | DONE | Parser/configuration tests; M4 release-binary command, mode, exit, diagnostic, and no-listener evidence in `.tmp/issue-2151-manual/summary.txt`. | | AC6 | DONE | `cargo test --test lifecycle-signals` (8 passed); M5. | -| Quality and documentation | TODO | Pre-commit gate, test runs, and T8 review passed; real manual evidence remains pending. | +| Quality and documentation | DONE | M1-M5 are recorded in `manual-verification-evidence.md`; 129 configuration tests, 18 CLI executable tests, 13 lifecycle tests, focused Clippy, `linter all`, and pre-commit passed. | ## Risks and Trade-offs diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md index 03e6cd9f5..3c5783160 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ai-harness-verification-plan.md @@ -91,31 +91,31 @@ owns its removal. Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Expected result | -| --- | ----------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| H1 | DONE | Add manual-evidence template | Added `docs/templates/MANUAL-VERIFICATION-EVIDENCE.md` with purpose, environment, multiple scenario records, actual steps, observed output/logs, conclusions, and follow-up. It forbids invented evidence. | -| H2 | DONE | Update issue template | `docs/templates/ISSUE.md` now requires issue-local manual evidence, defines real human-oriented manual verification, and records the rationale, location, and ownership rules for disposable scripts. | -| H3 | DONE | Update testing guidance | `docs/testing.md` distinguishes all three verification types; `tests/AGENTS.md` retains the authoritative Rust-only test-code policy and links to that guidance. | -| H4 | DONE | Update repository skills | Updated `create-issue` and `write-unit-test` with manual-evidence, disposable-script-rationale, Rust preference, and durable-test-promotion requirements. | -| H5 | DONE | Update custom agents | Updated Planner, Implementer, Task Reviewer, and Committer: the roles that plan, perform, validate, and finalize issue work. They link to or enforce the canonical verification guidance. | -| H6 | IN_PROGRESS | Apply the policy to #2151 | The issue records the removed Python verifier as historical disposable automation, and M1-M5 are reset for real release-style manual runs. Create `manual-verification-evidence.md` and record actual results. | -| H7 | DONE | Validate documentation | `validate-skill-links.sh`, `linter markdown`, `linter cspell`, `linter lychee`, and `git diff --check` passed. Reviewed the resulting guidance: `tests/AGENTS.md` remains the authoritative Rust-only test-code rule; other artifacts link to it or to `docs/testing.md` rather than defining competing exceptions. | +| ID | Status | Task | Expected result | +| --- | ------ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| H1 | DONE | Add manual-evidence template | Added `docs/templates/MANUAL-VERIFICATION-EVIDENCE.md` with purpose, environment, multiple scenario records, actual steps, observed output/logs, conclusions, and follow-up. It forbids invented evidence. | +| H2 | DONE | Update issue template | `docs/templates/ISSUE.md` now requires issue-local manual evidence, defines real human-oriented manual verification, and records the rationale, location, and ownership rules for disposable scripts. | +| H3 | DONE | Update testing guidance | `docs/testing.md` distinguishes all three verification types; `tests/AGENTS.md` retains the authoritative Rust-only test-code policy and links to that guidance. | +| H4 | DONE | Update repository skills | Updated `create-issue` and `write-unit-test` with manual-evidence, disposable-script-rationale, Rust preference, and durable-test-promotion requirements. | +| H5 | DONE | Update custom agents | Updated Planner, Implementer, Task Reviewer, and Committer: the roles that plan, perform, validate, and finalize issue work. They link to or enforce the canonical verification guidance. | +| H6 | DONE | Apply the policy to #2151 | The issue records the removed Python verifier as historical disposable automation. M1-M5 were executed against the release artifact and recorded, with actual commands, output, logs, and conclusions, in `manual-verification-evidence.md`. | +| H7 | DONE | Validate documentation | `validate-skill-links.sh`, `linter markdown`, `linter cspell`, `linter lychee`, and `git diff --check` passed. Reviewed the resulting guidance: `tests/AGENTS.md` remains the authoritative Rust-only test-code rule; other artifacts link to it or to `docs/testing.md` rather than defining competing exceptions. | ## Acceptance Criteria -- [ ] The repository documents automatic tests, manual verification, and +- [x] The repository documents automatic tests, manual verification, and disposable verification scripts as distinct activities with clear purposes. -- [ ] New issue specs require actual, issue-local manual-verification evidence +- [x] New issue specs require actual, issue-local manual-verification evidence at `manual-verification-evidence.md` when scenarios are executed. -- [ ] A disposable verification script must be issue-local and accompanied by a +- [x] A disposable verification script must be issue-local and accompanied by a concrete rationale for using temporary automation instead of a maintained automatic test. -- [ ] Python use in disposable scripts requires a recorded case-specific reason +- [x] Python use in disposable scripts requires a recorded case-specific reason for not using Rust. -- [ ] Maintained, tracked test code remains Rust-only. -- [ ] Relevant templates, repository skills, and custom-agent instructions link +- [x] Maintained, tracked test code remains Rust-only. +- [x] Relevant templates, repository skills, and custom-agent instructions link to consistent, repository-owned guidance. -- [ ] Documentation linters and local link checks pass. +- [x] Documentation linters and local link checks pass. ## Risks and Trade-offs diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/manual-verification-evidence.md b/docs/issues/open/2151-add-tracker-config-path-argument/manual-verification-evidence.md new file mode 100644 index 000000000..3251b57d2 --- /dev/null +++ b/docs/issues/open/2151-add-tracker-config-path-argument/manual-verification-evidence.md @@ -0,0 +1,246 @@ +--- +doc-type: manual-verification-evidence +issue-spec: docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +last-updated-utc: 2026-09-08 17:34 +--- + +# Manual Verification Evidence + +## Environment and Prerequisites + +- Date and time (UTC): 2026-09-08 17:15-17:28. +- Artifact under test: `target/release/torrust-tracker`, built from the current + `2151-add-tracker-config-path-argument` branch. +- Operating system / environment: Linux; repository root as current working + directory. +- Setup: Each process used a copy of + `share/default/config/tracker.development.sqlite3.toml` with loopback, + port-zero tracker/API bindings and an isolated SQLite file under + `.tmp/issue-2151-manual/`. The manual runs invoked the release binary + directly, rather than a test runner or disposable script. + +## Verification Processes + +### V1 - M1: CLI Path Only + +- Goal: Confirm that a release tracker can start from an explicit configuration + file without configuration-source environment variables. +- Initial state: Isolated valid configuration at + `.tmp/issue-2151-manual/m1-clean/tracker.toml`; no + `TORRUST_TRACKER_CONFIG_TOML` or `TORRUST_TRACKER_CONFIG_TOML_PATH`. +- Status: `DONE` + +#### Steps Performed + +1. Started the release binary directly: + + ```sh + env -u TORRUST_TRACKER_CONFIG_TOML -u TORRUST_TRACKER_CONFIG_TOML_PATH \ + target/release/torrust-tracker -c "$PWD/.tmp/issue-2151-manual/m1-clean/tracker.toml" \ + > .tmp/issue-2151-manual/m1-clean/tracker.log 2>&1 & + printf '%s\n' "$!" > .tmp/issue-2151-manual/m1-clean/tracker.pid + ``` + +2. Called `http://127.0.0.1:1313/health_check`, sent SIGTERM to the PID in + `tracker.pid`, and waited for the process to exit. + +#### Observed Result + +```text +M1 exit status: 0 +HEALTH CHECK API: Started on: http://127.0.0.1:1313 +HTTP/1.1 200 OK +Torrust tracker successfully shutdown. +``` + +#### Conclusion + +The release binary read the explicit file, served the health endpoint, and +shut down cleanly. M1 passed. + +### V2 - M2: CLI Source Precedence + +- Goal: Confirm that the CLI file wins over complete-TOML and path environment + base sources. +- Initial state: Three valid configurations specified health ports `43152` + (CLI), `43153` (environment path), and `43154` (environment TOML content). +- Status: `DONE` + +#### Steps Performed + +1. Started the release binary with all three base sources: + + ```sh + TORRUST_TRACKER_CONFIG_TOML="$(< .tmp/issue-2151-manual/m2/env-content.toml)" \ + TORRUST_TRACKER_CONFIG_TOML_PATH="$PWD/.tmp/issue-2151-manual/m2/env-path.toml" \ + target/release/torrust-tracker \ + --config-toml-path "$PWD/.tmp/issue-2151-manual/m2/cli.toml" \ + > .tmp/issue-2151-manual/m2/tracker.log 2>&1 & + printf '%s\n' "$!" > .tmp/issue-2151-manual/m2/tracker.pid + ``` + +2. Called `http://127.0.0.1:43152/health_check`, then sent SIGTERM and waited + for the process. + +#### Observed Result + +```text +M2 exit status: 0 +HEALTH CHECK API: Started on: http://127.0.0.1:43152 +HTTP/1.1 200 OK +Torrust tracker successfully shutdown. +``` + +The `43153` and `43154` environment-source ports were not selected. + +#### Conclusion + +The CLI-selected file was the exclusive base source. M2 passed. + +### V3 - M3: Per-Value Override + +- Goal: Confirm that per-value overrides remain higher priority than a + CLI-selected base file. +- Initial state: The CLI file set the health endpoint to `43155`; the override + set it to `43156`. +- Status: `DONE` + +#### Steps Performed + +1. Started the release binary directly: + + ```sh + TORRUST_TRACKER_CONFIG_OVERRIDE_HEALTH_CHECK_API__BIND_ADDRESS=127.0.0.1:43156 \ + env -u TORRUST_TRACKER_CONFIG_TOML -u TORRUST_TRACKER_CONFIG_TOML_PATH \ + target/release/torrust-tracker \ + --config-toml-path "$PWD/.tmp/issue-2151-manual/m3/tracker.toml" \ + > .tmp/issue-2151-manual/m3/tracker.log 2>&1 & + printf '%s\n' "$!" > .tmp/issue-2151-manual/m3/tracker.pid + ``` + +2. Called `http://127.0.0.1:43156/health_check`, then sent SIGTERM and waited + for the process. + +#### Observed Result + +```text +M3 exit status: 0 +HEALTH CHECK API: Started on: http://127.0.0.1:43156 +HTTP/1.1 200 OK +Torrust tracker successfully shutdown. +``` + +The CLI file specified `43155`, which was not the active health endpoint. + +#### Conclusion + +The override applied over the CLI base file. M3 passed. + +### V4 - M4: Invalid CLI Source + +- Goal: Confirm user-visible failure behavior for invalid argument and source + states, without starting a listener. +- Initial state: Isolated missing path, directory, mode-`000` valid TOML file, + malformed TOML file, and a `tracker.toml` file only in the parent of the + invocation directory. +- Status: `DONE` + +#### Steps Performed + +1. Ran the release binary directly with each of the following inputs, capturing + stderr and the exit status in `.tmp/issue-2151-manual/m4/`: + + ```sh + target/release/torrust-tracker --config-toml-path + target/release/torrust-tracker --config-toml-path '' + target/release/torrust-tracker --config-toml-path "$PWD/.tmp/issue-2151-manual/m4/missing.toml" + target/release/torrust-tracker --config-toml-path "$PWD/.tmp/issue-2151-manual/m4" + target/release/torrust-tracker --config-toml-path "$PWD/.tmp/issue-2151-manual/m4/unreadable.toml" + target/release/torrust-tracker --config-toml-path "$PWD/.tmp/issue-2151-manual/m4/malformed.toml" + (cd .tmp/issue-2151-manual/m4/parent/child && \ + "$OLDPWD/target/release/torrust-tracker" --config-toml-path tracker.toml) + ``` + +2. Restored the unreadable file mode to `600` and checked that no scenario + health ports `43152` through `43156` had a listening TCP socket. + +#### Observed Result + +```text +missing-value exit=2 +error: a value is required for '--config-toml-path ' but none was supplied + +empty-value exit=2 +error: invalid value '' for '--config-toml-path ': configuration TOML path must not be empty + +missing-file exit=1 +Unable to load explicit configuration file `.../m4/missing.toml`: No such file or directory (os error 2) + +directory exit=1 +Unable to load explicit configuration file `.../m4`: path is not a regular file + +unreadable exit=1 +Unable to load explicit configuration file `.../m4/unreadable.toml`: Permission denied (os error 13) + +malformed exit=1 +Unable to process explicit configuration file `.../m4/malformed.toml`: Missing mandatory configuration option + +parent-only-relative exit=1 +Unable to load explicit configuration file `tracker.toml`: No such file or directory (os error 2) + +ss -ltn | rg ':4315[2-6]\b' +# no output +``` + +#### Conclusion + +The parser failures exited `2`; all invalid-source failures exited `1`, named +the supplied source, and did not start a scenario listener. The mode-`000` +regular-file behavior was enforced by this Linux environment. M4 passed. + +### V5 - M5: Parallel Child Isolation (Unix) + +- Goal: Start two release binaries concurrently with different explicit files, + port-zero bindings, and isolated SQLite paths. +- Initial state: `first.toml` and `second.toml` specify separate databases + under `.tmp/issue-2151-manual/m5/` and `health_check_api.bind_address` as + `127.0.0.1:0`. +- Status: `DONE` + +#### Steps Performed + +1. Started the two release binaries directly with `first.toml` and + `second.toml`, recording PIDs `476822` and `476823`. +2. Normalized terminal color escapes from the logs, extracted the + `Started health check API` service bindings, and called both + `/health_check` endpoints. +3. Sent SIGTERM to both PIDs and waited for both processes to exit. + +#### Observed Result + +```text +first_url=http://127.0.0.1:41693 second_url=http://127.0.0.1:38835 +first_health=0 second_health=0 first_exit=0 second_exit=0 + +first health response: {"status":"Ok", ...} +second health response: {"status":"Ok", ...} + +first: HEALTH CHECK API: Started health check API ... service_binding=http://127.0.0.1:41693/ +second: HEALTH CHECK API: Started health check API ... service_binding=http://127.0.0.1:38835/ +first: Torrust tracker successfully shutdown. +second: Torrust tracker successfully shutdown. +``` + +#### Conclusion + +Both release processes used their own explicit source, dynamically assigned +health endpoint, and isolated storage. Both endpoint calls succeeded and both +processes shut down cleanly. M5 passed. + +## Failures and Follow-up + +- V5's initial endpoint-discovery command assumed no terminal color escape + sequences between `HEALTH` and `CHECK`; therefore, it did not exercise the + endpoints. The completed rerun normalized those escapes and used the stable + service-binding message. This was a verification-command defect, not a + product failure. diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index db4c8459c..8d4d2e143 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -83,7 +83,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | R6 | DONE | Review test design increment | Maintainer review of `invalid_sources.rs` found multi-contract asserts, an unreadable-file test that mixed fixture checks into the Assert step, and the suite's only fixed loopback ports (43157-43159). Resolved by: fixture assertion helpers (`assert_usage_error`, `assert_startup_failure`, `assert_diagnostic_names_source_path`) that print child output on failure; one contract per test; `enforced_or_report_skip()` so the unreadable-file test reads as plain AAA; moving permission-restoration and drop-without-runtime checks into the fixture's unit tests; dropping the candidate-port probe (the bounded exit wait already proves no start) so every configuration uses port zero; and separating `NativeTrackerStartAttempt` preparation from `.start()` so Arrange does not launch the child process. `cargo test --test cli-configuration` passed (18 tests, 3 consecutive runs). | | R7 | DONE | Remove Python test code | Removed `release-cli-verification.py` after R1-R5 preserved its durable product-behavior checks in Rust executable-boundary tests. The issue retains historical context in its progress log; real release-style manual verification remains separately required for M1-M5. | | R8 | DONE | Document Rust-only test policy | `tests/AGENTS.md` now defines the operational policy: tracked repository test code is Rust; Python is allowed only for separately justified non-test external tooling, never test automation, fixtures, or assertions. `docs/testing.md` states the policy and links to that authoritative guidance without duplicating the exception. | -| R9 | TODO | Final validation and evidence | Run the required focused tests, `linter all`, pre-commit, and manual release scenarios. Re-review acceptance criteria and record whether a retrospective is needed. | +| R9 | DONE | Final validation and evidence | M1-M5 release scenarios are recorded in `manual-verification-evidence.md`. Final validation passed: 129 configuration-package tests, 18 CLI executable tests, 13 lifecycle tests, focused Clippy, `linter all`, and the required pre-commit gate. Acceptance criteria were re-reviewed; no retrospective is warranted because the relevant discoveries are retained in the issue plans and repository guidance. | ## Scenario Contracts diff --git a/project-words.txt b/project-words.txt index cfd6a5d9f..1f9acd3e5 100644 --- a/project-words.txt +++ b/project-words.txt @@ -344,6 +344,7 @@ numwant nvCFlJCq7fz7Qx6KoKTDiMZvns8l5Kw7 objcopy obra +oldpwd oneline oneshot openexr From e82ccca449b3c80fabddf2c04fcc08b2a2b545b5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 19:43:49 +0100 Subject: [PATCH 26/44] docs(issues): format verification records --- .../ISSUE.md | 42 +++++++++---------- .../rust-executable-test-plan.md | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md index 309b7cfac..b114e3f93 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/ISSUE.md @@ -304,18 +304,18 @@ first vertical slice: Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. -| ID | Status | Task | Notes / Expected Output | -| --- | ----------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | -| T2 | DONE | Introduce typed source selection | Added `Info::new_with_explicit_config_toml_path(..., Option)`. Explicit paths are eagerly read and loaded from retained contents, take precedence over environment base sources, preserve their exact `PathBuf` in diagnostics, and never use parent lookup. Legacy environment and default file behavior remains on `Toml::file`. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); Rust formatting and Clippy passed. | -| T3 | DONE | Define the CLI boundary | Added a main-owned `clap` parser for `-c` / `--config-toml-path` with no `env` binding. Parser tests cover short/long forms, missing/empty values, unknown arguments, help, exit codes, and absent environment binding. `cargo test --package torrust-tracker --bin torrust-tracker` passed (7 tests). | -| T4 | DONE | Wire startup and precedence | Threaded `Option` from `main` through `app::start_with_explicit_config_toml_path`, `bootstrap::app::setup`, and `initialize_configuration` into T2 without environment mutation. Direct bootstrap tests cover every CLI-present table row: CLI only, CLI plus full TOML, CLI plus path, and CLI plus both sources. Root library tests passed (84 tests). | -| T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | -| T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | -| T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | -| T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | -| T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Scripted M1-M5 release-binary scenarios (disposable Python verifier) passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks; this is not human-oriented manual verification (see T10). Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | -| T10 | DONE | Complete Rust executable coverage and manual verification | The approved `rust-executable-test-plan.md` preserved the release-verifier behavior in Rust executable-boundary tests, documented the Rust-only tracked test-code policy, and removed the Python harness. Real release-style M1-M5 verification is recorded in `manual-verification-evidence.md`; final focused tests, Clippy, `linter all`, and the pre-commit gate passed. | +| ID | Status | Task | Notes / Expected Output | +| --- | ------ | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| T1 | DONE | Establish baseline source behavior | Added `figment::Jail` tests for all four existing no-CLI base-source rows, a path-source override, missing-file mandatory-option result, and parent-directory search. `cargo test --package torrust-tracker-configuration` passed (118 tests). | +| T2 | DONE | Introduce typed source selection | Added `Info::new_with_explicit_config_toml_path(..., Option)`. Explicit paths are eagerly read and loaded from retained contents, take precedence over environment base sources, preserve their exact `PathBuf` in diagnostics, and never use parent lookup. Legacy environment and default file behavior remains on `Toml::file`. `cargo test --package torrust-tracker-configuration --lib` passed (127 tests); Rust formatting and Clippy passed. | +| T3 | DONE | Define the CLI boundary | Added a main-owned `clap` parser for `-c` / `--config-toml-path` with no `env` binding. Parser tests cover short/long forms, missing/empty values, unknown arguments, help, exit codes, and absent environment binding. `cargo test --package torrust-tracker --bin torrust-tracker` passed (7 tests). | +| T4 | DONE | Wire startup and precedence | Threaded `Option` from `main` through `app::start_with_explicit_config_toml_path`, `bootstrap::app::setup`, and `initialize_configuration` into T2 without environment mutation. Direct bootstrap tests cover every CLI-present table row: CLI only, CLI plus full TOML, CLI plus path, and CLI plus both sources. Root library tests passed (84 tests). | +| T5 | DONE | Review first vertical slice | Review completed after the parser-to-bootstrap vertical slice passed. Ownership is coherent: parsing is binary-only; configuration loading remains in the configuration package; no new async resource or readiness wait was introduced. Existing native-fixture lifetime/deadline invariants are unchanged. No ADR is required now; reconsider only if a lasting wider source-selection policy emerges. | +| T6 | DONE | Preserve overrides and defaults | Existing explicit-file coverage proves a per-value override wins. Added table-driven tests that each mandatory field still fails before Rust defaults, and that an explicit file containing only mandatory fields receives the unchanged optional defaults. `cargo test --package torrust-tracker-configuration --lib` passed (129 tests); Rust formatting and Clippy passed. | +| T7 | DONE | Add executable-boundary coverage | Native fixtures now pass `--config-toml-path`, remove both inherited base-source variables, and retain per-child CLI-path/storage identities. The lifecycle target starts two children concurrently, verifies distinct PIDs, health addresses, CLI paths, and storage paths, then sends SIGTERM and reaps both. `cargo test --test lifecycle-signals` passed (8 tests); tracker tests, Rust formatting, and Clippy passed. | +| T8 | DONE | Update documentation | Updated the README, configuration crate/root API docs, container, benchmarking, profiling, source/test guidance, and local-run skill. CLI selection is primary for the main binary; environment examples remain valid. Documentation states final precedence, strict CLI-path behavior, profiling's environment-only boundary, and native fixture isolation. Skill-link validation, Markdown lint, spell checking, and diff checks passed. | +| T9 | DONE | Validate and record evidence | The mandatory pre-commit gate, configuration (129), tracker, and lifecycle-signals (8) tests passed. Scripted M1-M5 release-binary scenarios (disposable Python verifier) passed with `.tmp/issue-2151-manual/` evidence, including unreadable-file and no-listener checks; this is not human-oriented manual verification (see T10). Acceptance criteria were independently reviewed and all passed. No separate retrospective was warranted. | +| T10 | DONE | Complete Rust executable coverage and manual verification | The approved `rust-executable-test-plan.md` preserved the release-verifier behavior in Rust executable-boundary tests, documented the Rust-only tracked test-code policy, and removed the Python harness. Real release-style M1-M5 verification is recorded in `manual-verification-evidence.md`; final focused tests, Clippy, `linter all`, and the pre-commit gate passed. | Each task must be independently buildable and tested. T1 is a behavior-preserving safety-net change; T2 is a configuration refactor; T3-T4 @@ -449,15 +449,15 @@ the progress log before proceeding. ### Acceptance Verification -| AC ID | Status (`TODO`/`DONE`) | Evidence | -| ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| AC1 | DONE | Parser tests; `cargo test --package torrust-tracker --bin torrust-tracker` (7 passed). | -| AC2 | DONE | T1/T4 configuration and bootstrap tests; M2. | -| AC3 | DONE | T1/T2/T6 configuration tests; M3. | -| AC4 | DONE | T6 table-driven mandatory/default tests (129 configuration tests passed). | -| AC5 | DONE | Parser/configuration tests; M4 release-binary command, mode, exit, diagnostic, and no-listener evidence in `.tmp/issue-2151-manual/summary.txt`. | -| AC6 | DONE | `cargo test --test lifecycle-signals` (8 passed); M5. | -| Quality and documentation | DONE | M1-M5 are recorded in `manual-verification-evidence.md`; 129 configuration tests, 18 CLI executable tests, 13 lifecycle tests, focused Clippy, `linter all`, and pre-commit passed. | +| AC ID | Status (`TODO`/`DONE`) | Evidence | +| ------------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| AC1 | DONE | Parser tests; `cargo test --package torrust-tracker --bin torrust-tracker` (7 passed). | +| AC2 | DONE | T1/T4 configuration and bootstrap tests; M2. | +| AC3 | DONE | T1/T2/T6 configuration tests; M3. | +| AC4 | DONE | T6 table-driven mandatory/default tests (129 configuration tests passed). | +| AC5 | DONE | Parser/configuration tests; M4 release-binary command, mode, exit, diagnostic, and no-listener evidence in `.tmp/issue-2151-manual/summary.txt`. | +| AC6 | DONE | `cargo test --test lifecycle-signals` (8 passed); M5. | +| Quality and documentation | DONE | M1-M5 are recorded in `manual-verification-evidence.md`; 129 configuration tests, 18 CLI executable tests, 13 lifecycle tests, focused Clippy, `linter all`, and pre-commit passed. | ## Risks and Trade-offs diff --git a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md index 8d4d2e143..adff2cb1c 100644 --- a/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md +++ b/docs/issues/open/2151-add-tracker-config-path-argument/rust-executable-test-plan.md @@ -83,7 +83,7 @@ Status values: `TODO`, `IN_PROGRESS`, `BLOCKED`, `DONE`. | R6 | DONE | Review test design increment | Maintainer review of `invalid_sources.rs` found multi-contract asserts, an unreadable-file test that mixed fixture checks into the Assert step, and the suite's only fixed loopback ports (43157-43159). Resolved by: fixture assertion helpers (`assert_usage_error`, `assert_startup_failure`, `assert_diagnostic_names_source_path`) that print child output on failure; one contract per test; `enforced_or_report_skip()` so the unreadable-file test reads as plain AAA; moving permission-restoration and drop-without-runtime checks into the fixture's unit tests; dropping the candidate-port probe (the bounded exit wait already proves no start) so every configuration uses port zero; and separating `NativeTrackerStartAttempt` preparation from `.start()` so Arrange does not launch the child process. `cargo test --test cli-configuration` passed (18 tests, 3 consecutive runs). | | R7 | DONE | Remove Python test code | Removed `release-cli-verification.py` after R1-R5 preserved its durable product-behavior checks in Rust executable-boundary tests. The issue retains historical context in its progress log; real release-style manual verification remains separately required for M1-M5. | | R8 | DONE | Document Rust-only test policy | `tests/AGENTS.md` now defines the operational policy: tracked repository test code is Rust; Python is allowed only for separately justified non-test external tooling, never test automation, fixtures, or assertions. `docs/testing.md` states the policy and links to that authoritative guidance without duplicating the exception. | -| R9 | DONE | Final validation and evidence | M1-M5 release scenarios are recorded in `manual-verification-evidence.md`. Final validation passed: 129 configuration-package tests, 18 CLI executable tests, 13 lifecycle tests, focused Clippy, `linter all`, and the required pre-commit gate. Acceptance criteria were re-reviewed; no retrospective is warranted because the relevant discoveries are retained in the issue plans and repository guidance. | +| R9 | DONE | Final validation and evidence | M1-M5 release scenarios are recorded in `manual-verification-evidence.md`. Final validation passed: 129 configuration-package tests, 18 CLI executable tests, 13 lifecycle tests, focused Clippy, `linter all`, and the required pre-commit gate. Acceptance criteria were re-reviewed; no retrospective is warranted because the relevant discoveries are retained in the issue plans and repository guidance. | ## Scenario Contracts From feb53850fb17ddb15e5a7ddf61195428a68c3297 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 8 Sep 2026 20:03:31 +0100 Subject: [PATCH 27/44] style(configuration): group standard imports --- packages/configuration/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index d5f424592..75841da20 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -11,11 +11,9 @@ pub mod v3_0_0; pub mod validator; use std::collections::HashMap; -use std::env; -use std::fs; -use std::io; use std::path::{Path, PathBuf}; use std::sync::Arc; +use std::{env, fs, io}; use camino::Utf8PathBuf; use derive_more::Display; From 8e9c0b687c32575793ebbf6840f9c5d0348b0e65 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 07:35:47 +0100 Subject: [PATCH 28/44] refactor(configuration): clarify explicit source loading --- packages/configuration/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/configuration/src/lib.rs b/packages/configuration/src/lib.rs index 75841da20..5a0809723 100644 --- a/packages/configuration/src/lib.rs +++ b/packages/configuration/src/lib.rs @@ -189,7 +189,7 @@ impl Info { fn from_explicit_file(default_config_toml_path: String, path: PathBuf) -> Result { let config_toml = Self::read_explicit_config_toml_file(&path)?; - info!(path = ?path, "Loading extra configuration from explicit configuration file"); + info!(path = ?path, "Loading base configuration from explicit configuration file"); Ok(Self { config_toml: Some(config_toml), @@ -255,21 +255,21 @@ impl Info { } } - fn read_explicit_config_toml_file(path: &PathBuf) -> Result { + fn read_explicit_config_toml_file(path: &Path) -> Result { let metadata = fs::metadata(path).map_err(|source| Error::UnableToLoadExplicitConfigFile { - path: path.clone(), + path: path.to_path_buf(), source, })?; if !metadata.is_file() { return Err(Error::UnableToLoadExplicitConfigFile { - path: path.clone(), + path: path.to_path_buf(), source: io::Error::new(io::ErrorKind::InvalidInput, "path is not a regular file"), }); } fs::read_to_string(path).map_err(|source| Error::UnableToLoadExplicitConfigFile { - path: path.clone(), + path: path.to_path_buf(), source, }) } From 957ed394552f29db0ff88d169778ccea272ce229 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 08:25:04 +0100 Subject: [PATCH 29/44] fix(configuration): address CLI path review findings --- docs/containers.md | 8 +++++--- src/bootstrap/config.rs | 9 +++++++++ tests/AGENTS.md | 8 +++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/containers.md b/docs/containers.md index ac1b5cc5f..5d4a84e10 100644 --- a/docs/containers.md +++ b/docs/containers.md @@ -147,10 +147,11 @@ podman run -it docker.io/torrust-tracker:debug ### Arguments -Docker or Podman runtime arguments are placed before the image tag. Tracker -command-line arguments are placed after it. +Docker or Podman runtime arguments are placed before the image tag. Because +tracker arguments after the image tag replace the image `CMD`, include the +tracker binary before its command-line arguments. -`run [runtime arguments] torrust-tracker:release [tracker arguments]` +`run [runtime arguments] torrust-tracker:release /usr/bin/torrust-tracker [tracker arguments]` #### Tracker Command Options @@ -159,6 +160,7 @@ path is evaluated inside the container, so use an in-container mounted path: ```sh docker run -it torrust/tracker:latest \ + /usr/bin/torrust-tracker \ --config-toml-path /etc/torrust/tracker/tracker.toml ``` diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 5052bfcaf..5f4f7e759 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -117,6 +117,14 @@ mod tests { std::env::remove_var("TORRUST_TRACKER_CONFIG_TOML"); } } + + #[allow(unsafe_code)] + fn remove_path() { + // SAFETY: `ENVIRONMENT_LOCK` serializes environment mutations in this test module. + unsafe { + std::env::remove_var(torrust_tracker_configuration::ENV_VAR_CONFIG_TOML_PATH); + } + } } impl Drop for ConfigurationPathGuard { @@ -236,6 +244,7 @@ mod tests { let explicit_path = directory.path().join("explicit.toml"); fs::write(&explicit_path, configuration_with_health_check_port(42155)).expect("write explicit configuration"); let _path_guard = ConfigurationPathGuard::replace(&directory.path().join("environment.toml")); + ConfigurationPathGuard::remove_path(); ConfigurationPathGuard::set_complete_toml(configuration_with_health_check_port(42156)); // Act diff --git a/tests/AGENTS.md b/tests/AGENTS.md index e6b88b418..a61590725 100644 --- a/tests/AGENTS.md +++ b/tests/AGENTS.md @@ -128,8 +128,6 @@ Until these global side effects are eliminated (tracked in integration-test binary must start exactly one tracker instance with one fixed configuration. Scenario functions run sequentially against that shared instance. -## Test Infrastructure Requirements - ## Test Implementation Language All tracked repository test code must be Rust. Python is permitted only for @@ -140,12 +138,16 @@ Manual verification and disposable verification scripts are not tracked test code. Follow [the testing strategy](../docs/testing.md#verification-types) and the issue template for their separate evidence and rationale requirements. +## Test Infrastructure Requirements + All integration tests at this level must: 1. **Use port `0` for bind addresses by default**: The OS assigns free ephemeral ports, preventing conflicts when tests run in parallel. Fixed ports are permitted when the test scenario specifically requires distinct addresses (e.g., verifying per-instance - behavior). Use non-overlapping port ranges and document the constraint. + behavior). Use non-overlapping port ranges and document the constraint. The + `cli-configuration` target reserves TCP ports `43152-43156` to distinguish + CLI, environment-source, and override selection in executable-boundary tests. 2. **Use isolated temporary workspaces**: Use `tempfile::TempDir` to create isolated directories with separate config files and storage subdirectories 3. **Extract actual bound ports**: Query `AppContainer`'s `Registar` to get the OS-assigned ports From d7bd7bafe14ea066cd3bd55b40355797cf42d08e Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 08:44:47 +0100 Subject: [PATCH 30/44] fix(test): reap failed tracker start without runtime --- tests/common/native_tracker.rs | 36 +++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index 26bd3dc51..32214a349 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -837,9 +837,14 @@ impl Drop for NativeTrackerFailedStart { drop(permission_restore.restore()); } - // `kill_on_drop(true)` remains the no-runtime fallback. With a runtime, - // retain the workspace until child and output cleanup finishes. + // Without a runtime, synchronously kill and reap before field drop can + // release the fixture workspace. let Ok(runtime) = tokio::runtime::Handle::try_current() else { + drop(child.start_kill()); + while child.try_wait().ok().flatten().is_none() { + std::thread::yield_now(); + } + drop(output); return; }; let workspace = self.workspace.take(); @@ -1034,10 +1039,15 @@ mod tests { use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::os::unix::fs::PermissionsExt as _; use std::path::Path; + use std::process::Stdio; + + use nix::sys::signal::kill; + use nix::unistd::Pid; + use tokio::process::Command; use super::{ - NativeTrackerInvalidCliSource, NativeTrackerPermissionRestore, NativeTrackerStartAttempt, invalid_source_command, - parse_health_check_address, tracker_command, write_configuration, + NativeTrackerFailedStart, NativeTrackerInvalidCliSource, NativeTrackerPermissionRestore, NativeTrackerStartAttempt, + TrackerOutputCapture, invalid_source_command, parse_health_check_address, tracker_command, write_configuration, }; #[test] @@ -1187,13 +1197,29 @@ mod tests { #[tokio::test] async fn it_should_not_panic_when_a_failed_start_is_dropped_without_a_tokio_runtime() { // Arrange: spawning needs a runtime; the drop below happens outside one. - let failed_start = NativeTrackerStartAttempt::with_invalid_cli_source(NativeTrackerInvalidCliSource::MissingFile).start(); + let mut child = Command::new("sleep") + .arg("60") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("spawn long-running tracker substitute"); + let pid = Pid::from_raw(child.id().expect("child should have a PID").cast_signed()); + let stdout = child.stdout.take().expect("child stdout is piped"); + let stderr = child.stderr.take().expect("child stderr is piped"); + let failed_start = NativeTrackerFailedStart { + child: Some(child), + output: Some(TrackerOutputCapture::new(stdout, stderr)), + permission_restore: None, + workspace: Some(tempfile::tempdir().expect("create fixture workspace")), + source_path: None, + }; // Act let result = std::thread::spawn(move || drop(failed_start)).join(); // Assert assert!(result.is_ok(), "dropping a failed start outside Tokio must not panic"); + assert_eq!(kill(pid, None), Err(nix::errno::Errno::ESRCH)); } #[tokio::test] From 547bf876e5c2338ef77486644b436f43d84f36e6 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 09:16:26 +0100 Subject: [PATCH 31/44] docs(review): add PR review feedback workflow --- .../process-pr-review-feedback/SKILL.md | 91 +++++++++++++++++++ docs/AGENTS.md | 1 + docs/index.md | 10 ++ docs/pr-review-feedback/README.md | 29 ++++++ .../pr-2178-review-feedback.md | 45 +++++++++ docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md | 71 +++++++++++++++ 6 files changed, 247 insertions(+) create mode 100644 .github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md create mode 100644 docs/pr-review-feedback/README.md create mode 100644 docs/pr-review-feedback/pr-2178-review-feedback.md create mode 100644 docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md diff --git a/.github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md b/.github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md new file mode 100644 index 000000000..05ecaa785 --- /dev/null +++ b/.github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md @@ -0,0 +1,91 @@ +--- +name: process-pr-review-feedback +description: Process pull-request reviews that may contain multiple independent findings from maintainers, collaborators, or agents acting for them. Use when asked to address reviewer feedback, maintainer PR comments, contributor review summaries, or non-Copilot suggestions. +metadata: + author: torrust + version: "1.0" + semantic-links: + related-artifacts: + - docs/pr-review-feedback/README.md + - docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md + - .github/skills/dev/pr-reviews/fetch-review-threads/SKILL.md + - .github/skills/dev/pr-reviews/resolve-review-threads/SKILL.md +--- + +# Processing PR Review Feedback + +Use this workflow for review feedback from one or more maintainers, +collaborators, or agents acting for them. Use `process-copilot-suggestions` for +Copilot-generated review threads instead. + +## Model + +A submitted **review** and an inline **review thread** are different GitHub +resources: + +- A review has a numeric review ID, state, body, reviewed commit, and URL. Its + body may contain multiple findings. GitHub has no resolved state for it. +- An inline review thread has a GraphQL node ID and can be replied to and + resolved. A reviewer can create these just as Copilot can. + +Create `docs/pr-review-feedback/pr--review-feedback.md` from +`docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md`. Treat its review-response +state as the durable completion status for a review-level summary. + +## Procedure + +1. **Fetch reviews and threads.** Query submitted reviews and inline comments + by review ID. Fetch all review threads separately, including their IDs, + author, paths, bodies, and resolved state. Do not assume review-comment IDs + are thread IDs. +2. **Create the audit record.** Add one row per review. Decompose each review + body and inline comment into one row per independent finding, with a decision + of `ACTION`, `NO_ACTION`, or `FOLLOW_UP`. +3. **Implement each action independently.** For every `ACTION`, make the + smallest correct change, run relevant validation, and create a separate GPG + signed Conventional Commit. Do not combine feature fixes with this workflow's + docs, template, or audit records. +4. **Handle inline suggestions.** After the relevant action/no-action decision + is complete, reply directly on each inline thread. Record the reply URL, + then resolve the thread using `resolve-review-threads`. Do not resolve before + replying. +5. **Reply to the review summary.** Once all findings from one submitted review + are done or explicitly deferred, post one consolidated PR conversation + comment. Include the review ID, each finding's outcome, associated commit, + validation, and any follow-up. Store that comment URL in the review row. +6. **Update progressively.** Update the audit record immediately after each + commit, PR reply, or resolution. Preserve the historical GitHub review state + (including `DISMISSED`) and record the current disposition in the audit + fields. +7. **Complete.** Verify every finding status and every inline-thread state from + the current PR. Commit the workflow/audit documentation separately with a + signed `docs(review): ...` commit. + +## GitHub CLI Queries + +Fetch an individual review and its review-specific inline comments: + +```bash +gh api repos/torrust/torrust-tracker/pulls//reviews/ +gh api repos/torrust/torrust-tracker/pulls//reviews//comments?per_page=100 +``` + +Fetch all review threads with GraphQL before resolving inline feedback. Use the +repository `fetch-review-threads` skill for the supported scripts and query +shape. + +Post a consolidated response as a PR conversation comment: + +```bash +gh pr comment --repo torrust/torrust-tracker --body-file +``` + +## Completion Checklist + +- [ ] Reviews and their inline comments fetched by review ID +- [ ] Audit record has one row per review and one row per independent finding +- [ ] Each action validated and committed independently +- [ ] Each inline thread replied to and resolved, with reply URL recorded +- [ ] Each review summary has one consolidated PR response, with URL recorded +- [ ] Historical review states and current audit statuses are both recorded +- [ ] Workflow/audit documentation committed separately from product fixes diff --git a/docs/AGENTS.md b/docs/AGENTS.md index e583dc6c5..208a5d167 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -36,6 +36,7 @@ For the full project context see the [root AGENTS.md](../AGENTS.md). | `issues/` | Issue specification documents linked to GitHub issues | | `refactor-plans/` | Refactor plans (same lifecycle as issue specs) | | `copilot-pr-reviews/` | Copilot PR review records and suggestion threads | +| `pr-review-feedback/` | PR review feedback audit records | | `skills/` | Internal conventions used by humans and AI agents | | `testing/` | Durable testing guidance and test-design refactoring pattern catalog | | `templates/` | Canonical document templates (ADR, agent review reports, EPIC, issue, refactor plan, security analysis) | diff --git a/docs/index.md b/docs/index.md index d503da2e8..ba5c5d9e6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,6 +19,7 @@ semantic-links: - docs/adrs/index.md - docs/issues/README.md - docs/copilot-pr-reviews/README.md + - docs/pr-review-feedback/README.md - docs/refactor-plans/closed/README.md - docs/refactor-plans/drafts/README.md - docs/refactor-plans/open/README.md @@ -127,6 +128,14 @@ Records of Copilot pull request suggestion reviews. | ------------------------------------------------------------ | ----------------------------------------- | | [copilot-pr-reviews/README.md](copilot-pr-reviews/README.md) | Overview of the Copilot PR review archive | +## Pull Request Review Feedback + +Records of review summaries, their independently tracked findings, and consolidated responses. + +| Document | Description | +| ------------------------------------------------------ | -------------------------------------------- | +| [pr-review-feedback/README.md](pr-review-feedback/README.md) | Overview of the PR review feedback archive | + ## Skills and Conventions Internal documentation on project-specific conventions used by both humans and AI agents. @@ -151,6 +160,7 @@ that type. | [templates/SECURITY-ANALYSIS.md](templates/SECURITY-ANALYSIS.md) | Template for public scanner-finding and vulnerability analysis | | [templates/SECURITY-REPORT.md](templates/SECURITY-REPORT.md) | Template for handled coordinated-disclosure records | | [templates/COPILOT-SUGGESTIONS-TEMPLATE.md](templates/COPILOT-SUGGESTIONS-TEMPLATE.md) | Template for recording Copilot PR review suggestions | +| [templates/PR-REVIEW-FEEDBACK-TEMPLATE.md](templates/PR-REVIEW-FEEDBACK-TEMPLATE.md) | Template for tracking PR review findings and responses | ## Media diff --git a/docs/pr-review-feedback/README.md b/docs/pr-review-feedback/README.md new file mode 100644 index 000000000..8fd3cb86a --- /dev/null +++ b/docs/pr-review-feedback/README.md @@ -0,0 +1,29 @@ +--- +semantic-links: + skill-links: + - process-pr-review-feedback + related-artifacts: + - docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md + - .github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md +--- + +# Pull Request Review Feedback + +This directory retains audit records for pull-request feedback from one or +more reviewers. A reviewer may be a maintainer, collaborator, or an agent +acting for either. It is separate from +[Copilot PR suggestion audits](../copilot-pr-reviews/), whose inputs are +thread-oriented suggestions generated by Copilot. + +Create one record per pull request from +[the PR review feedback template](../templates/PR-REVIEW-FEEDBACK-TEMPLATE.md), +named `pr--review-feedback.md`. + +A submitted review summary may contain multiple problems. Track each +independent problem as a finding and commit each fix independently. Reply once +to the submitted review with a consolidated outcome after all its findings are +done. + +Inline review suggestions remain GitHub review threads. Reply to and resolve +those threads using the same mechanics as the Copilot thread workflow, while +also recording their state in the review-feedback audit. diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md new file mode 100644 index 000000000..e286395f9 --- /dev/null +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -0,0 +1,45 @@ +--- +semantic-links: + skill-links: + - process-pr-review-feedback + related-artifacts: + - docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md + - .github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md +--- + + + +# PR #2178 Review Feedback Tracking + +Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2178/reviews) for [PR #2178](https://github.com/torrust/torrust-tracker/pull/2178). + +## Reviews + +| Review ID | Submitted at (UTC) | Reviewer | State | URL | Reviewed commit | Consolidated response URL | Response state | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `5146523360` | 2026-09-08 20:09 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5146523360) | `feb53850` | Pending | `PENDING` | +| `5150709986` | 2026-09-09 06:47 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | `8e9c0b68` | Pending | `PENDING` | +| `5151181594` | 2026-09-09 07:37 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | `957ed394` | Pending | `PENDING` | + +## Findings + +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | + +## Processing Log + +- 2026-09-09 08:55 UTC - Created this audit after all currently known reviewer findings had been addressed. F1-F4 were fixed independently in `957ed394` and their inline threads were replied to and resolved. F5 and F7 are re-review/approval observations, not new implementation work. F6 was explicitly declined as a pre-existing, out-of-scope terminology sweep; its thread was replied to and resolved. +- 2026-09-09 08:55 UTC - Pending: post one consolidated PR conversation response for each submitted review and record each response URL in the Reviews table. The historical GitHub review states remain unchanged; `Response state` is the current audit status. + +## Notes + +- The initial review also discussed two Copilot-generated threads. They are tracked in the Copilot review workflow, not duplicated here. +- Review `5151181594` is `DISMISSED` because later branch changes superseded its reviewed commit. Its approval remains evidence that F1-F4 were independently verified at `957ed394`. +- The later no-runtime cleanup correction `d7bd7baf` was prompted by an independent PR review, not one of the three Cameron reviews recorded here. diff --git a/docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md b/docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md new file mode 100644 index 000000000..162a3869e --- /dev/null +++ b/docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md @@ -0,0 +1,71 @@ +--- +semantic-links: + skill-links: + - process-pr-review-feedback + related-artifacts: + - .github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md +--- + + + +# PR # Review Feedback Tracking + +Source: pull-request reviews and inline review comments for . + +## Purpose + +Track review feedback independently from Copilot review-thread audits. A +review summary can contain several independently actionable findings, so this +record has one **review** row and one or more **finding** rows per review. + +A GitHub review ID identifies the submitted review. An inline review thread ID +identifies a resolvable suggestion thread. They are different resources and +must not be used interchangeably. + +## Status Values + +- Finding decision: `ACTION`, `NO_ACTION`, `FOLLOW_UP` +- Finding status: `OPEN`, `IN_PROGRESS`, `DONE`, `BLOCKED` +- Inline thread state: `NOT_APPLICABLE`, `OPEN`, `RESOLVED` +- Review response state: `PENDING`, `POSTED` + +## Workflow + +1. Record each submitted review by review ID and URL. +2. Decompose every review body and inline comment into independent findings. +3. Commit each independent action separately; do not mix workflow documentation + with product fixes. +4. For an inline suggestion thread, reply with the outcome and resolve it after + its action or no-action decision is complete. +5. For a review-level summary, post one consolidated PR comment after all of + that review's findings are complete. Link that response from the review row. +6. Update this audit record after every decision, commit, reply, or resolution. + +## Reviews + +| Review ID | Submitted at (UTC) | Reviewer | State | URL | Reviewed commit | Consolidated response URL | Response state | +| --- | --- | --- | --- | --- | --- | --- | --- | +| | | | | | | | | + +## Findings + +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| F1 | | | | | | | | | | | | + +## Processing Log + +- - Started audit. + +## Notes + +- A `CHANGES_REQUESTED` review state is historical evidence of the reviewer's + decision, not a per-finding completion state. Keep it unchanged in the table. +- A `DISMISSED` review remains an auditable review record; record why it was + superseded in the processing log or finding notes. +- GitHub does not expose a dedicated reply resource for a submitted review + summary. Use a normal PR conversation comment as the consolidated response + and record its comment URL. +- Do not resolve an inline review thread without first replying on that thread. +- A review-level comment with no inline thread cannot be marked resolved in + GitHub. The review ID plus this record's response state is the durable status. From 0dbce8b09d8e41d43944ca5f31422d2fbf83d887 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 09:22:42 +0100 Subject: [PATCH 32/44] docs(review): record PR 2178 feedback responses --- docs/AGENTS.md | 2 +- docs/index.md | 4 +-- .../pr-2178-review-feedback.md | 30 +++++++++---------- docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md | 12 ++++---- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 208a5d167..0d4f12ff2 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -36,7 +36,7 @@ For the full project context see the [root AGENTS.md](../AGENTS.md). | `issues/` | Issue specification documents linked to GitHub issues | | `refactor-plans/` | Refactor plans (same lifecycle as issue specs) | | `copilot-pr-reviews/` | Copilot PR review records and suggestion threads | -| `pr-review-feedback/` | PR review feedback audit records | +| `pr-review-feedback/` | PR review feedback audit records | | `skills/` | Internal conventions used by humans and AI agents | | `testing/` | Durable testing guidance and test-design refactoring pattern catalog | | `templates/` | Canonical document templates (ADR, agent review reports, EPIC, issue, refactor plan, security analysis) | diff --git a/docs/index.md b/docs/index.md index ba5c5d9e6..6ae795291 100644 --- a/docs/index.md +++ b/docs/index.md @@ -132,8 +132,8 @@ Records of Copilot pull request suggestion reviews. Records of review summaries, their independently tracked findings, and consolidated responses. -| Document | Description | -| ------------------------------------------------------ | -------------------------------------------- | +| Document | Description | +| ------------------------------------------------------------ | ------------------------------------------ | | [pr-review-feedback/README.md](pr-review-feedback/README.md) | Overview of the PR review feedback archive | ## Skills and Conventions diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index e286395f9..3d28ce7e4 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -15,28 +15,28 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 ## Reviews -| Review ID | Submitted at (UTC) | Reviewer | State | URL | Reviewed commit | Consolidated response URL | Response state | -| --- | --- | --- | --- | --- | --- | --- | --- | -| `5146523360` | 2026-09-08 20:09 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5146523360) | `feb53850` | Pending | `PENDING` | -| `5150709986` | 2026-09-09 06:47 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | `8e9c0b68` | Pending | `PENDING` | -| `5151181594` | 2026-09-09 07:37 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | `957ed394` | Pending | `PENDING` | +| Review ID | Submitted at (UTC) | Reviewer | State | URL | Reviewed commit | Consolidated response URL | Response state | +| ------------ | ------------------ | -------- | ------------------- | ------------------------------------------------------------------------------------------- | --------------- | ---------------------------------------------------------------------------------------- | -------------- | +| `5146523360` | 2026-09-08 20:09 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5146523360) | `feb53850` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598628374) | `POSTED` | +| `5150709986` | 2026-09-09 06:47 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | `8e9c0b68` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598628743) | `POSTED` | +| `5151181594` | 2026-09-09 07:37 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | `957ed394` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598629024) | `POSTED` | ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | -| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | -| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | -| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | -| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | -| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | -| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------- | ------ | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | ## Processing Log - 2026-09-09 08:55 UTC - Created this audit after all currently known reviewer findings had been addressed. F1-F4 were fixed independently in `957ed394` and their inline threads were replied to and resolved. F5 and F7 are re-review/approval observations, not new implementation work. F6 was explicitly declined as a pre-existing, out-of-scope terminology sweep; its thread was replied to and resolved. -- 2026-09-09 08:55 UTC - Pending: post one consolidated PR conversation response for each submitted review and record each response URL in the Reviews table. The historical GitHub review states remain unchanged; `Response state` is the current audit status. +- 2026-09-09 08:20 UTC - Posted one consolidated PR conversation response for each submitted review and recorded its URL above. The historical GitHub review states remain unchanged; `Response state` is the current audit status. ## Notes diff --git a/docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md b/docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md index 162a3869e..42d40ce80 100644 --- a/docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md +++ b/docs/templates/PR-REVIEW-FEEDBACK-TEMPLATE.md @@ -43,15 +43,15 @@ must not be used interchangeably. ## Reviews -| Review ID | Submitted at (UTC) | Reviewer | State | URL | Reviewed commit | Consolidated response URL | Response state | -| --- | --- | --- | --- | --- | --- | --- | --- | -| | | | | | | | | +| Review ID | Submitted at (UTC) | Reviewer | State | URL | Reviewed commit | Consolidated response URL | Response state | +| ----------- | ------------------ | ---------- | -------------- | ------------ | --------------- | ------------------------- | ------------------- | +| | | | | | | | | ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| F1 | | | | | | | | | | | | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ----------- | ----------------------- | ---------------------- | ------------- | --------- | ---------- | ---------------------- | ------------ | ----------------- | ------------------- | -------- | +| F1 | | | | | | | | | | | | ## Processing Log From 8b3927c1b5587a62e0f449e3a5f79efc7db991af Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 09:39:54 +0100 Subject: [PATCH 33/44] fix(test): bound no-runtime tracker reaping --- tests/common/native_tracker.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/common/native_tracker.rs b/tests/common/native_tracker.rs index 32214a349..d201f73b2 100644 --- a/tests/common/native_tracker.rs +++ b/tests/common/native_tracker.rs @@ -841,8 +841,12 @@ impl Drop for NativeTrackerFailedStart { // release the fixture workspace. let Ok(runtime) = tokio::runtime::Handle::try_current() else { drop(child.start_kill()); - while child.try_wait().ok().flatten().is_none() { - std::thread::yield_now(); + let deadline = std::time::Instant::now() + FAILURE_DEADLINE; + while std::time::Instant::now() < deadline { + match child.try_wait() { + Ok(Some(_)) | Err(_) => break, + Ok(None) => std::thread::sleep(RETRY_INTERVAL), + } } drop(output); return; @@ -1195,7 +1199,7 @@ mod tests { } #[tokio::test] - async fn it_should_not_panic_when_a_failed_start_is_dropped_without_a_tokio_runtime() { + async fn it_should_reap_a_failed_start_dropped_without_a_tokio_runtime() { // Arrange: spawning needs a runtime; the drop below happens outside one. let mut child = Command::new("sleep") .arg("60") From aeb3c53341311870cd4f85586a8ab5027ba9f5da Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 09:43:06 +0100 Subject: [PATCH 34/44] docs(review): complete PR 2178 feedback audit --- .../pr-2178-review-feedback.md | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index 3d28ce7e4..8339c2a1a 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -20,23 +20,31 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 | `5146523360` | 2026-09-08 20:09 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5146523360) | `feb53850` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598628374) | `POSTED` | | `5150709986` | 2026-09-09 06:47 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | `8e9c0b68` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598628743) | `POSTED` | | `5151181594` | 2026-09-09 07:37 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | `957ed394` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598629024) | `POSTED` | +| `5151425410` | 2026-09-09 08:02 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151425410) | `d7bd7baf` | Pending | `PENDING` | +| `5151742675` | 2026-09-09 08:31 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151742675) | `0dbce8b0` | Pending | `PENDING` | ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------- | ------ | -| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | -| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | -| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | -| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | -| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | -| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | -| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------- | ------------- | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | +| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | +| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | Pending | Audit now records review `5151425410`. | Pending | `OPEN` | `IN_PROGRESS` | +| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | Pending | Audit now records F8-F9 with current thread state. | Pending | `OPEN` | `IN_PROGRESS` | ## Processing Log - 2026-09-09 08:55 UTC - Created this audit after all currently known reviewer findings had been addressed. F1-F4 were fixed independently in `957ed394` and their inline threads were replied to and resolved. F5 and F7 are re-review/approval observations, not new implementation work. F6 was explicitly declined as a pre-existing, out-of-scope terminology sweep; its thread was replied to and resolved. - 2026-09-09 08:20 UTC - Posted one consolidated PR conversation response for each submitted review and recorded its URL above. The historical GitHub review states remain unchanged; `Response state` is the current audit status. +- 2026-09-09 08:40 UTC - Recorded review `5151425410`, its F8-F9 inline findings, and review `5151742675` with F10-F11 record-accuracy findings. F8-F11 are in progress; do not treat the earlier three-review audit as complete. +- 2026-09-09 08:45 UTC - Completed F8-F9 in `8b3927c1`; each thread was replied to and resolved. This audit correction completes F10-F11 by retaining the formerly omitted review and its findings. Pending: commit this correction, reply to and resolve F10-F11, then post consolidated responses for reviews `5151425410` and `5151742675`. ## Notes From a33fdafcb295141c4d53e9ba9847352ac94a2f46 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 09:48:03 +0100 Subject: [PATCH 35/44] docs(review): record PR 2178 final feedback outcomes --- .../pr-2178-review-feedback.md | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index 8339c2a1a..7d73894d8 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -20,24 +20,24 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 | `5146523360` | 2026-09-08 20:09 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5146523360) | `feb53850` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598628374) | `POSTED` | | `5150709986` | 2026-09-09 06:47 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | `8e9c0b68` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598628743) | `POSTED` | | `5151181594` | 2026-09-09 07:37 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | `957ed394` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598629024) | `POSTED` | -| `5151425410` | 2026-09-09 08:02 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151425410) | `d7bd7baf` | Pending | `PENDING` | -| `5151742675` | 2026-09-09 08:31 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151742675) | `0dbce8b0` | Pending | `PENDING` | +| `5151425410` | 2026-09-09 08:02 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151425410) | `d7bd7baf` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5599020210) | `POSTED` | +| `5151742675` | 2026-09-09 08:31 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151742675) | `0dbce8b0` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5599020464) | `POSTED` | ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------- | ------------- | -| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | -| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | -| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | -| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | -| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | -| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | -| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | -| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | -| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | -| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | Pending | Audit now records review `5151425410`. | Pending | `OPEN` | `IN_PROGRESS` | -| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | Pending | Audit now records F8-F9 with current thread state. | Pending | `OPEN` | `IN_PROGRESS` | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------- | ------ | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | +| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | +| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | +| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | ## Processing Log @@ -45,9 +45,11 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 08:20 UTC - Posted one consolidated PR conversation response for each submitted review and recorded its URL above. The historical GitHub review states remain unchanged; `Response state` is the current audit status. - 2026-09-09 08:40 UTC - Recorded review `5151425410`, its F8-F9 inline findings, and review `5151742675` with F10-F11 record-accuracy findings. F8-F11 are in progress; do not treat the earlier three-review audit as complete. - 2026-09-09 08:45 UTC - Completed F8-F9 in `8b3927c1`; each thread was replied to and resolved. This audit correction completes F10-F11 by retaining the formerly omitted review and its findings. Pending: commit this correction, reply to and resolve F10-F11, then post consolidated responses for reviews `5151425410` and `5151742675`. +- 2026-09-09 08:50 UTC - Completed F10-F11 in `aeb3c533`; both threads were replied to and resolved. Posted and recorded consolidated responses for reviews `5151425410` and `5151742675`. All findings currently recorded in this audit are complete. ## Notes - The initial review also discussed two Copilot-generated threads. They are tracked in the Copilot review workflow, not duplicated here. - Review `5151181594` is `DISMISSED` because later branch changes superseded its reviewed commit. Its approval remains evidence that F1-F4 were independently verified at `957ed394`. - The later no-runtime cleanup correction `d7bd7baf` was prompted by an independent PR review, not one of the three Cameron reviews recorded here. +- Review `5151425410` subsequently suggested hardening its no-runtime cleanup fallback; F8-F9 record the separately committed `8b3927c1` follow-up. From 3174a6cdcec8ebc81c506141f0ccacdba22e3db7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 10:01:19 +0100 Subject: [PATCH 36/44] docs(review): correct PR 2178 audit chronology --- docs/pr-review-feedback/pr-2178-review-feedback.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index 7d73894d8..fab6663a1 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -38,14 +38,16 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 | F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | | F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | | F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | +| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | Pending | Pending | Pending | `OPEN` | `IN_PROGRESS` | ## Processing Log -- 2026-09-09 08:55 UTC - Created this audit after all currently known reviewer findings had been addressed. F1-F4 were fixed independently in `957ed394` and their inline threads were replied to and resolved. F5 and F7 are re-review/approval observations, not new implementation work. F6 was explicitly declined as a pre-existing, out-of-scope terminology sweep; its thread was replied to and resolved. +- 2026-09-09 08:15 UTC - Created this audit after all currently known reviewer findings had been addressed. F1-F4 were fixed independently in `957ed394` and their inline threads were replied to and resolved. F5 and F7 are re-review/approval observations, not new implementation work. F6 was explicitly declined as a pre-existing, out-of-scope terminology sweep; its thread was replied to and resolved. - 2026-09-09 08:20 UTC - Posted one consolidated PR conversation response for each submitted review and recorded its URL above. The historical GitHub review states remain unchanged; `Response state` is the current audit status. - 2026-09-09 08:40 UTC - Recorded review `5151425410`, its F8-F9 inline findings, and review `5151742675` with F10-F11 record-accuracy findings. F8-F11 are in progress; do not treat the earlier three-review audit as complete. - 2026-09-09 08:45 UTC - Completed F8-F9 in `8b3927c1`; each thread was replied to and resolved. This audit correction completes F10-F11 by retaining the formerly omitted review and its findings. Pending: commit this correction, reply to and resolve F10-F11, then post consolidated responses for reviews `5151425410` and `5151742675`. - 2026-09-09 08:50 UTC - Completed F10-F11 in `aeb3c533`; both threads were replied to and resolved. Posted and recorded consolidated responses for reviews `5151425410` and `5151742675`. All findings currently recorded in this audit are complete. +- 2026-09-09 08:55 UTC - Recorded F12 from review `5151742675`; it corrects the processing-log chronology and is in progress. ## Notes From 78f4cb585f91f9c39d74e928717ab5c4b84838f7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 10:31:21 +0100 Subject: [PATCH 37/44] docs(review): add PR 2178 Copilot audit --- .../pr-2178-copilot-suggestions.md | 30 +++++++++++++++++++ .../pr-2178-review-feedback.md | 30 ++++++++++--------- 2 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 docs/copilot-pr-reviews/pr-2178-copilot-suggestions.md diff --git a/docs/copilot-pr-reviews/pr-2178-copilot-suggestions.md b/docs/copilot-pr-reviews/pr-2178-copilot-suggestions.md new file mode 100644 index 000000000..482e72f78 --- /dev/null +++ b/docs/copilot-pr-reviews/pr-2178-copilot-suggestions.md @@ -0,0 +1,30 @@ +--- +semantic-links: + skill-links: + - process-copilot-suggestions + related-artifacts: + - docs/templates/COPILOT-SUGGESTIONS-TEMPLATE.md + - .github/skills/dev/pr-reviews/process-copilot-suggestions/SKILL.md +--- + + + +# PR #2178 Copilot Suggestions Tracking + +Source: Copilot PR review threads for [PR #2178](https://github.com/torrust/torrust-tracker/pull/2178). + +## Processing Log + +- 2026-09-09 09:00 UTC - Created this audit for the two Copilot threads that were previously handled inline. Both were resolved before this record was added; their decision, fix, and reply URLs were verified from the current PR thread state. + +## Suggestions + +| # | Thread ID | Path | URL | Suggestion Summary | Decision | Reply URL | Status | Thread State | +| --- | ----------------------- | ----------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------ | ------------ | +| 1 | `PRRT_kwDOGp2yqc6gYHrC` | `packages/configuration/src/lib.rs` | [thread](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961389301) | Explicit-file log said “extra configuration,” implying additive source selection. | `ACTION`: renamed it to “base configuration” in `8e9c0b68`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965743966) | `DONE` | `RESOLVED` | +| 2 | `PRRT_kwDOGp2yqc6gYHrU` | `packages/configuration/src/lib.rs` | [thread](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961389339) | Explicit-file reader accepted `&PathBuf` rather than the more general `&Path`. | `ACTION`: accepts `&Path` and owns paths only in errors in `8e9c0b68`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744330) | `DONE` | `RESOLVED` | + +## Notes + +- Commit `8e9c0b68` passed configuration tests, focused Clippy, formatting, and the required pre-commit gate before the threads were resolved. +- This late record corrects the missing audit reference identified by PR review feedback; it does not claim the original thread handling occurred after this file was created. diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index fab6663a1..438d3fecd 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -25,20 +25,21 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------- | ------ | -| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | -| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | -| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | -| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | -| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | -| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | -| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | -| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | -| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | -| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | -| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | -| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | Pending | Pending | Pending | `OPEN` | `IN_PROGRESS` | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------- | ------------- | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | +| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | +| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | +| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | +| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | +| F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | Pending | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | Pending | `OPEN` | `IN_PROGRESS` | ## Processing Log @@ -48,6 +49,7 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 08:45 UTC - Completed F8-F9 in `8b3927c1`; each thread was replied to and resolved. This audit correction completes F10-F11 by retaining the formerly omitted review and its findings. Pending: commit this correction, reply to and resolve F10-F11, then post consolidated responses for reviews `5151425410` and `5151742675`. - 2026-09-09 08:50 UTC - Completed F10-F11 in `aeb3c533`; both threads were replied to and resolved. Posted and recorded consolidated responses for reviews `5151425410` and `5151742675`. All findings currently recorded in this audit are complete. - 2026-09-09 08:55 UTC - Recorded F12 from review `5151742675`; it corrects the processing-log chronology and is in progress. +- 2026-09-09 09:00 UTC - Completed F12 in `3174a6cd`; its thread was replied to and resolved. Created the missing Copilot thread audit for F13; pending validation, separate commit, and F13 thread response. ## Notes From df6ccf2e7de39822c7381a3e682842926aa6f583 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 10:51:27 +0100 Subject: [PATCH 38/44] docs(review): finalize PR 2178 feedback audit --- .../pr-2178-review-feedback.md | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index 438d3fecd..a67187b8b 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -25,21 +25,21 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------- | ------------- | -| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | -| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | -| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | -| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | -| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | -| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | -| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | -| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | -| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | -| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | -| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | -| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | -| F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | Pending | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | Pending | `OPEN` | `IN_PROGRESS` | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------- | ------ | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | +| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | +| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | +| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | +| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | +| F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | `78f4cb58` | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966897692) | `RESOLVED` | `DONE` | ## Processing Log @@ -50,6 +50,7 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 08:50 UTC - Completed F10-F11 in `aeb3c533`; both threads were replied to and resolved. Posted and recorded consolidated responses for reviews `5151425410` and `5151742675`. All findings currently recorded in this audit are complete. - 2026-09-09 08:55 UTC - Recorded F12 from review `5151742675`; it corrects the processing-log chronology and is in progress. - 2026-09-09 09:00 UTC - Completed F12 in `3174a6cd`; its thread was replied to and resolved. Created the missing Copilot thread audit for F13; pending validation, separate commit, and F13 thread response. +- 2026-09-09 09:10 UTC - Completed F13 in `78f4cb58`; its thread was replied to and resolved. All currently known PR #2178 review-feedback findings are complete. ## Notes From 67feff3ab1ff196c52cc644e3662c64af7859399 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 11:09:12 +0100 Subject: [PATCH 39/44] docs(review): correct PR 2178 feedback identifier --- docs/pr-review-feedback/pr-2178-review-feedback.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index a67187b8b..b31e78631 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -37,9 +37,10 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 | F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | | F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | | F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | -| F11 | `5151742675` | Inline review comment | `3966277176` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277176) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | +| F11 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | | F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | | F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | `78f4cb58` | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966897692) | `RESOLVED` | `DONE` | +| F14 | `5151742675` | Inline review comment | `3966542753` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966542753) | F11 cited an invalid review-comment ID and dead URL. | `ACTION` | Pending | GitHub confirms the parent ID is `3966277146`. | Pending | `OPEN` | `IN_PROGRESS` | ## Processing Log @@ -51,6 +52,7 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 08:55 UTC - Recorded F12 from review `5151742675`; it corrects the processing-log chronology and is in progress. - 2026-09-09 09:00 UTC - Completed F12 in `3174a6cd`; its thread was replied to and resolved. Created the missing Copilot thread audit for F13; pending validation, separate commit, and F13 thread response. - 2026-09-09 09:10 UTC - Completed F13 in `78f4cb58`; its thread was replied to and resolved. All currently known PR #2178 review-feedback findings are complete. +- 2026-09-09 10:55 UTC - Recorded F14 from review `5151742675`; corrected F11's review-comment ID and URL after verifying reply `3966391039` has `in_reply_to_id` `3966277146`. Pending validation, a separate correction commit, and F14 thread resolution. ## Notes From 09d1de6138831df21cd8eb0de767c08f18344500 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 11:11:45 +0100 Subject: [PATCH 40/44] docs(skills): harden PR feedback thread discovery --- .../dev/pr-reviews/process-pr-review-feedback/SKILL.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md b/.github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md index 05ecaa785..7cbf0f7de 100644 --- a/.github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md +++ b/.github/skills/dev/pr-reviews/process-pr-review-feedback/SKILL.md @@ -36,8 +36,9 @@ state as the durable completion status for a review-level summary. 1. **Fetch reviews and threads.** Query submitted reviews and inline comments by review ID. Fetch all review threads separately, including their IDs, - author, paths, bodies, and resolved state. Do not assume review-comment IDs - are thread IDs. + author, paths, bodies, and resolved state. The GraphQL thread query is the + authority: the REST per-review `comments` endpoint can under-report inline + comments. Do not assume review-comment IDs are thread IDs. 2. **Create the audit record.** Add one row per review. Decompose each review body and inline comment into one row per independent finding, with a decision of `ACTION`, `NO_ACTION`, or `FOLLOW_UP`. From 635ff5a226f98198a2059c1de5abfe9feaea670c Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 11:23:06 +0100 Subject: [PATCH 41/44] docs(review): finalize PR 2178 feedback workflow --- .../pr-2178-review-feedback.md | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index b31e78631..d3662c9ac 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -25,22 +25,23 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------- | ------ | -| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | -| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | -| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | -| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | -| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | -| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | -| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | -| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | -| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | -| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | -| F11 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | -| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | -| F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | `78f4cb58` | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966897692) | `RESOLVED` | `DONE` | -| F14 | `5151742675` | Inline review comment | `3966542753` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966542753) | F11 cited an invalid review-comment ID and dead URL. | `ACTION` | Pending | GitHub confirms the parent ID is `3966277146`. | Pending | `OPEN` | `IN_PROGRESS` | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------- | ------------- | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | +| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | +| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | +| F11 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | +| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | +| F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | `78f4cb58` | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966897692) | `RESOLVED` | `DONE` | +| F14 | `5151742675` | Inline review comment | `3966542753` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966542753) | F11 cited an invalid review-comment ID and dead URL. | `ACTION` | `67feff3a` | GitHub confirms the parent ID is `3966277146`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967148984) | `RESOLVED` | `DONE` | +| F15 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | REST per-review comments can omit inline threads, leaving the audit incomplete. | `ACTION` | `09d1de61` | GraphQL review threads are documented as authoritative. | N/A | `NOT_APPLICABLE` | `DONE` | ## Processing Log @@ -53,6 +54,8 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 09:00 UTC - Completed F12 in `3174a6cd`; its thread was replied to and resolved. Created the missing Copilot thread audit for F13; pending validation, separate commit, and F13 thread response. - 2026-09-09 09:10 UTC - Completed F13 in `78f4cb58`; its thread was replied to and resolved. All currently known PR #2178 review-feedback findings are complete. - 2026-09-09 10:55 UTC - Recorded F14 from review `5151742675`; corrected F11's review-comment ID and URL after verifying reply `3966391039` has `in_reply_to_id` `3966277146`. Pending validation, a separate correction commit, and F14 thread resolution. +- 2026-09-09 11:05 UTC - Completed F14 in `67feff3a`; its thread was replied to and resolved. Recorded F15 from the same review: the REST per-review comments endpoint omitted F8-F9, so the review-feedback skill now treats GraphQL review-thread data as authoritative. Pending validation and a separate workflow-only commit. +- 2026-09-09 11:15 UTC - Completed F15 in `09d1de61`. All currently known PR #2178 review-feedback findings are complete. ## Notes From 7f313b4f5b92850e3e162df663998576819333be Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 11:43:36 +0100 Subject: [PATCH 42/44] docs(review): correct PR 2178 review history --- .../pr-2178-review-feedback.md | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index d3662c9ac..2f14f14d9 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -22,6 +22,9 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 | `5151181594` | 2026-09-09 07:37 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | `957ed394` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598629024) | `POSTED` | | `5151425410` | 2026-09-09 08:02 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151425410) | `d7bd7baf` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5599020210) | `POSTED` | | `5151742675` | 2026-09-09 08:31 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151742675) | `0dbce8b0` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5599020464) | `POSTED` | +| `5152084856` | 2026-09-09 09:00 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152084856) | `a33fdafc` | Pending | `PENDING` | +| `5152707859` | 2026-09-09 10:00 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152707859) | `df6ccf2e` | Pending | `PENDING` | +| `5152875522` | 2026-09-09 10:15 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152875522) | `09d1de61` | Pending | `PENDING` | ## Findings @@ -40,8 +43,11 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 | F11 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | | F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | | F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | `78f4cb58` | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966897692) | `RESOLVED` | `DONE` | -| F14 | `5151742675` | Inline review comment | `3966542753` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966542753) | F11 cited an invalid review-comment ID and dead URL. | `ACTION` | `67feff3a` | GitHub confirms the parent ID is `3966277146`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967148984) | `RESOLVED` | `DONE` | -| F15 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | REST per-review comments can omit inline threads, leaving the audit incomplete. | `ACTION` | `09d1de61` | GraphQL review threads are documented as authoritative. | N/A | `NOT_APPLICABLE` | `DONE` | +| F14 | `5152084856` | Inline review comment | `3966542753` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966542753) | F11 cited an invalid review-comment ID and dead URL. | `ACTION` | `67feff3a` | GitHub confirms the parent ID is `3966277146`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967148984) | `RESOLVED` | `DONE` | +| F15 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | REST per-review comments can omit inline threads, leaving the audit incomplete. | `ACTION` | `09d1de61` | GraphQL review threads are documented as authoritative. | N/A | `NOT_APPLICABLE` | `DONE` | +| F16 | `5152084856` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152084856) | Re-review confirmed F8-F9 and identified F12-F13 as outstanding record corrections. | `NO_ACTION` | N/A | F12-F13 were fixed in `3174a6cd` and `78f4cb58`. | N/A | `NOT_APPLICABLE` | `DONE` | +| F17 | `5152707859` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152707859) | Approval confirmed F12-F13; it noted F14 and audit-language accuracy. | `ACTION` | Pending | Pending | Pending | `NOT_APPLICABLE` | `IN_PROGRESS` | +| F18 | `5152875522` | Inline review comment | `3967199432` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967199432) | F14 processing-log entry postdates its commit and breaks chronology. | `ACTION` | Pending | Pending | Pending | `OPEN` | `IN_PROGRESS` | ## Processing Log @@ -52,10 +58,11 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 08:50 UTC - Completed F10-F11 in `aeb3c533`; both threads were replied to and resolved. Posted and recorded consolidated responses for reviews `5151425410` and `5151742675`. All findings currently recorded in this audit are complete. - 2026-09-09 08:55 UTC - Recorded F12 from review `5151742675`; it corrects the processing-log chronology and is in progress. - 2026-09-09 09:00 UTC - Completed F12 in `3174a6cd`; its thread was replied to and resolved. Created the missing Copilot thread audit for F13; pending validation, separate commit, and F13 thread response. -- 2026-09-09 09:10 UTC - Completed F13 in `78f4cb58`; its thread was replied to and resolved. All currently known PR #2178 review-feedback findings are complete. -- 2026-09-09 10:55 UTC - Recorded F14 from review `5151742675`; corrected F11's review-comment ID and URL after verifying reply `3966391039` has `in_reply_to_id` `3966277146`. Pending validation, a separate correction commit, and F14 thread resolution. -- 2026-09-09 11:05 UTC - Completed F14 in `67feff3a`; its thread was replied to and resolved. Recorded F15 from the same review: the REST per-review comments endpoint omitted F8-F9, so the review-feedback skill now treats GraphQL review-thread data as authoritative. Pending validation and a separate workflow-only commit. -- 2026-09-09 11:15 UTC - Completed F15 in `09d1de61`. All currently known PR #2178 review-feedback findings are complete. +- 2026-09-09 09:10 UTC - Completed F13 in `78f4cb58`; its thread was replied to and resolved. All findings processed through review `5151742675` were complete. +- 2026-09-09 10:03 UTC - Recorded F14 from review `5152084856`; corrected F11's review-comment ID and URL after verifying reply `3966391039` has `in_reply_to_id` `3966277146`. Pending validation, a separate correction commit, and F14 thread resolution. +- 2026-09-09 10:09 UTC - Completed F14 in `67feff3a`; its thread was replied to and resolved. Recorded F15 from review `5151742675`: the REST per-review comments endpoint omitted F8-F9, so the review-feedback skill now treats GraphQL review-thread data as authoritative. Pending validation and a separate workflow-only commit. +- 2026-09-09 10:12 UTC - Completed F15 in `09d1de61`. All findings processed through review `5151742675` were complete. +- 2026-09-09 10:15 UTC - Recorded reviews `5152084856`, `5152707859`, and `5152875522` from the current PR state. F16 is complete because F12-F13 were already fixed; F17-F18 are in progress to correct the audit-language and chronology gaps they identify. ## Notes @@ -63,3 +70,4 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - Review `5151181594` is `DISMISSED` because later branch changes superseded its reviewed commit. Its approval remains evidence that F1-F4 were independently verified at `957ed394`. - The later no-runtime cleanup correction `d7bd7baf` was prompted by an independent PR review, not one of the three Cameron reviews recorded here. - Review `5151425410` subsequently suggested hardening its no-runtime cleanup fallback; F8-F9 record the separately committed `8b3927c1` follow-up. +- Review records are an observed snapshot, not a claim that no future review can arrive. Before declaring the PR ready, refresh the GitHub review list and append newly submitted reviews before posting a final completion response. From 19548829c775866e2a3ee7aa0f2e5d07905463a9 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 11:46:55 +0100 Subject: [PATCH 43/44] docs(review): record PR 2178 review outcomes --- .../pr-2178-review-feedback.md | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index 2f14f14d9..d2a1d6b99 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -22,32 +22,32 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 | `5151181594` | 2026-09-09 07:37 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | `957ed394` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5598629024) | `POSTED` | | `5151425410` | 2026-09-09 08:02 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151425410) | `d7bd7baf` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5599020210) | `POSTED` | | `5151742675` | 2026-09-09 08:31 | `da2ce7` | `CHANGES_REQUESTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151742675) | `0dbce8b0` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5599020464) | `POSTED` | -| `5152084856` | 2026-09-09 09:00 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152084856) | `a33fdafc` | Pending | `PENDING` | -| `5152707859` | 2026-09-09 10:00 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152707859) | `df6ccf2e` | Pending | `PENDING` | -| `5152875522` | 2026-09-09 10:15 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152875522) | `09d1de61` | Pending | `PENDING` | +| `5152084856` | 2026-09-09 09:00 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152084856) | `a33fdafc` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5600575465) | `POSTED` | +| `5152707859` | 2026-09-09 10:00 | `da2ce7` | `DISMISSED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152707859) | `df6ccf2e` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5600575750) | `POSTED` | +| `5152875522` | 2026-09-09 10:15 | `da2ce7` | `COMMENTED` | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152875522) | `09d1de61` | [response](https://github.com/torrust/torrust-tracker/pull/2178#issuecomment-5600575999) | `POSTED` | ## Findings -| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | -| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------- | ------------- | -| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | -| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | -| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | -| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | -| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | -| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | -| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | -| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | -| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | -| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | -| F11 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | -| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | -| F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | `78f4cb58` | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966897692) | `RESOLVED` | `DONE` | -| F14 | `5152084856` | Inline review comment | `3966542753` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966542753) | F11 cited an invalid review-comment ID and dead URL. | `ACTION` | `67feff3a` | GitHub confirms the parent ID is `3966277146`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967148984) | `RESOLVED` | `DONE` | -| F15 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | REST per-review comments can omit inline threads, leaving the audit incomplete. | `ACTION` | `09d1de61` | GraphQL review threads are documented as authoritative. | N/A | `NOT_APPLICABLE` | `DONE` | -| F16 | `5152084856` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152084856) | Re-review confirmed F8-F9 and identified F12-F13 as outstanding record corrections. | `NO_ACTION` | N/A | F12-F13 were fixed in `3174a6cd` and `78f4cb58`. | N/A | `NOT_APPLICABLE` | `DONE` | -| F17 | `5152707859` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152707859) | Approval confirmed F12-F13; it noted F14 and audit-language accuracy. | `ACTION` | Pending | Pending | Pending | `NOT_APPLICABLE` | `IN_PROGRESS` | -| F18 | `5152875522` | Inline review comment | `3967199432` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967199432) | F14 processing-log entry postdates its commit and breaks chronology. | `ACTION` | Pending | Pending | Pending | `OPEN` | `IN_PROGRESS` | +| ID | Review ID | Source | Comment / thread ID | URL | Summary | Decision | Independent fix commit | Validation | Reply URL | Inline thread state | Status | +| --- | ------------ | --------------------- | ------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------- | ---------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------- | ------ | +| F1 | `5146523360` | Inline review comment | `3961826843` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826843) | Container invocation replaced `CMD` without naming the tracker executable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; docs checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744613) | `RESOLVED` | `DONE` | +| F2 | `5146523360` | Inline review comment | `3961826854` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826854) | Language heading left the infrastructure heading without its requirements. | `ACTION` | `957ed394` | Markdown checks; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965744928) | `RESOLVED` | `DONE` | +| F3 | `5146523360` | Inline review comment | `3961826862` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826862) | CLI executable tests used an undocumented fixed-port range. | `ACTION` | `957ed394` | Markdown checks; `cargo test --test cli-configuration`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745265) | `RESOLVED` | `DONE` | +| F4 | `5146523360` | Inline review comment | `3961826873` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3961826873) | Complete-TOML-only test also installed a path-source variable. | `ACTION` | `957ed394` | `cargo test -p torrust-tracker --lib`; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965745612) | `RESOLVED` | `DONE` | +| F5 | `5150709986` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5150709986) | Re-review confirmed the two Copilot-thread fixes in `8e9c0b68`; it restated F1-F4 as still open at that reviewed commit. | `NO_ACTION` | N/A | Later review `5151181594` independently confirmed F1-F4. | N/A | `NOT_APPLICABLE` | `DONE` | +| F6 | `5150709986` | Inline review comment | `3965426611` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965426611) | Pre-existing environment-source log terminology is mixed with the new explicit-source wording. | `NO_ACTION` | N/A | Verified unchanged from `develop`; out of feature scope. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3965895286) | `RESOLVED` | `DONE` | +| F7 | `5151181594` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5151181594) | Approval re-verified F1-F4 and their validations at `957ed394`. | `NO_ACTION` | N/A | Human approval records completed re-review. | N/A | `NOT_APPLICABLE` | `DONE` | +| F8 | `5151425410` | Inline review comment | `3966020055` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020055) | No-runtime reap loop is unbounded and treats `try_wait` errors as still running. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366377) | `RESOLVED` | `DONE` | +| F9 | `5151425410` | Inline review comment | `3966020059` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966020059) | No-runtime reap loop busy-spins and the regression name omits its reaping contract. | `ACTION` | `8b3927c1` | Executable suites (18 and 13 passed); Clippy; pre-commit | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966366818) | `RESOLVED` | `DONE` | +| F10 | `5151742675` | Inline review comment | `3966277134` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277134) | Audit omitted review `5151425410`. | `ACTION` | `aeb3c533` | Audit records review `5151425410`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966390617) | `RESOLVED` | `DONE` | +| F11 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | Audit omitted F8-F9, the only open review threads at the reviewed head. | `ACTION` | `aeb3c533` | Audit records F8-F9 with current thread state. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966391039) | `RESOLVED` | `DONE` | +| F12 | `5151742675` | Inline review comment | `3966277151` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277151) | First processing-log timestamp is later than the following entry and its own commit. | `ACTION` | `3174a6cd` | Processing log is chronological. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966550824) | `RESOLVED` | `DONE` | +| F13 | `5151742675` | Inline review comment | `3966277158` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277158) | Note claims Copilot threads are tracked, but no PR #2178 Copilot audit exists. | `ACTION` | `78f4cb58` | [Copilot audit](../copilot-pr-reviews/pr-2178-copilot-suggestions.md) records both threads. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966897692) | `RESOLVED` | `DONE` | +| F14 | `5152084856` | Inline review comment | `3966542753` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966542753) | F11 cited an invalid review-comment ID and dead URL. | `ACTION` | `67feff3a` | GitHub confirms the parent ID is `3966277146`. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967148984) | `RESOLVED` | `DONE` | +| F15 | `5151742675` | Inline review comment | `3966277146` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3966277146) | REST per-review comments can omit inline threads, leaving the audit incomplete. | `ACTION` | `09d1de61` | GraphQL review threads are documented as authoritative. | N/A | `NOT_APPLICABLE` | `DONE` | +| F16 | `5152084856` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152084856) | Re-review confirmed F8-F9 and identified F12-F13 as outstanding record corrections. | `NO_ACTION` | N/A | F12-F13 were fixed in `3174a6cd` and `78f4cb58`. | N/A | `NOT_APPLICABLE` | `DONE` | +| F17 | `5152707859` | Review body | N/A | [review](https://github.com/torrust/torrust-tracker/pull/2178#pullrequestreview-5152707859) | Approval confirmed F12-F13; it noted F14 and audit-language accuracy. | `ACTION` | `7f313b4f` | Audit completion is snapshot-scoped and review history is complete through this review. | N/A | `NOT_APPLICABLE` | `DONE` | +| F18 | `5152875522` | Inline review comment | `3967199432` | [comment](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967199432) | F14 processing-log entry postdates its commit and breaks chronology. | `ACTION` | `7f313b4f` | F14 is timestamped 10:09 UTC, matching its commit. | [reply](https://github.com/torrust/torrust-tracker/pull/2178#discussion_r3967456954) | `RESOLVED` | `DONE` | ## Processing Log @@ -63,6 +63,7 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 10:09 UTC - Completed F14 in `67feff3a`; its thread was replied to and resolved. Recorded F15 from review `5151742675`: the REST per-review comments endpoint omitted F8-F9, so the review-feedback skill now treats GraphQL review-thread data as authoritative. Pending validation and a separate workflow-only commit. - 2026-09-09 10:12 UTC - Completed F15 in `09d1de61`. All findings processed through review `5151742675` were complete. - 2026-09-09 10:15 UTC - Recorded reviews `5152084856`, `5152707859`, and `5152875522` from the current PR state. F16 is complete because F12-F13 were already fixed; F17-F18 are in progress to correct the audit-language and chronology gaps they identify. +- 2026-09-09 10:35 UTC - Completed F17-F18 in `7f313b4f`; the active chronology thread was replied to and resolved. Posted and recorded consolidated responses for reviews `5152084856`, `5152707859`, and `5152875522`. All findings processed through review `5152875522` are complete. ## Notes From 44f634a1c8d1c27fb6347d20887784f062381167 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 9 Sep 2026 11:52:17 +0100 Subject: [PATCH 44/44] docs(review): correct PR 2178 audit timestamp --- docs/pr-review-feedback/pr-2178-review-feedback.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pr-review-feedback/pr-2178-review-feedback.md b/docs/pr-review-feedback/pr-2178-review-feedback.md index d2a1d6b99..68ffeb8ed 100644 --- a/docs/pr-review-feedback/pr-2178-review-feedback.md +++ b/docs/pr-review-feedback/pr-2178-review-feedback.md @@ -60,7 +60,7 @@ Source: [pull-request reviews](https://github.com/torrust/torrust-tracker/pull/2 - 2026-09-09 09:00 UTC - Completed F12 in `3174a6cd`; its thread was replied to and resolved. Created the missing Copilot thread audit for F13; pending validation, separate commit, and F13 thread response. - 2026-09-09 09:10 UTC - Completed F13 in `78f4cb58`; its thread was replied to and resolved. All findings processed through review `5151742675` were complete. - 2026-09-09 10:03 UTC - Recorded F14 from review `5152084856`; corrected F11's review-comment ID and URL after verifying reply `3966391039` has `in_reply_to_id` `3966277146`. Pending validation, a separate correction commit, and F14 thread resolution. -- 2026-09-09 10:09 UTC - Completed F14 in `67feff3a`; its thread was replied to and resolved. Recorded F15 from review `5151742675`: the REST per-review comments endpoint omitted F8-F9, so the review-feedback skill now treats GraphQL review-thread data as authoritative. Pending validation and a separate workflow-only commit. +- 2026-09-09 10:10 UTC - Completed F14 in `67feff3a`; its thread was replied to and resolved. Recorded F15 from review `5151742675`: the REST per-review comments endpoint omitted F8-F9, so the review-feedback skill now treats GraphQL review-thread data as authoritative. Pending validation and a separate workflow-only commit. - 2026-09-09 10:12 UTC - Completed F15 in `09d1de61`. All findings processed through review `5151742675` were complete. - 2026-09-09 10:15 UTC - Recorded reviews `5152084856`, `5152707859`, and `5152875522` from the current PR state. F16 is complete because F12-F13 were already fixed; F17-F18 are in progress to correct the audit-language and chronology gaps they identify. - 2026-09-09 10:35 UTC - Completed F17-F18 in `7f313b4f`; the active chronology thread was replied to and resolved. Posted and recorded consolidated responses for reviews `5152084856`, `5152707859`, and `5152875522`. All findings processed through review `5152875522` are complete.