Skip to main content

execution_run_subsequence/
execution_run_subsequence.rs

1//! Example: run a subsequence directly, passing it a parameter.
2//!
3//! ```text
4//! cargo run --example execution_run_subsequence
5//! ```
6//!
7//! `MainSequence` is only a convention, an execution can start at any sequence
8//! in the file. Starting at a subsequence is how a host runs one part of a test
9//! program on its own: a diagnostic routine, a calibration step, a single
10//! fixture check.
11//!
12//! A subsequence usually takes parameters, and this shows the honest way to
13//! supply one. The engine's own argument-passing route needs a sequence-call
14//! step; running a subsequence *directly* has no caller, so its parameters keep
15//! their default values, which means the value must be written into the
16//! sequence's parameter defaults before the run starts.
17//!
18//! Runs both ways so the difference is visible: `MainSequence` first, then the
19//! subsequence on its own.
20
21use std::time::{Duration, Instant};
22
23use rs_teststand::{
24    AdapterKeyName, Engine, Error, PropValType, PropertyOptions, Sequence, SequenceFile, StepGroup,
25    UIMessageCode, pump_thread_messages,
26};
27
28/// The subsequence this example runs on its own.
29const SUBSEQUENCE: &str = "Diagnostics";
30/// The parameter it takes.
31const PARAMETER: &str = "FixtureId";
32/// How long to let a run take before giving up on it.
33const RUN_DEADLINE: Duration = Duration::from_secs(60);
34
35const fn none() -> i32 {
36    PropertyOptions::NONE.bits()
37}
38
39const fn insert_if_missing() -> i32 {
40    PropertyOptions::INSERT_IF_MISSING.bits()
41}
42
43/// Appends an Action step that records a result and reports what it saw.
44fn add_action(engine: &Engine, sequence: &Sequence, name: &str) -> Result<(), Error> {
45    // The None adapter is what actually means "no code module"; an empty key
46    // would let the step type pick, and a step on a real adapter fails at run
47    // time with "module has not yet been specified".
48    let step = engine.new_step(AdapterKeyName::NoneAdapter.as_str(), "Action")?;
49    step.set_name(name)?;
50    step.set_record_result(true)?;
51    sequence.insert_step(
52        &step,
53        sequence.get_num_steps(StepGroup::Main)?,
54        StepGroup::Main,
55    )
56}
57
58/// Builds a file with a `MainSequence` and a parameterised subsequence.
59fn build(engine: &Engine) -> Result<SequenceFile, Error> {
60    let sequence_file = engine.new_sequence_file()?;
61
62    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
63    for name in ["Initialize", "Run Test", "Shut Down"] {
64        add_action(engine, &main_sequence, name)?;
65    }
66
67    let diagnostics = engine.new_sequence()?;
68    diagnostics.set_name(SUBSEQUENCE)?;
69    // A parameter is an ordinary property in the sequence's Parameters scope.
70    diagnostics.parameters()?.new_sub_property(
71        PARAMETER,
72        PropValType::String,
73        false,
74        "",
75        insert_if_missing(),
76    )?;
77    for name in ["Check Power Rails", "Check Clock"] {
78        add_action(engine, &diagnostics, name)?;
79    }
80    sequence_file.insert_sequence(&diagnostics)?;
81
82    Ok(sequence_file)
83}
84
85/// Waits for a run to finish, pumping and draining as it goes.
86fn wait_for_end(engine: &Engine, deadline: Duration) -> Result<bool, Error> {
87    let started = Instant::now();
88    while started.elapsed() < deadline {
89        if pump_thread_messages() {
90            return Ok(false);
91        }
92        while !engine.is_ui_message_queue_empty()? {
93            let message = engine.get_ui_message()?;
94            let ended = matches!(
95                UIMessageCode::from_bits(message.event()?),
96                Ok(UIMessageCode::EndExecution)
97            );
98            message.acknowledge()?;
99            if ended {
100                return Ok(true);
101            }
102        }
103    }
104    Ok(false)
105}
106
107/// Starts a run at the named sequence and prints what it recorded.
108fn run(engine: &Engine, sequence_file: &SequenceFile, entry_point: &str) -> Result<(), Error> {
109    let execution = engine.new_execution(sequence_file, entry_point, None, false, 0)?;
110    println!("\nRunning {entry_point}...");
111
112    if !wait_for_end(engine, RUN_DEADLINE)? {
113        println!("  did not finish within {RUN_DEADLINE:?}; terminating");
114        execution.terminate()?;
115        return Ok(());
116    }
117    println!("  status: {}", execution.result_status()?);
118
119    let results = execution.result_object()?;
120    if !results.exists("ResultList", none())? {
121        println!("  no results recorded");
122        return Ok(());
123    }
124    let result_list = results.get_property_object("ResultList", none())?;
125    for index in 0..result_list.get_num_elements()? {
126        let entry = result_list.get_property_object_by_offset(index, none())?;
127        println!(
128            "  {}: {}",
129            entry
130                .get_val_string("TS.StepName", none())
131                .unwrap_or_else(|_| "<unnamed>".to_owned()),
132            entry
133                .get_val_string("Status", none())
134                .unwrap_or_else(|_| "<no status>".to_owned())
135        );
136    }
137    Ok(())
138}
139
140fn main() -> Result<(), Box<dyn std::error::Error>> {
141    let engine = Engine::new()?;
142    engine.set_ui_message_polling_enabled(true)?;
143
144    let sequence_file = build(&engine)?;
145    println!("File holds {} sequence(s).", sequence_file.num_sequences()?);
146
147    // The conventional entry point.
148    run(&engine, &sequence_file, "MainSequence")?;
149
150    // A subsequence run on its own has no caller, so nothing supplies its
151    // parameters, they keep whatever default the sequence carries. Setting
152    // that default is therefore how a direct run is given its input.
153    let diagnostics = sequence_file.get_sequence_by_name(SUBSEQUENCE)?;
154    diagnostics
155        .parameters()?
156        .set_val_string(PARAMETER, none(), "FIXTURE-07")?;
157    println!(
158        "\n{SUBSEQUENCE}.{PARAMETER} default is now {:?}",
159        diagnostics
160            .parameters()?
161            .get_val_string(PARAMETER, none())?
162    );
163
164    run(&engine, &sequence_file, SUBSEQUENCE)?;
165
166    engine.release_sequence_file_ex(sequence_file, none())?;
167    Ok(())
168}