#![allow(
clippy::too_many_arguments,
reason = "rstest macro expansion generates >4 params; #[expect] doesn't work in macros"
)]
use super::*;
use crate::localization;
use crate::output_prefs;
use crate::snapshot_test_support::{snapshot_settings, theme_prefs};
use anyhow::{Context, Result, ensure};
use insta::assert_snapshot;
use rstest::{fixture, rstest};
use test_support::fluent::normalize_fluent_isolates;
use test_support::{EnLocalizer, en_localizer};
fn test_prefs() -> crate::output_prefs::OutputPrefs {
output_prefs::resolve_with(None, |_| None)
}
fn stage6_message(reporter: &IndicatifReporter) -> Result<String> {
let state = reporter
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
Ok(state
.bars
.get(STAGE6_INDEX)
.context("stage 6 progress bar should exist")?
.message())
}
#[fixture]
fn force_text_reporter() -> IndicatifReporter {
IndicatifReporter::with_force_text_task_updates(test_prefs(), true)
}
#[fixture]
fn running_stage6_reporter() -> IndicatifReporter {
let reporter = IndicatifReporter::with_force_text_task_updates(test_prefs(), false);
{
let mut state = reporter
.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
state.is_hidden = false;
}
reporter.report_stage(
PipelineStage::NinjaSynthesisAndExecution.index(),
PIPELINE_STAGE_TOTAL,
"Executing Build",
);
reporter
}
#[rstest]
#[case(PipelineStage::ManifestIngestion, 1)]
#[case(PipelineStage::InitialYamlParsing, 2)]
#[case(PipelineStage::TemplateExpansion, 3)]
#[case(PipelineStage::FinalRendering, 4)]
#[case(PipelineStage::IrGenerationValidation, 5)]
#[case(PipelineStage::NinjaSynthesisAndExecution, 6)]
#[case(PipelineStage::GraphRendering, 6)]
fn stage_index_matches_pipeline_order(#[case] stage: PipelineStage, #[case] expected: u32) {
assert_eq!(stage.index().get(), expected);
}
#[test]
fn pipeline_stage_total_derived_from_all() {
let expected = u32::try_from(PipelineStage::ALL.len()).expect("stage array length fits u32");
assert_eq!(PIPELINE_STAGE_TOTAL.get(), expected);
}
#[test]
fn localization_key_round_trips_static_key() {
let key = LocalizationKey::new(keys::STATUS_STATE_PENDING);
assert_eq!(key.as_str(), keys::STATUS_STATE_PENDING);
}
#[test]
fn localization_key_from_static_str() {
let key: LocalizationKey = keys::STATUS_STATE_PENDING.into();
assert_eq!(key.as_str(), keys::STATUS_STATE_PENDING);
}
#[rstest]
#[case(1, 2, "cc -c src/main.c", "Task 1/2: cc -c src/main.c")]
#[case(2, 2, "", "Task 2/2")]
fn task_progress_update_formats_expected_text(
en_localizer: EnLocalizer,
#[case] current: u32,
#[case] total: u32,
#[case] description: &str,
#[case] expected: &str,
) -> Result<()> {
let _localizer = en_localizer;
let rendered = task_progress_update(current, total, description);
ensure!(
normalize_fluent_isolates(&rendered) == expected,
"task progress text should match the pinned en-US expectation"
);
Ok(())
}
#[rstest]
fn indicatif_reporter_ignores_task_updates_when_stage6_is_not_running(
en_localizer: EnLocalizer,
force_text_reporter: IndicatifReporter,
) -> Result<()> {
let _localizer = en_localizer;
force_text_reporter.report_task_progress(1, 2, "cc -c src/a.c");
let stage6_message = stage6_message(&force_text_reporter)?;
ensure!(
!normalize_fluent_isolates(&stage6_message).contains("Task 1/2"),
"stage 6 should not include task progress before the stage is running"
);
Ok(())
}
#[rstest]
fn indicatif_reporter_sets_stage6_bar_message_for_non_text_updates(
en_localizer: EnLocalizer,
running_stage6_reporter: IndicatifReporter,
) -> Result<()> {
let _localizer = en_localizer;
running_stage6_reporter.report_task_progress(1, 2, "cc -c src/a.c");
let stage6_message = stage6_message(&running_stage6_reporter)?;
let task = task_progress_update(1, 2, "cc -c src/a.c");
let state_label = localization::message(keys::STATUS_STATE_RUNNING).to_string();
let stage_line = stage_label(
PipelineStage::NinjaSynthesisAndExecution.index(),
PIPELINE_STAGE_TOTAL,
"Executing Build",
);
let expected = localization::message(keys::STATUS_STAGE_SUMMARY_WITH_TASK)
.with_arg("state", state_label)
.with_arg("label", stage_line)
.with_arg("task_progress", &task)
.to_string();
ensure!(
normalize_fluent_isolates(&stage6_message) == normalize_fluent_isolates(&expected),
"stage 6 message should match expected format"
);
Ok(())
}
#[rstest]
fn accessible_reporter_formats_stage_with_info_prefix(en_localizer: EnLocalizer) -> Result<()> {
let _localizer = en_localizer;
let prefs = test_prefs();
let reporter = AccessibleReporter::with_writer(prefs, Vec::new());
reporter.report_stage(
StageNumber::new_unchecked(1),
StageNumber::new_unchecked(6),
"Reading manifest file",
);
let output = reporter
.writer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let line = normalize_fluent_isolates(&String::from_utf8_lossy(&output));
let info_prefix = normalize_fluent_isolates(&prefs.info_prefix());
ensure!(
line.starts_with(&info_prefix),
"stage line should start with info prefix; line was: {line:?}, prefix was: {info_prefix:?}"
);
ensure!(
line.contains("Stage 1/6: Reading manifest file"),
"stage line should contain the stage label; line was: {line:?}"
);
Ok(())
}
#[rstest]
fn accessible_reporter_indents_task_progress(en_localizer: EnLocalizer) -> Result<()> {
let _localizer = en_localizer;
let prefs = test_prefs();
let reporter = AccessibleReporter::with_writer(prefs, Vec::new());
reporter.report_task_progress(1, 2, "cc -c src/main.c");
let output = reporter
.writer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let line = normalize_fluent_isolates(&String::from_utf8_lossy(&output));
let info_prefix = normalize_fluent_isolates(&prefs.info_prefix());
ensure!(
line.starts_with(prefs.task_indent()),
"task line should be indented by the resolved task token; line was: {line:?}"
);
ensure!(
!line.trim_start().starts_with(&info_prefix),
"task line should not include info prefix; line was: {line:?}, prefix was: {info_prefix:?}"
);
Ok(())
}
#[rstest]
fn completion_line_includes_success_prefix(en_localizer: EnLocalizer) -> Result<()> {
let _localizer = en_localizer;
let prefs = test_prefs();
let line = normalize_fluent_isolates(&format_completion_line(
prefs,
LocalizationKey::new(keys::STATUS_TOOL_GENERATE),
));
let success_prefix = normalize_fluent_isolates(&prefs.success_prefix());
ensure!(
line.starts_with(&success_prefix),
"completion line should start with success prefix; line was: {line:?}, prefix was: {success_prefix:?}"
);
Ok(())
}
#[rstest]
#[case::unicode(
crate::theme::ThemePreference::Unicode,
"accessible_unicode_stage_and_completion"
)]
#[case::ascii(
crate::theme::ThemePreference::Ascii,
"accessible_ascii_stage_and_completion"
)]
fn accessible_reporter_stage_and_completion_snapshot(
en_localizer: EnLocalizer,
#[case] theme: crate::theme::ThemePreference,
#[case] snapshot_name: &str,
) {
let _localizer = en_localizer;
let prefs = theme_prefs(theme);
let reporter = AccessibleReporter::with_writer(prefs, Vec::new());
for stage in PipelineStage::ALL {
reporter.report_stage(
stage.index(),
PIPELINE_STAGE_TOTAL,
&stage.description(None),
);
}
reporter.report_complete(LocalizationKey::new(keys::STATUS_TOOL_GENERATE));
let output = reporter
.writer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let rendered = normalize_fluent_isolates(&String::from_utf8_lossy(&output));
snapshot_settings("status").bind(|| {
assert_snapshot!(snapshot_name, rendered);
});
}
#[rstest]
#[case::unicode(
crate::theme::ThemePreference::Unicode,
"accessible_unicode_task_progress"
)]
#[case::ascii(crate::theme::ThemePreference::Ascii, "accessible_ascii_task_progress")]
fn accessible_reporter_task_progress_snapshot(
en_localizer: EnLocalizer,
#[case] theme: crate::theme::ThemePreference,
#[case] snapshot_name: &str,
) {
let _localizer = en_localizer;
let reporter = AccessibleReporter::with_writer(theme_prefs(theme), Vec::new());
reporter.report_task_progress(1, 2, "cc -c src/a.c");
reporter.report_task_progress(2, 2, "cc -c src/b.c");
let output = reporter
.writer
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let rendered = normalize_fluent_isolates(&String::from_utf8_lossy(&output));
snapshot_settings("status").bind(|| {
assert_snapshot!(snapshot_name, rendered);
});
}