Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/dwarves-runa/tests/runs_on.rs

//! `runs_on` — WHERE a pipeline's steps run: `Container` (the default), `Vm`,
//! `Kubernetes`. Set at pipeline level, overridden per stage.
//!
//! Red-when-broken: every file written before `runs_on` existed must still load
//! (and mean Container), the setting must round-trip, a stage override must win,
//! and a value with no backend (`Kubernetes`) is refused BY NAME at load.

use dwarves_runa::{Pipeline, RunaError, RunsOn, ValidationError};

/// A pipeline as every `*.runa.ron` before `runs_on` was written.
const OLD: &str = r#"Pipeline(
  name: "old",
  stages: [
    Stage(name: "build", steps: [ CargoBuild(profile: Release) ]),
    Stage(name: "test", needs: ["build"], steps: [ Nextest(all_features: true) ]),
  ],
)"#;

const MIXED: &str = r#"Pipeline(
  name: "mixed",
  runs_on: Vm,
  stages: [
    Stage(name: "build", runs_on: Container, steps: [ CargoBuild(profile: Release) ]),
    Stage(name: "smoke", needs: ["build"], steps: [ VmBoot ]),
  ],
)"#;

#[test]
fn a_file_without_runs_on_still_loads_and_runs_in_a_container() {
    let p = Pipeline::from_ron_str(OLD).expect("an old file loads");
    assert_eq!(p.runs_on, RunsOn::Container, "the default is Container");
    assert_eq!(RunsOn::default(), RunsOn::Container);
    for s in &p.stages {
        assert_eq!(s.runs_on, None, "no stage override in an old file");
        assert_eq!(p.runs_on_for(s), RunsOn::Container, "stage `{}`", s.name);
    }
}

#[test]
fn a_stage_override_wins_over_the_pipeline_setting() {
    let p = Pipeline::from_ron_str(MIXED).expect("loads");
    assert_eq!(p.runs_on, RunsOn::Vm);
    assert_eq!(p.stages[0].runs_on, Some(RunsOn::Container));
    assert_eq!(p.runs_on_for(&p.stages[0]), RunsOn::Container, "the stage says Container");
    assert_eq!(p.runs_on_for(&p.stages[1]), RunsOn::Vm, "no override: the pipeline's Vm");
}

#[test]
fn runs_on_round_trips() {
    let p = Pipeline::from_ron_str(MIXED).expect("loads");
    let again = Pipeline::from_ron_str(&p.to_ron_string().expect("serialise")).expect("reloads");
    assert_eq!(again, p, "parse -> serialise -> parse is an identity with runs_on set");
}

#[test]
fn an_old_file_serialises_without_runs_on() {
    // The default is not written back: an old file re-saved stays the file it was.
    let p = Pipeline::from_ron_str(OLD).expect("loads");
    let out = p.to_ron_string().expect("serialise");
    assert!(!out.contains("runs_on"), "default runs_on leaked into the output:\n{out}");
    assert_eq!(Pipeline::from_ron_str(&out).expect("reloads"), p);
}

#[test]
fn kubernetes_is_refused_by_name_at_load_until_it_has_a_backend() {
    let at_pipeline = OLD.replace("name: \"old\",", "name: \"old\", runs_on: Kubernetes,");
    match Pipeline::from_ron_str(&at_pipeline) {
        Err(RunaError::Validation(e @ ValidationError::RunsOnNotWired { .. })) => {
            assert_eq!(e, ValidationError::RunsOnNotWired { stage: None, runs_on: RunsOn::Kubernetes });
            let msg = e.to_string();
            assert!(msg.contains("Kubernetes"), "names the value: {msg}");
        }
        other => panic!("pipeline-level Kubernetes must be refused at load, got {other:?}"),
    }

    let at_stage = OLD.replace("Stage(name: \"test\",", "Stage(name: \"test\", runs_on: Kubernetes,");
    match Pipeline::from_ron_str(&at_stage) {
        Err(RunaError::Validation(e)) => {
            assert_eq!(e, ValidationError::RunsOnNotWired { stage: Some("test".into()), runs_on: RunsOn::Kubernetes });
            let msg = e.to_string();
            assert!(msg.contains("Kubernetes") && msg.contains("`test`"), "names the value and the stage: {msg}");
        }
        other => panic!("stage-level Kubernetes must be refused at load, got {other:?}"),
    }
    assert!(!RunsOn::Kubernetes.has_backend());
    assert!(RunsOn::Container.has_backend() && RunsOn::Vm.has_backend());
}

#[test]
fn a_misspelled_runs_on_value_is_refused() {
    let bad = OLD.replace("name: \"old\",", "name: \"old\", runs_on: Docker,");
    assert!(matches!(Pipeline::from_ron_str(&bad), Err(RunaError::Parse(_))), "an unknown value never falls back to the default");
}