integra8/decorations/
hierarchy.rs

1use std::collections::HashMap;
2
3use crate::decorations::{BookEndDecoration, ComponentDecoration, SuiteAttributesDecoration, TestDecoration};
4
5use crate::components::{
6    ComponentDescription, ComponentGeneratorId, Suite, SuiteAttributes, TestParameters,
7};
8
9#[derive(Debug)]
10pub struct ComponentGroup<TParameters> {
11    pub suite: Option<SuiteAttributesDecoration>,
12    pub tests: Vec<TestDecoration<TParameters>>,
13    pub setups: Vec<BookEndDecoration<TParameters>>,
14    pub tear_downs: Vec<BookEndDecoration<TParameters>>,
15    pub sub_groups: Vec<ComponentGroup<TParameters>>,
16}
17
18impl<TParameters: TestParameters> ComponentGroup<TParameters> {
19    pub fn into_root_component<ComponentsIterator>(
20        components: ComponentsIterator,
21        parameters: &TParameters,
22    ) -> Suite<TParameters>
23    where
24        ComponentsIterator: IntoIterator<Item = ComponentDecoration<TParameters>>,
25    {
26        ComponentHierarchy::from_decorated_components(components)
27            .into_component_groups()
28            .into_component(&mut ComponentGeneratorId::new(), None, parameters)
29    }
30
31    fn into_component(
32        self,
33        id_gen: &mut ComponentGeneratorId,
34        parent: Option<(&SuiteAttributes, &ComponentDescription)>,
35        parameters: &TParameters,
36    ) -> Suite<TParameters> {
37        let parent_suite_attributes = self
38            .suite
39            .unwrap_or_else(|| SuiteAttributesDecoration::root(parameters.root_namespace()));
40
41        let mut suite = parent_suite_attributes.into_component(id_gen.next(), parent, parameters);
42
43        // For a clean implementation, we want the id to accessed in approximate order of execution.
44
45        // 1: Setups
46        suite.setups = self
47            .setups
48            .into_iter()
49            .map(|x| x.into_setup_component(id_gen.next(), &suite.description, &suite.attributes))
50            .collect();
51
52        // 2: Tests
53        suite.tests = self
54            .tests
55            .into_iter()
56            .map(|x| {
57                x.into_component(
58                    id_gen.next(),
59                    &suite.description,
60                    &suite.attributes,
61                    parameters,
62                )
63            })
64            .collect();
65
66        // 3: Nested Suites
67        suite.suites = self
68            .sub_groups
69            .into_iter()
70            .map(|x| {
71                x.into_component(
72                    id_gen,
73                    Some((&suite.attributes, &suite.description)),
74                    parameters,
75                )
76            })
77            .collect();
78
79        // Tear downs
80        suite.tear_downs = self
81            .tear_downs
82            .into_iter()
83            .map(|x| {
84                x.into_tear_down_component(id_gen.next(), &suite.description, &suite.attributes)
85            })
86            .collect();
87
88        suite
89    }
90}
91
92#[derive(Debug)]
93pub struct ComponentHierarchy<TParameters> {
94    root: HierarchyNode<TParameters>,
95}
96
97impl<TParameters> ComponentHierarchy<TParameters> {
98    pub fn new() -> Self {
99        Self {
100            root: HierarchyNode::new_node(),
101        }
102    }
103
104    pub fn from_decorated_components<ComponentsIterator>(components: ComponentsIterator) -> Self
105    where
106        ComponentsIterator: IntoIterator<Item = ComponentDecoration<TParameters>>,
107    {
108        Self {
109            root: components
110                .into_iter()
111                .fold(HierarchyNode::new_node(), |mut root, c| {
112                    root.insert_component(c);
113                    root
114                }),
115        }
116    }
117
118    pub fn into_component_groups(self) -> ComponentGroup<TParameters> {
119        self.root.into_component_groups()
120    }
121}
122
123#[derive(Debug)]
124pub struct HierarchyNode<TParameters> {
125    suite: Option<SuiteAttributesDecoration>,
126    tests: Vec<TestDecoration<TParameters>>,
127    setups: Vec<BookEndDecoration<TParameters>>,
128    tear_downs: Vec<BookEndDecoration<TParameters>>,
129    nodes: HashMap<String, HierarchyNode<TParameters>>,
130}
131
132impl<TParameters> HierarchyNode<TParameters> {
133    pub fn new_node() -> Self {
134        Self {
135            suite: None,
136            tests: Vec::<TestDecoration<TParameters>>::new(),
137            setups: Vec::<BookEndDecoration<TParameters>>::new(),
138            tear_downs: Vec::<BookEndDecoration<TParameters>>::new(),
139            nodes: HashMap::new(),
140        }
141    }
142
143    pub fn insert_component(&mut self, component: ComponentDecoration<TParameters>) {
144        match component {
145            ComponentDecoration::IntegrationTest(test) => {
146                self.insert_test(test);
147            }
148            ComponentDecoration::Suite(suite_description) => {
149                self.insert_suite(suite_description);
150            }
151            ComponentDecoration::TearDown(bookend) => {
152                self.insert_teardown(bookend);
153            }
154            ComponentDecoration::Setup(bookend) => {
155                self.insert_setup(bookend);
156            }
157        }
158    }
159
160    pub fn insert_suite(&mut self, suite: SuiteAttributesDecoration) {
161        let mut node = self.find_namespace_entry(&suite.location.path.as_str());
162        node.suite = Some(suite);
163    }
164
165    pub fn insert_test(&mut self, test: TestDecoration<TParameters>) {
166        let node = self.find_method_entry(&test.desc.location.path.as_str());
167        node.tests.push(test);
168    }
169
170    pub fn insert_setup(&mut self, setup: BookEndDecoration<TParameters>) {
171        let node = self.find_method_entry(&setup.desc.location.path.as_str());
172        node.setups.push(setup);
173    }
174
175    pub fn insert_teardown(&mut self, teardown: BookEndDecoration<TParameters>) {
176        let node = self.find_method_entry(&teardown.desc.location.path.as_str());
177        node.tear_downs.push(teardown);
178    }
179
180    fn find_namespace_entry<'a>(&'a mut self, path: &str) -> &'a mut Self {
181        let v: Vec<&str> = path.split("::").collect();
182        self.find_entry_from_path_elements(&v)
183    }
184
185    fn find_method_entry<'a>(&'a mut self, path: &str) -> &'a mut Self {
186        let v: Vec<&str> = path.split("::").collect();
187        // discard the last element, as the last element is the name of the method
188        match v.split_last() {
189            None => self,
190            Some((_, path)) => self.find_entry_from_path_elements(path),
191        }
192    }
193
194    fn find_entry_from_path_elements<'a>(&'a mut self, path: &[&str]) -> &'a mut Self {
195        match path.split_first() {
196            None => self,
197            Some((cur, rest)) => {
198                let next = self
199                    .nodes
200                    .entry(cur.to_string())
201                    .or_insert(Self::new_node());
202                next.find_entry_from_path_elements(rest)
203            }
204        }
205    }
206
207    pub fn into_component_groups(mut self) -> ComponentGroup<TParameters> {
208        let mut sub_groups = vec![];
209        let suite = std::mem::take(&mut self.suite);
210        let mut tests = std::mem::take(&mut self.tests);
211        let mut setups = std::mem::take(&mut self.setups);
212        let mut tear_downs = std::mem::take(&mut self.tear_downs);
213
214        for (_, node) in self.nodes {
215            let mut group = node.into_component_groups();
216
217            if group.suite.is_some() {
218                sub_groups.push(group);
219            } else {
220                tests.append(&mut group.tests);
221                tear_downs.append(&mut group.tear_downs);
222                setups.append(&mut group.setups);
223                sub_groups.append(&mut group.sub_groups);
224            }
225        }
226
227        // Order components
228        sub_groups.sort_unstable_by(|a, b| {
229            let a = a.suite.as_ref().map(|x| &x.location);
230            let b = b.suite.as_ref().map(|x| &x.location);
231            a.cmp(&b)
232        });
233        tests.sort_unstable_by(|a, b| a.desc.location.cmp(&b.desc.location));
234        setups.sort_unstable_by(|a, b| a.desc.location.cmp(&b.desc.location));
235        tear_downs.sort_unstable_by(|a, b| a.desc.location.cmp(&b.desc.location));
236
237        return ComponentGroup {
238            suite,
239            tests,
240            setups,
241            tear_downs,
242            sub_groups,
243        };
244    }
245}