use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::atomic::{AtomicUsize, Ordering};
struct TempDir(PathBuf);
impl TempDir {
fn new(label: &str) -> Self {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
let mut path = std::env::temp_dir();
path.push(format!(
"ridl-reorder-{label}-{}-{}",
std::process::id(),
COUNTER.fetch_add(1, Ordering::SeqCst),
));
std::fs::create_dir_all(&path).expect("create the temp dir");
Self(path)
}
fn path(&self) -> &Path {
&self.0
}
fn write(&self, relative: &str, text: &str) -> PathBuf {
let path = self.0.join(relative);
std::fs::create_dir_all(path.parent().expect("a relative path has a parent"))
.expect("create parent directories");
std::fs::write(&path, text).expect("write the fixture file");
path
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn ridl(args: &[&std::ffi::OsStr]) -> (i32, String, String) {
let output = Command::new(env!("CARGO_BIN_EXE_ridl"))
.args(args)
.output()
.expect("the ridl binary must run");
let code = output.status.code().expect("the process exits with a code");
(
code,
String::from_utf8_lossy(&output.stdout).into_owned(),
String::from_utf8_lossy(&output.stderr).into_owned(),
)
}
const MANIFEST: &str = "[package]\nname = \"veh.cluster\"\nversion = \"1.0.0\"\n";
fn workspace(dir: &TempDir, relative: &str, source: &str) -> PathBuf {
dir.write(&format!("{relative}/ridl.toml"), MANIFEST);
dir.write(&format!("{relative}/report.ridl"), source);
dir.path().join(relative)
}
const FIELDS: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
door: DoorState
latch: DoorState
}
";
const SWAPPED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
latch: DoorState
door: DoorState
}
";
const CHANGED_IN_PLACE: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
door: DoorState
latch: Count
}
";
#[test]
fn a_swapped_struct_field_reports_member_reordered() {
let dir = TempDir::new("reorder");
let old = workspace(&dir, "old", FIELDS);
let new = workspace(&dir, "new", SWAPPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
assert!(
out.contains("member_reordered"),
"the reorder has its own category:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"the fallback is not reported in its place:\n{out}",
);
assert!(
out.contains("door") && out.contains("latch"),
"both moved members are named:\n{out}",
);
}
#[test]
fn a_moved_field_reports_its_old_and_new_ordinal() {
let dir = TempDir::new("ordinals");
let old = workspace(&dir, "old", FIELDS);
let new = workspace(&dir, "new", SWAPPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Report/door: ordinal 1 -> ordinal 2\n"),
"door left ordinal 1 for ordinal 2:\n{out}",
);
assert!(
out.contains("member_reordered veh.cluster/Report/latch: ordinal 2 -> ordinal 1\n"),
"latch left ordinal 2 for ordinal 1:\n{out}",
);
}
#[test]
fn an_in_place_change_still_reports_constraint_changed() {
let dir = TempDir::new("in-place");
let old = workspace(&dir, "old", FIELDS);
let new = workspace(&dir, "new", CHANGED_IN_PLACE);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "narrowing a member in place is breaking:\n{out}");
assert!(
out.contains("constraint_changed"),
"an in-place change keeps the category it always had:\n{out}",
);
assert!(
!out.contains("member_reordered"),
"nothing moved, so nothing reorders:\n{out}",
);
}
const THREE_FIELDS: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
door: DoorState
latch: DoorState
window: DoorState
}
";
const OUTER_SWAPPED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
window: DoorState
latch: DoorState
door: DoorState
}
";
const SWAPPED_AND_RETYPED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
latch: DoorState
door: DoorState
window: Count
}
";
const GEARS: &str = "package veh.cluster
enum GearPosition {
PARK = 0
DRIVE = 1
REVERSE = 2
}
";
const GEARS_REORDERED: &str = "package veh.cluster
enum GearPosition {
DRIVE = 1
PARK = 0
REVERSE = 2
}
";
const GEARS_REORDERED_AND_REVALUED: &str = "package veh.cluster
enum GearPosition {
DRIVE = 1
PARK = 0
REVERSE = 5
}
";
#[test]
fn only_the_members_that_moved_are_reported() {
let dir = TempDir::new("outer-swap");
let old = workspace(&dir, "old", THREE_FIELDS);
let new = workspace(&dir, "new", OUTER_SWAPPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Report/door")
&& out.contains("member_reordered veh.cluster/Report/window"),
"both moved members are reported:\n{out}",
);
assert!(
!out.contains("member_reordered veh.cluster/Report/latch"),
"a member that kept its position is not reported:\n{out}",
);
}
#[test]
fn a_reorder_with_an_in_place_change_reports_both() {
let dir = TempDir::new("reorder-and-retype");
let old = workspace(&dir, "old", THREE_FIELDS);
let new = workspace(&dir, "new", SWAPPED_AND_RETYPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
let door = out.find("member_reordered veh.cluster/Report/door");
let latch = out.find("member_reordered veh.cluster/Report/latch");
let container = out.find("constraint_changed veh.cluster/Report\n");
assert!(
door.is_some() && latch.is_some(),
"both moved members are reported:\n{out}",
);
assert!(
container.is_some(),
"the in-place retype is reported on the container as well:\n{out}",
);
assert!(
container > door && container > latch,
"the container line follows the member lines:\n{out}",
);
}
#[test]
fn an_enum_reorder_with_a_changed_value_reports_constraint_changed() {
let dir = TempDir::new("enum-reorder-revalue");
let old = workspace(&dir, "old", GEARS);
let new = workspace(&dir, "new", GEARS_REORDERED_AND_REVALUED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a changed enum value is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/GearPosition/PARK: position 1 -> position 2\n")
&& out.contains(
"member_reordered veh.cluster/GearPosition/DRIVE: position 2 -> position 1\n"
),
"both moved values are reported with their positions:\n{out}",
);
assert!(
!out.contains("member_reordered veh.cluster/GearPosition/REVERSE"),
"a value that kept its position is not reported:\n{out}",
);
assert!(
out.contains("constraint_changed veh.cluster/GearPosition\n"),
"the changed value is reported on the container:\n{out}",
);
}
#[test]
fn an_enum_reorder_with_unchanged_values_reports_only_member_reordered() {
let dir = TempDir::new("enum-reorder");
let old = workspace(&dir, "old", GEARS);
let new = workspace(&dir, "new", GEARS_REORDERED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is reported breaking:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/GearPosition/PARK")
&& out.contains("member_reordered veh.cluster/GearPosition/DRIVE"),
"both moved values are reported:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"no value changed, so nothing is reported on the container:\n{out}",
);
}
const TOMBSTONE_FIRST: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
reserved gone
door: DoorState
latch: DoorState
}
";
const TOMBSTONE_MOVED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
door: DoorState
reserved gone
latch: DoorState
}
";
const GEARS_WITH_RESERVED: &str = "package veh.cluster
enum GearPosition {
PARK = 0
DRIVE = 1
reserved 3
reserved 4
}
";
const GEARS_RESERVED_REORDERED: &str = "package veh.cluster
enum GearPosition {
PARK = 0
DRIVE = 1
reserved 4
reserved 3
}
";
#[test]
fn a_moved_struct_tombstone_is_not_identical() {
let dir = TempDir::new("tombstone-moved");
let old = workspace(&dir, "old", TOMBSTONE_FIRST);
let new = workspace(&dir, "new", TOMBSTONE_MOVED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a shifted field ordinal is a wire break:\n{out}");
assert!(
stdout.starts_with("breaking"),
"the report verdict is breaking, not identical:\n{out}",
);
}
#[test]
fn a_reordered_enum_reserved_list_is_not_identical() {
let dir = TempDir::new("enum-reserved-reordered");
let old = workspace(&dir, "old", GEARS_WITH_RESERVED);
let new = workspace(&dir, "new", GEARS_RESERVED_REORDERED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(
code, 1,
"a changed reserved list is reported breaking:\n{out}"
);
assert!(
stdout.starts_with("breaking"),
"the report verdict is breaking, not identical:\n{out}",
);
assert!(
out.contains("constraint_changed veh.cluster/GearPosition\n"),
"the change is reported on the container:\n{out}",
);
assert!(!out.contains("member_reordered"), "no value moved:\n{out}",);
}
const TOMBSTONE_LAST: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
door: DoorState
latch: DoorState
reserved gone
}
";
const TOMBSTONE_LAST_AND_SWAPPED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
latch: DoorState
door: DoorState
reserved gone
}
";
const TOMBSTONE_BETWEEN: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
door: DoorState
latch: DoorState
reserved gone
window: DoorState
}
";
const SWAPPED_AROUND_TOMBSTONE: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
struct Report {
latch: DoorState
door: DoorState
window: DoorState
reserved gone
}
";
#[test]
fn a_moved_struct_tombstone_reports_the_field_it_shifted() {
let dir = TempDir::new("tombstone-shift");
let old = workspace(&dir, "old", TOMBSTONE_FIRST);
let new = workspace(&dir, "new", TOMBSTONE_MOVED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a shifted field ordinal is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Report/door: ordinal 2 -> ordinal 1\n"),
"door slid into the slot the tombstone left:\n{out}",
);
assert!(
!out.contains("member_reordered veh.cluster/Report/latch"),
"latch kept ordinal 3:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"nothing changed in place, so nothing is reported on the container:\n{out}",
);
}
#[test]
fn a_field_shifted_by_a_moved_tombstone_is_reported_by_ordinal() {
let dir = TempDir::new("swap-around-tombstone");
let old = workspace(&dir, "old", TOMBSTONE_BETWEEN);
let new = workspace(&dir, "new", SWAPPED_AROUND_TOMBSTONE);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Report/door: ordinal 1 -> ordinal 2\n")
&& out.contains("member_reordered veh.cluster/Report/latch: ordinal 2 -> ordinal 1\n"),
"the swapped fields are reported with their ordinals:\n{out}",
);
assert!(
out.contains("member_reordered veh.cluster/Report/window: ordinal 4 -> ordinal 3\n"),
"window kept its place among the live names but not its ordinal:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"nothing changed in place, so nothing is reported on the container:\n{out}",
);
}
#[test]
fn a_tombstone_moved_to_the_end_shifts_every_field_after_it() {
let dir = TempDir::new("tombstone-last");
let old = workspace(&dir, "old", TOMBSTONE_FIRST);
let new = workspace(&dir, "new", TOMBSTONE_LAST);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a shifted field ordinal is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Report/door: ordinal 2 -> ordinal 1\n")
&& out.contains("member_reordered veh.cluster/Report/latch: ordinal 3 -> ordinal 2\n"),
"both fields slid down one ordinal:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"nothing changed in place, so nothing is reported on the container:\n{out}",
);
}
#[test]
fn a_tombstone_move_with_a_swap_reports_only_the_ordinals_that_changed() {
let dir = TempDir::new("tombstone-last-and-swap");
let old = workspace(&dir, "old", TOMBSTONE_FIRST);
let new = workspace(&dir, "new", TOMBSTONE_LAST_AND_SWAPPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a shifted field ordinal is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Report/latch: ordinal 3 -> ordinal 1\n"),
"latch took the slot the tombstone left:\n{out}",
);
assert!(
!out.contains("member_reordered veh.cluster/Report/door"),
"door kept ordinal 2:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"nothing changed in place, so nothing is reported on the container:\n{out}",
);
}
const GEARS_AND_RESERVED_REORDERED: &str = "package veh.cluster
enum GearPosition {
DRIVE = 1
PARK = 0
reserved 4
reserved 3
}
";
#[test]
fn an_enum_reorder_with_its_reserved_list_reordered_reports_only_the_values() {
let dir = TempDir::new("enum-and-reserved-reordered");
let old = workspace(&dir, "old", GEARS_WITH_RESERVED);
let new = workspace(&dir, "new", GEARS_AND_RESERVED_REORDERED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is reported breaking:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/GearPosition/PARK: position 1 -> position 2\n")
&& out.contains(
"member_reordered veh.cluster/GearPosition/DRIVE: position 2 -> position 1\n"
),
"both moved values are reported:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"the order of the reserved list is not content:\n{out}",
);
}
const ARMS: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
type Level: integer [0..100]
union Reading {
door: DoorState
count: Count
}
";
const ARMS_SWAPPED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
type Level: integer [0..100]
union Reading {
count: Count
door: DoorState
}
";
const ARMS_SWAPPED_AND_RETYPED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
type Level: integer [0..100]
union Reading {
count: Count
door: Level
}
";
const ARMS_TOMBSTONE_FIRST: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
type Level: integer [0..100]
union Reading {
reserved oldArm
door: DoorState
count: Count
}
";
const ARMS_TOMBSTONE_LAST: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
type Level: integer [0..100]
union Reading {
door: DoorState
count: Count
reserved oldArm
}
";
const ARMS_TWO_TOMBSTONES: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
type Level: integer [0..100]
union Reading {
door: DoorState
reserved legacyA
reserved legacyB
count: Count
}
";
const ARMS_TWO_TOMBSTONES_SWAPPED: &str = "package veh.cluster
type DoorState: integer [0..1]
type Count: integer [0..10]
type Level: integer [0..100]
union Reading {
count: Count
reserved legacyB
reserved legacyA
door: DoorState
}
";
#[test]
fn a_swapped_union_arm_reports_member_reordered() {
let dir = TempDir::new("union-swap");
let old = workspace(&dir, "old", ARMS);
let new = workspace(&dir, "new", ARMS_SWAPPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Reading/door: ordinal 1 -> ordinal 2\n")
&& out.contains("member_reordered veh.cluster/Reading/count: ordinal 2 -> ordinal 1\n"),
"both moved arms are reported with their ordinals:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"nothing changed in place, so nothing is reported on the container:\n{out}",
);
}
#[test]
fn a_union_reorder_with_a_retyped_arm_reports_both() {
let dir = TempDir::new("union-swap-and-retype");
let old = workspace(&dir, "old", ARMS);
let new = workspace(&dir, "new", ARMS_SWAPPED_AND_RETYPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Reading/door")
&& out.contains("member_reordered veh.cluster/Reading/count"),
"both moved arms are reported:\n{out}",
);
assert!(
out.contains("constraint_changed veh.cluster/Reading\n"),
"the retype is reported on the container as well:\n{out}",
);
}
#[test]
fn a_union_tombstone_moved_to_the_end_shifts_every_arm_after_it() {
let dir = TempDir::new("union-tombstone-last");
let old = workspace(&dir, "old", ARMS_TOMBSTONE_FIRST);
let new = workspace(&dir, "new", ARMS_TOMBSTONE_LAST);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a shifted arm ordinal is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Reading/door: ordinal 2 -> ordinal 1\n")
&& out.contains("member_reordered veh.cluster/Reading/count: ordinal 3 -> ordinal 2\n"),
"both arms slid down one ordinal:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"nothing changed in place, so nothing is reported on the container:\n{out}",
);
}
#[test]
fn a_union_swap_with_its_tombstones_swapped_reports_only_the_arms() {
let dir = TempDir::new("union-two-tombstones");
let old = workspace(&dir, "old", ARMS_TWO_TOMBSTONES);
let new = workspace(&dir, "new", ARMS_TWO_TOMBSTONES_SWAPPED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/Reading/door: ordinal 1 -> ordinal 4\n")
&& out.contains("member_reordered veh.cluster/Reading/count: ordinal 4 -> ordinal 1\n"),
"both moved arms are reported with their ordinals:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"the order of the tombstone list is not content:\n{out}",
);
}
const FLAGS: &str = "package veh.cluster
enumset WarningFlags {
LOW_FUEL = 0
CHECK_ENGINE = 1
DOOR_OPEN = 2
}
";
const FLAGS_REORDERED: &str = "package veh.cluster
enumset WarningFlags {
CHECK_ENGINE = 1
LOW_FUEL = 0
DOOR_OPEN = 2
}
";
const FLAGS_REORDERED_AND_REBITTED: &str = "package veh.cluster
enumset WarningFlags {
CHECK_ENGINE = 1
LOW_FUEL = 0
DOOR_OPEN = 5
}
";
#[test]
fn an_enum_set_reorder_with_unchanged_bits_reports_only_member_reordered() {
let dir = TempDir::new("enumset-reorder");
let old = workspace(&dir, "old", FLAGS);
let new = workspace(&dir, "new", FLAGS_REORDERED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a reorder is reported breaking:\n{out}");
assert!(
out.contains(
"member_reordered veh.cluster/WarningFlags/LOW_FUEL: position 1 -> position 2\n"
) && out.contains(
"member_reordered veh.cluster/WarningFlags/CHECK_ENGINE: position 2 -> position 1\n"
),
"both moved bits are reported with their positions:\n{out}",
);
assert!(
!out.contains("constraint_changed"),
"no bit changed, so nothing is reported on the container:\n{out}",
);
}
#[test]
fn an_enum_set_reorder_with_a_changed_bit_reports_constraint_changed() {
let dir = TempDir::new("enumset-reorder-rebit");
let old = workspace(&dir, "old", FLAGS);
let new = workspace(&dir, "new", FLAGS_REORDERED_AND_REBITTED);
let (code, stdout, stderr) = ridl(&["diff".as_ref(), old.as_os_str(), new.as_os_str()]);
let out = format!("{stdout}{stderr}");
assert_eq!(code, 1, "a changed bit is a wire break:\n{out}");
assert!(
out.contains("member_reordered veh.cluster/WarningFlags/LOW_FUEL")
&& out.contains("member_reordered veh.cluster/WarningFlags/CHECK_ENGINE"),
"both moved bits are reported:\n{out}",
);
assert!(
out.contains("constraint_changed veh.cluster/WarningFlags\n"),
"the changed bit is reported on the container:\n{out}",
);
}