Skip to main content

whitaker_common/test_support/
decomposition.rs

1//! Shared decomposition-advice fixtures for unit and integration tests.
2//!
3//! These helpers keep the recurring grammar/serde/filesystem and transport
4//! method communities aligned across diagnostic unit tests and behaviour
5//! coverage.
6
7#[path = "decomposition_vector_algebra.rs"]
8mod vector_algebra;
9
10use crate::decomposition_advice::{
11    DecompositionContext, DecompositionSuggestion, MethodProfile, MethodProfileBuilder,
12    SubjectKind, methods_meet_cosine_threshold as runtime_methods_meet_cosine_threshold,
13    suggest_decomposition,
14};
15
16pub use self::vector_algebra::{MethodVectorAlgebraReport, method_vector_algebra};
17
18/// Input data for building a [`MethodProfile`] in tests.
19///
20/// # Examples
21///
22/// ```ignore
23/// use whitaker_common::test_support::decomposition::{MethodInput, profile};
24///
25/// let profile = profile(MethodInput {
26///     name: "parse_tokens",
27///     fields: &["grammar"],
28///     signature_types: &[],
29///     local_types: &[],
30///     domains: &[],
31/// });
32///
33/// assert_eq!(profile.name(), "parse_tokens");
34/// ```
35pub struct MethodInput<'a> {
36    pub name: &'a str,
37    pub fields: &'a [&'a str],
38    pub signature_types: &'a [&'a str],
39    pub local_types: &'a [&'a str],
40    pub domains: &'a [&'a str],
41}
42
43/// Builds a [`MethodProfile`] from declarative test input.
44///
45/// # Examples
46///
47/// ```ignore
48/// use whitaker_common::test_support::decomposition::{MethodInput, profile};
49///
50/// let profile = profile(MethodInput {
51///     name: "save_to_disk",
52///     fields: &[],
53///     signature_types: &[],
54///     local_types: &["PathBuf"],
55///     domains: &["std::fs"],
56/// });
57///
58/// assert_eq!(profile.name(), "save_to_disk");
59/// ```
60#[must_use]
61pub fn profile(input: MethodInput<'_>) -> MethodProfile {
62    let mut builder = MethodProfileBuilder::new(input.name);
63    for field in input.fields {
64        builder.record_accessed_field(*field);
65    }
66    for type_name in input.signature_types {
67        builder.record_signature_type(*type_name);
68    }
69    for type_name in input.local_types {
70        builder.record_local_type(*type_name);
71    }
72    for domain in input.domains {
73        builder.record_external_domain(*domain);
74    }
75    builder.build()
76}
77
78/// Builds the recurring parser/serde/filesystem fixture.
79///
80/// # Examples
81///
82/// ```ignore
83/// use whitaker_common::test_support::decomposition::parser_serde_fs_fixture;
84///
85/// let methods = parser_serde_fs_fixture();
86/// assert_eq!(methods.len(), 6);
87/// ```
88#[must_use]
89pub fn parser_serde_fs_fixture() -> Vec<MethodProfile> {
90    vec![
91        profile(MethodInput {
92            name: "parse_tokens",
93            fields: &["grammar", "tokens"],
94            signature_types: &["TokenStream"],
95            local_types: &[],
96            domains: &[],
97        }),
98        profile(MethodInput {
99            name: "parse_nodes",
100            fields: &["grammar", "ast"],
101            signature_types: &[],
102            local_types: &["ParseState"],
103            domains: &[],
104        }),
105        profile(MethodInput {
106            name: "encode_json",
107            fields: &[],
108            signature_types: &["Serializer"],
109            local_types: &[],
110            domains: &["serde::json"],
111        }),
112        profile(MethodInput {
113            name: "decode_json",
114            fields: &[],
115            signature_types: &["Deserializer"],
116            local_types: &[],
117            domains: &["serde::json"],
118        }),
119        profile(MethodInput {
120            name: "load_from_disk",
121            fields: &[],
122            signature_types: &[],
123            local_types: &["PathBuf"],
124            domains: &["std::fs"],
125        }),
126        profile(MethodInput {
127            name: "save_to_disk",
128            fields: &[],
129            signature_types: &[],
130            local_types: &["PathBuf"],
131            domains: &["std::fs"],
132        }),
133    ]
134}
135
136/// Builds the recurring transport trait fixture.
137///
138/// # Examples
139///
140/// ```ignore
141/// use whitaker_common::test_support::decomposition::transport_trait_fixture;
142///
143/// let methods = transport_trait_fixture();
144/// assert_eq!(methods.len(), 4);
145/// ```
146#[must_use]
147pub fn transport_trait_fixture() -> Vec<MethodProfile> {
148    vec![
149        profile(MethodInput {
150            name: "encode_request",
151            fields: &[],
152            signature_types: &[],
153            local_types: &[],
154            domains: &["serde::json"],
155        }),
156        profile(MethodInput {
157            name: "decode_request",
158            fields: &[],
159            signature_types: &[],
160            local_types: &[],
161            domains: &["serde::json"],
162        }),
163        profile(MethodInput {
164            name: "read_frame",
165            fields: &[],
166            signature_types: &["IoBuffer"],
167            local_types: &[],
168            domains: &["std::io"],
169        }),
170        profile(MethodInput {
171            name: "write_frame",
172            fields: &[],
173            signature_types: &["IoBuffer"],
174            local_types: &[],
175            domains: &["std::io"],
176        }),
177    ]
178}
179
180/// Computes decomposition suggestions for `methods`.
181///
182/// # Examples
183///
184/// ```ignore
185/// use whitaker_common::decomposition_advice::SubjectKind;
186/// use whitaker_common::test_support::decomposition::{
187///     decomposition_suggestions,
188///     parser_serde_fs_fixture,
189/// };
190///
191/// let methods = parser_serde_fs_fixture();
192/// let (_context, suggestions) =
193///     decomposition_suggestions("Foo", SubjectKind::Type, &methods);
194/// assert!(!suggestions.is_empty());
195/// ```
196#[must_use]
197pub fn decomposition_suggestions(
198    subject: &str,
199    kind: SubjectKind,
200    methods: &[MethodProfile],
201) -> (DecompositionContext, Vec<DecompositionSuggestion>) {
202    let context = DecompositionContext::new(subject, kind);
203    let suggestions = suggest_decomposition(&context, methods);
204    (context, suggestions)
205}
206
207/// Evaluates whether two methods satisfy Whitaker's cosine threshold.
208///
209/// This helper exists for behaviour tests that need an observable seam without
210/// widening the production decomposition API.
211///
212/// # Examples
213///
214/// ```ignore
215/// use whitaker_common::test_support::decomposition::{MethodInput, methods_meet_cosine_threshold, profile};
216///
217/// let left = profile(MethodInput {
218///     name: "parse_tokens",
219///     fields: &["grammar"],
220///     signature_types: &[],
221///     local_types: &[],
222///     domains: &[],
223/// });
224/// let right = profile(MethodInput {
225///     name: "parse_nodes",
226///     fields: &["grammar"],
227///     signature_types: &[],
228///     local_types: &[],
229///     domains: &[],
230/// });
231///
232/// assert!(methods_meet_cosine_threshold(&left, &right));
233/// ```
234#[must_use]
235pub fn methods_meet_cosine_threshold(left: &MethodProfile, right: &MethodProfile) -> bool {
236    runtime_methods_meet_cosine_threshold(left, right)
237}