1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2026 Noyalib. All rights reserved.
//! Behaviour of the optional features, asserted rather than assumed.
//!
//! Ten features had no integration test naming them. Three of those are
//! pure aliases (`minimal`, `noyavalidate`, `compare-saphyr`) with no
//! `cfg` site of their own — for those the only meaningful claim is that
//! the composition still resolves, which a build with the feature on
//! proves by existing.
//!
//! The rest select between two implementations of the same thing:
//!
//! | feature | swaps |
//! | :--- | :--- |
//! | `fast-int` | `itoa` for `write!` when serialising integers |
//! | `fast-float` | `ryu` for `{:?}` when checking float round-trips |
//! | `wasm-opt` | an alternative streaming buffer strategy |
//!
//! A faster implementation that produces *different output* is a bug,
//! not an optimisation. So these tests assert the observable result and
//! are written to pass identically whether the feature is on or off —
//! run under both configurations, they pin the equivalence. A test that
//! only ran in one configuration could not.
#![allow(clippy::unwrap_used, clippy::expect_used)]
use noyalib::Value;
/// `fast-int` swaps `itoa` in for `write!`. Same digits, both ways.
///
/// Boundaries included deliberately: `itoa` and `write!` are most
/// likely to disagree at the extremes of each width, and at zero, where
/// a hand-rolled digit loop is easiest to get wrong.
#[test]
fn integer_serialisation_is_identical_with_or_without_fast_int() {
for n in [
0_i64,
1,
-1,
9,
10,
-10,
99,
100,
i64::from(i32::MAX),
i64::from(i32::MIN),
i64::MAX,
i64::MIN,
] {
let yaml = noyalib::to_string(&n).expect("serialise");
assert_eq!(
yaml.trim(),
n.to_string(),
"integer {n} did not serialise to its own decimal form"
);
let back: i64 = noyalib::from_str(&yaml).expect("round trip");
assert_eq!(back, n, "integer {n} did not survive a round trip");
}
}
/// The same, inside a document rather than at the top level, so the
/// value goes through the mapping writer rather than the scalar
/// shortcut.
#[test]
fn integers_in_a_mapping_serialise_identically() {
let src = "a: 0\nb: -1\nc: 9223372036854775807\nd: -9223372036854775808\n";
let v: Value = noyalib::from_str(src).expect("parse");
let out = noyalib::to_string(&v).expect("serialise");
let again: Value = noyalib::from_str(&out).expect("reparse");
assert_eq!(
v, again,
"a mapping of boundary integers did not round trip"
);
for want in ["9223372036854775807", "-9223372036854775808"] {
assert!(out.contains(want), "{want} missing from:\n{out}");
}
}
/// `fast-float` swaps `ryu` in for `{:?}` when checking that a float
/// survives formatting. Both must accept exactly the values that round
/// trip and reject exactly those that do not.
#[test]
fn float_round_trips_are_identical_with_or_without_fast_float() {
for f in [
0.0_f64,
-0.0,
1.0,
-1.0,
0.1,
1e300,
1e-300,
f64::MAX,
f64::MIN,
f64::MIN_POSITIVE,
std::f64::consts::PI,
] {
let yaml = noyalib::to_string(&f).expect("serialise");
let back: f64 = noyalib::from_str(&yaml).expect("round trip");
assert!(
back == f || (back.is_nan() && f.is_nan()),
"float {f} round-tripped to {back} via {yaml:?}"
);
}
}
/// Non-finite floats are the case where the two formatters are most
/// likely to differ, and where YAML has its own spelling.
#[test]
fn non_finite_floats_behave_the_same_either_way() {
for (value, expected) in [(f64::INFINITY, ".inf"), (f64::NEG_INFINITY, "-.inf")] {
let yaml = noyalib::to_string(&value).expect("serialise");
assert!(
yaml.contains(expected),
"{value} serialised to {yaml:?}, expected {expected}"
);
}
let nan = noyalib::to_string(&f64::NAN).expect("serialise");
assert!(nan.contains(".nan"), "NaN serialised to {nan:?}");
}
/// `wasm-opt` selects a different streaming buffer strategy. The
/// documents it produces must not depend on which one is compiled in.
#[test]
fn streaming_output_is_identical_with_or_without_wasm_opt() {
let src = concat!(
"---\n",
"first: 1\n",
"nested:\n a: [1, 2, 3]\n b: {x: y}\n",
"text: |\n kept\n lines\n",
"---\n",
"second: 2\n",
);
let docs = noyalib::load_all_as::<Value>(src).expect("load all");
assert_eq!(docs.len(), 2, "expected two documents");
// Re-serialising each document and reparsing must be a fixed point,
// whichever buffer strategy is in play.
for d in &docs {
let out = noyalib::to_string(d).expect("serialise");
let again: Value = noyalib::from_str(&out).expect("reparse");
assert_eq!(d, &again, "document was not a fixed point:\n{out}");
}
}
/// `arbitrary` derives `Arbitrary` for [`Value`]. Only meaningful with
/// the feature on; without it the type simply does not implement the
/// trait and there is nothing to assert.
#[cfg(feature = "arbitrary")]
#[test]
fn value_implements_arbitrary_when_the_feature_is_on() {
use arbitrary::{Arbitrary as _, Unstructured};
let data = [0u8; 64];
let mut u = Unstructured::new(&data);
// The point is that this compiles and does not panic on trivial
// input; a fuzzer supplies the interesting cases.
let _ = Value::arbitrary(&mut u);
}
/// The alias features carry no `cfg` of their own — their whole content
/// is the set they pull in. Building with one enabled is the assertion;
/// this records which, so an alias that stops resolving is a failing
/// test rather than a silent no-op.
#[test]
fn alias_features_resolve_to_something() {
// `minimal = ["std"]`, and `noyavalidate` pulls in miette +
// validate-schema. Both are consumed elsewhere — `noyavalidate` by
// `noya-cli`, which is why it looks unreferenced from inside this
// repository and is not vestigial.
#[cfg(feature = "std")]
{
let v: Value = noyalib::from_str("k: v").expect("std parse");
assert!(v.get("k").is_some());
}
#[cfg(feature = "validate-schema")]
{
// Present purely to prove the feature composes; the schema
// surface has its own suites.
// The implication lives in Cargo.toml, so check it where a
// Cargo.toml edit would break it: at compile time.
const _: () = assert!(cfg!(feature = "schema"));
}
}