@layerzerolabs/framework-solana-anchor-trait 0.2.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +18 -0
- package/examples/shapes/Anchor.toml +19 -0
- package/examples/shapes/macros/Cargo.toml +17 -0
- package/examples/shapes/macros/src/instructions/compute_area.rs +50 -0
- package/examples/shapes/macros/src/instructions/compute_perimeter.rs +50 -0
- package/examples/shapes/macros/src/instructions/get_dimension_count.rs +44 -0
- package/examples/shapes/macros/src/instructions/get_name.rs +18 -0
- package/examples/shapes/macros/src/instructions/initialize.rs +18 -0
- package/examples/shapes/macros/src/instructions/mod.rs +26 -0
- package/examples/shapes/macros/src/lib.rs +50 -0
- package/examples/shapes/macros/src/shapes.rs +56 -0
- package/examples/shapes/programs/ellipse/Cargo.toml +30 -0
- package/examples/shapes/programs/ellipse/src/lib.rs +88 -0
- package/examples/shapes/programs/ellipse/src/state.rs +9 -0
- package/examples/shapes/programs/triangle/Cargo.toml +30 -0
- package/examples/shapes/programs/triangle/src/lib.rs +83 -0
- package/examples/shapes/programs/triangle/src/state.rs +9 -0
- package/package.json +36 -0
- package/rust-toolchain.toml +3 -0
- package/rustfmt.toml +23 -0
- package/src/assembly.rs +63 -0
- package/src/generation.rs +226 -0
- package/src/lib.rs +126 -0
- package/src/overrides.rs +289 -0
- package/tests/assembly.rs +339 -0
- package/tests/common/mod.rs +206 -0
- package/tests/declare_instructions.rs +206 -0
- package/tests/generation.rs +358 -0
- package/tests/override.rs +414 -0
- package/tests/snapshots/snapshots__tests__snapshot_all_defaults.snap +35 -0
- package/tests/snapshots/snapshots__tests__snapshot_full_override.snap +47 -0
- package/tests/snapshots/snapshots__tests__snapshot_handler_override.snap +42 -0
- package/tests/snapshots/snapshots__tests__snapshot_mixed_overrides.snap +57 -0
- package/tests/snapshots.rs +139 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
//! Shared test fixtures and helpers for anchor-trait integration tests.
|
|
2
|
+
|
|
3
|
+
#![allow(dead_code)]
|
|
4
|
+
|
|
5
|
+
use anchor_trait::{
|
|
6
|
+
generate_instructions, DefaultImpl, GeneratedInstructions, InstructionOverrides,
|
|
7
|
+
InstructionSet, InstructionSpec, OverrideConfig,
|
|
8
|
+
};
|
|
9
|
+
use proc_macro2::TokenStream;
|
|
10
|
+
use quote::{format_ident, quote, ToTokens};
|
|
11
|
+
use syn::{FnArg, GenericArgument, ItemFn, ItemStruct, Pat, PatIdent, PathArguments, Type};
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Test instruction set
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
/// Instruction set shared across generation and assembly tests.
|
|
18
|
+
///
|
|
19
|
+
/// - SetPeer: mutable, has params, has default impl (provided)
|
|
20
|
+
/// - GetPeer: view, has params, has default impl (provided)
|
|
21
|
+
/// - LzReceive: mutable, has params, NO default impl (required)
|
|
22
|
+
/// - Token: view, NO params, has default impl (provided)
|
|
23
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
24
|
+
pub enum TestIx {
|
|
25
|
+
SetPeer,
|
|
26
|
+
GetPeer,
|
|
27
|
+
LzReceive,
|
|
28
|
+
Token,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Dummy codegen context.
|
|
32
|
+
pub struct TestCtx;
|
|
33
|
+
|
|
34
|
+
/// Generates a placeholder default impl (struct only) named after the instruction's
|
|
35
|
+
/// default accounts type, mirroring real codegen which emits instruction-specific structs.
|
|
36
|
+
pub fn gen_default_impl(_ctx: &TestCtx, spec: &InstructionSpec<TestCtx>) -> TokenStream {
|
|
37
|
+
let ident = &spec.default_impl.as_ref().unwrap().accounts_ident;
|
|
38
|
+
quote! { struct #ident; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl InstructionSet for TestIx {
|
|
42
|
+
type Ctx = TestCtx;
|
|
43
|
+
|
|
44
|
+
fn all() -> &'static [Self] {
|
|
45
|
+
&[Self::SetPeer, Self::GetPeer, Self::LzReceive, Self::Token]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fn from_ident(ident: &syn::Ident) -> Option<Self> {
|
|
49
|
+
match ident.to_string().as_str() {
|
|
50
|
+
"set_peer" => Some(Self::SetPeer),
|
|
51
|
+
"get_peer" => Some(Self::GetPeer),
|
|
52
|
+
"lz_receive" => Some(Self::LzReceive),
|
|
53
|
+
"token" => Some(Self::Token),
|
|
54
|
+
_ => None,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn canonical_name(&self) -> &'static str {
|
|
59
|
+
match self {
|
|
60
|
+
Self::SetPeer => "set_peer",
|
|
61
|
+
Self::GetPeer => "get_peer",
|
|
62
|
+
Self::LzReceive => "lz_receive",
|
|
63
|
+
Self::Token => "token",
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
fn spec(self, _ctx: &Self::Ctx) -> InstructionSpec<TestCtx> {
|
|
68
|
+
match self {
|
|
69
|
+
Self::SetPeer => InstructionSpec {
|
|
70
|
+
params_type: Some(quote!(SetPeerParams)),
|
|
71
|
+
return_type: quote!(()),
|
|
72
|
+
is_view: false,
|
|
73
|
+
default_impl: Some(DefaultImpl {
|
|
74
|
+
accounts_ident: format_ident!("StdSetPeer"),
|
|
75
|
+
handler_ident: format_ident!("apply"),
|
|
76
|
+
generator: gen_default_impl,
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
Self::GetPeer => InstructionSpec {
|
|
80
|
+
params_type: Some(quote!(GetPeerParams)),
|
|
81
|
+
return_type: quote!([u8; 32]),
|
|
82
|
+
is_view: true,
|
|
83
|
+
default_impl: Some(DefaultImpl {
|
|
84
|
+
accounts_ident: format_ident!("StdGetPeer"),
|
|
85
|
+
handler_ident: format_ident!("apply"),
|
|
86
|
+
generator: gen_default_impl,
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
Self::LzReceive => InstructionSpec {
|
|
90
|
+
params_type: Some(quote!(LzReceiveParams)),
|
|
91
|
+
return_type: quote!(()),
|
|
92
|
+
is_view: false,
|
|
93
|
+
default_impl: None, // required — no default
|
|
94
|
+
},
|
|
95
|
+
Self::Token => InstructionSpec {
|
|
96
|
+
params_type: None, // no params
|
|
97
|
+
return_type: quote!(Pubkey),
|
|
98
|
+
is_view: true,
|
|
99
|
+
default_impl: Some(DefaultImpl {
|
|
100
|
+
accounts_ident: format_ident!("StdToken"),
|
|
101
|
+
handler_ident: format_ident!("apply"),
|
|
102
|
+
generator: gen_default_impl,
|
|
103
|
+
}),
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
pub const TEST_CONFIG: OverrideConfig =
|
|
110
|
+
OverrideConfig { attr_name: "test_instruction", domain: "Test" };
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Pipeline helpers
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/// Collect overrides from a module and run generation.
|
|
117
|
+
pub fn generate_ok(module: &syn::ItemMod) -> GeneratedInstructions {
|
|
118
|
+
let overrides = InstructionOverrides::<TestIx>::collect(module, TEST_CONFIG)
|
|
119
|
+
.expect("collect() should succeed");
|
|
120
|
+
generate_instructions(&TestCtx, &overrides).expect("generate should succeed")
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Collect overrides and expect generation to fail.
|
|
124
|
+
pub fn generate_err(module: &syn::ItemMod) -> String {
|
|
125
|
+
let overrides = InstructionOverrides::<TestIx>::collect(module, TEST_CONFIG)
|
|
126
|
+
.expect("collect() should succeed");
|
|
127
|
+
match generate_instructions(&TestCtx, &overrides) {
|
|
128
|
+
Err(e) => e.to_string(),
|
|
129
|
+
Ok(_) => panic!("generate should have failed but succeeded"),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Collect overrides from a module (for assembly tests that need the overrides directly).
|
|
134
|
+
pub fn collect_overrides(module: &syn::ItemMod) -> InstructionOverrides<TestIx> {
|
|
135
|
+
InstructionOverrides::<TestIx>::collect(module, TEST_CONFIG).expect("collect() should succeed")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// AST assertion helpers
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
/// Parse a token stream and extract all function items.
|
|
143
|
+
pub fn parse_fns(ts: &TokenStream) -> Vec<ItemFn> {
|
|
144
|
+
let file: syn::File = syn::parse2(quote! { #ts }).expect("should parse as File");
|
|
145
|
+
file.items
|
|
146
|
+
.into_iter()
|
|
147
|
+
.filter_map(|item| match item {
|
|
148
|
+
syn::Item::Fn(f) => Some(f),
|
|
149
|
+
_ => None,
|
|
150
|
+
})
|
|
151
|
+
.collect()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Parse a token stream and extract all struct items.
|
|
155
|
+
pub fn parse_structs(ts: &TokenStream) -> Vec<ItemStruct> {
|
|
156
|
+
let file: syn::File = syn::parse2(quote! { #ts }).expect("should parse as File");
|
|
157
|
+
file.items
|
|
158
|
+
.into_iter()
|
|
159
|
+
.filter_map(|item| match item {
|
|
160
|
+
syn::Item::Struct(s) => Some(s),
|
|
161
|
+
_ => None,
|
|
162
|
+
})
|
|
163
|
+
.collect()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Find a function by name in a list of parsed functions.
|
|
167
|
+
pub fn find_fn<'a>(fns: &'a [ItemFn], name: &str) -> Option<&'a ItemFn> {
|
|
168
|
+
fns.iter().find(|f| f.sig.ident == name)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Find a struct by name in a list of parsed structs.
|
|
172
|
+
pub fn find_struct<'a>(structs: &'a [ItemStruct], name: &str) -> Option<&'a ItemStruct> {
|
|
173
|
+
structs.iter().find(|s| s.ident == name)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// Extract (is_mut, context_inner_type) from the first parameter of a function.
|
|
177
|
+
/// Expects the first param to be `ctx: Context<T>` or `mut ctx: Context<T>`.
|
|
178
|
+
pub fn first_param_context_type(f: &ItemFn) -> (bool, String) {
|
|
179
|
+
let arg = f.sig.inputs.first().expect("function should have at least one param");
|
|
180
|
+
|
|
181
|
+
let FnArg::Typed(pat_type) = arg else {
|
|
182
|
+
panic!("expected typed param, got self");
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Check mutability from the pattern (mut ctx vs ctx)
|
|
186
|
+
let is_mut = match pat_type.pat.as_ref() {
|
|
187
|
+
Pat::Ident(PatIdent { mutability, .. }) => mutability.is_some(),
|
|
188
|
+
other => panic!("expected ident pattern, got: {}", other.to_token_stream()),
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Extract the inner type T from Context<T> (or Context<'a, 'b, ..., T>)
|
|
192
|
+
let Type::Path(type_path) = pat_type.ty.as_ref() else {
|
|
193
|
+
panic!("expected path type for ctx param");
|
|
194
|
+
};
|
|
195
|
+
let segment = type_path.path.segments.last().expect("empty path");
|
|
196
|
+
assert_eq!(segment.ident, "Context", "first param should be Context<T>");
|
|
197
|
+
let PathArguments::AngleBracketed(args) = &segment.arguments else {
|
|
198
|
+
panic!("Context should have angle bracket args");
|
|
199
|
+
};
|
|
200
|
+
let inner = args.args.last().expect("Context should have at least one type arg");
|
|
201
|
+
let GenericArgument::Type(inner_type) = inner else {
|
|
202
|
+
panic!("last generic arg should be a type");
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
(is_mut, inner_type.to_token_stream().to_string())
|
|
206
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
//! Tests for the `declare_instructions!` macro.
|
|
2
|
+
|
|
3
|
+
use anchor_trait::{InstructionSet, InstructionSpec};
|
|
4
|
+
use quote::quote;
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Test fixtures — fake modules that declare_instructions! dispatches to
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
struct TestCtx;
|
|
11
|
+
|
|
12
|
+
/// Module with ctx-passing spec (the `(ctx)` marker form).
|
|
13
|
+
mod alpha {
|
|
14
|
+
use super::*;
|
|
15
|
+
|
|
16
|
+
pub fn spec(_ctx: &TestCtx) -> InstructionSpec<TestCtx> {
|
|
17
|
+
InstructionSpec {
|
|
18
|
+
params_type: Some(quote!(AlphaParams)),
|
|
19
|
+
return_type: quote!(()),
|
|
20
|
+
is_view: false,
|
|
21
|
+
default_impl: None,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Module with no-ctx spec (no marker form).
|
|
27
|
+
mod beta {
|
|
28
|
+
use super::*;
|
|
29
|
+
|
|
30
|
+
pub fn spec() -> InstructionSpec<TestCtx> {
|
|
31
|
+
InstructionSpec {
|
|
32
|
+
params_type: None,
|
|
33
|
+
return_type: quote!(u64),
|
|
34
|
+
is_view: true,
|
|
35
|
+
default_impl: None,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Third module to test with more than two variants.
|
|
41
|
+
mod gamma {
|
|
42
|
+
use super::*;
|
|
43
|
+
|
|
44
|
+
pub fn spec(ctx: &TestCtx) -> InstructionSpec<TestCtx> {
|
|
45
|
+
let _ = ctx;
|
|
46
|
+
InstructionSpec {
|
|
47
|
+
params_type: Some(quote!(GammaParams)),
|
|
48
|
+
return_type: quote!(bool),
|
|
49
|
+
is_view: false,
|
|
50
|
+
default_impl: None,
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Primary invocation — mixed ctx/no-ctx, trailing comma, multiple variants
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
anchor_trait::declare_instructions! {
|
|
60
|
+
name = TestIx;
|
|
61
|
+
context = TestCtx;
|
|
62
|
+
|
|
63
|
+
Alpha => alpha(ctx),
|
|
64
|
+
Beta => beta,
|
|
65
|
+
Gamma => gamma(ctx),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Compile-time grammar guards
|
|
70
|
+
//
|
|
71
|
+
// These modules exist solely to prove that the macro accepts certain grammar
|
|
72
|
+
// variants. If any of these stops compiling, a future change broke the macro's
|
|
73
|
+
// accepted syntax. No runtime assertions needed — compilation is the test.
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/// Single-variant invocation. Guards the `)+` (one-or-more) boundary.
|
|
77
|
+
mod single_variant {
|
|
78
|
+
use super::*;
|
|
79
|
+
|
|
80
|
+
anchor_trait::declare_instructions! {
|
|
81
|
+
name = SingleIx;
|
|
82
|
+
context = TestCtx;
|
|
83
|
+
|
|
84
|
+
Alpha => alpha(ctx),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// No trailing comma. Guards the optional `$(,)?` at the end.
|
|
89
|
+
mod no_trailing_comma {
|
|
90
|
+
use super::*;
|
|
91
|
+
|
|
92
|
+
anchor_trait::declare_instructions! {
|
|
93
|
+
name = NoCommaIx;
|
|
94
|
+
context = TestCtx;
|
|
95
|
+
|
|
96
|
+
Alpha => alpha(ctx),
|
|
97
|
+
Beta => beta
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// All variants use the `(ctx)` marker. Guards that the ctx-passing path
|
|
102
|
+
/// works without any no-ctx variants present.
|
|
103
|
+
mod all_ctx {
|
|
104
|
+
use super::*;
|
|
105
|
+
|
|
106
|
+
anchor_trait::declare_instructions! {
|
|
107
|
+
name = AllCtxIx;
|
|
108
|
+
context = TestCtx;
|
|
109
|
+
|
|
110
|
+
Alpha => alpha(ctx),
|
|
111
|
+
Gamma => gamma(ctx),
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// No variants use the `(ctx)` marker. Guards that the no-ctx path works
|
|
116
|
+
/// without any ctx-passing variants present.
|
|
117
|
+
mod all_no_ctx {
|
|
118
|
+
use super::*;
|
|
119
|
+
|
|
120
|
+
anchor_trait::declare_instructions! {
|
|
121
|
+
name = AllNoCtxIx;
|
|
122
|
+
context = TestCtx;
|
|
123
|
+
|
|
124
|
+
Beta => beta,
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Tests
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
#[test]
|
|
133
|
+
fn all_returns_variants_in_declaration_order() {
|
|
134
|
+
let all = TestIx::all();
|
|
135
|
+
assert_eq!(all.len(), 3);
|
|
136
|
+
assert_eq!(all[0], TestIx::Alpha);
|
|
137
|
+
assert_eq!(all[1], TestIx::Beta);
|
|
138
|
+
assert_eq!(all[2], TestIx::Gamma);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[test]
|
|
142
|
+
fn from_ident_maps_canonical_names() {
|
|
143
|
+
let alpha_ident = syn::Ident::new("alpha", proc_macro2::Span::call_site());
|
|
144
|
+
let beta_ident = syn::Ident::new("beta", proc_macro2::Span::call_site());
|
|
145
|
+
let gamma_ident = syn::Ident::new("gamma", proc_macro2::Span::call_site());
|
|
146
|
+
|
|
147
|
+
assert_eq!(TestIx::from_ident(&alpha_ident), Some(TestIx::Alpha));
|
|
148
|
+
assert_eq!(TestIx::from_ident(&beta_ident), Some(TestIx::Beta));
|
|
149
|
+
assert_eq!(TestIx::from_ident(&gamma_ident), Some(TestIx::Gamma));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
#[test]
|
|
153
|
+
fn from_ident_returns_none_for_unknown() {
|
|
154
|
+
let unknown = syn::Ident::new("nonexistent", proc_macro2::Span::call_site());
|
|
155
|
+
assert_eq!(TestIx::from_ident(&unknown), None);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[test]
|
|
159
|
+
fn canonical_name_returns_module_name() {
|
|
160
|
+
assert_eq!(TestIx::Alpha.canonical_name(), "alpha");
|
|
161
|
+
assert_eq!(TestIx::Beta.canonical_name(), "beta");
|
|
162
|
+
assert_eq!(TestIx::Gamma.canonical_name(), "gamma");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#[test]
|
|
166
|
+
fn from_ident_and_canonical_name_are_inverses() {
|
|
167
|
+
for &variant in TestIx::all() {
|
|
168
|
+
let name = variant.canonical_name();
|
|
169
|
+
let ident = syn::Ident::new(name, proc_macro2::Span::call_site());
|
|
170
|
+
assert_eq!(TestIx::from_ident(&ident), Some(variant), "round-trip failed for {name}");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn spec_dispatches_with_ctx_marker() {
|
|
176
|
+
let ctx = TestCtx;
|
|
177
|
+
let spec = TestIx::Alpha.spec(&ctx);
|
|
178
|
+
|
|
179
|
+
// Alpha uses (ctx) marker → alpha::spec(&ctx)
|
|
180
|
+
assert!(!spec.is_view, "alpha should be mutable");
|
|
181
|
+
assert_eq!(spec.params_type.unwrap().to_string(), quote!(AlphaParams).to_string());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#[test]
|
|
185
|
+
fn spec_dispatches_without_ctx_marker() {
|
|
186
|
+
let ctx = TestCtx;
|
|
187
|
+
let spec = TestIx::Beta.spec(&ctx);
|
|
188
|
+
|
|
189
|
+
// Beta has no ctx marker → beta::spec() (ctx not passed)
|
|
190
|
+
assert!(spec.is_view, "beta should be a view");
|
|
191
|
+
assert!(spec.params_type.is_none(), "beta should have no params");
|
|
192
|
+
assert_eq!(spec.return_type.to_string(), quote!(u64).to_string());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
#[test]
|
|
196
|
+
fn ord_matches_declaration_order() {
|
|
197
|
+
// The macro derives Ord. Enum discriminant order = declaration order,
|
|
198
|
+
// so Alpha < Beta < Gamma must hold.
|
|
199
|
+
assert!(TestIx::Alpha < TestIx::Beta);
|
|
200
|
+
assert!(TestIx::Beta < TestIx::Gamma);
|
|
201
|
+
|
|
202
|
+
// Also verify via sort: sorting should be a no-op on all()
|
|
203
|
+
let mut sorted = TestIx::all().to_vec();
|
|
204
|
+
sorted.sort();
|
|
205
|
+
assert_eq!(sorted, TestIx::all());
|
|
206
|
+
}
|