|
34 | 34 | */ |
35 | 35 | class EngineDiscoveryResultValidator { |
36 | 36 |
|
37 | | - static abstract class ValidationError { |
| 37 | + static abstract class ValidationError extends RuntimeException { |
| 38 | + static final long serialVersionUID = -7034897190745766939L; |
38 | 39 |
|
39 | 40 | public abstract String message(); |
40 | 41 | } |
41 | 42 |
|
42 | 43 | static class CyclicGraphError extends ValidationError { |
43 | | - TestDescriptor node1; |
44 | | - TestDescriptor node2; |
| 44 | + static final long serialVersionUID = -7034897190745766939L; |
| 45 | + |
| 46 | + LinkedList<TestDescriptor> p1; |
| 47 | + LinkedList<TestDescriptor> p2; |
45 | 48 |
|
46 | 49 | @Override |
47 | 50 | public String message() { |
48 | | - LinkedList<TestDescriptor> p1 = getPath(node1); |
49 | | - LinkedList<TestDescriptor> p2 = getPath(node2); |
50 | 51 |
|
51 | 52 | StringBuilder result = new StringBuilder(); |
52 | 53 | result.append("Graph cycle found:\n\t"); |
@@ -90,130 +91,132 @@ public void accept(TestDescriptor x) { |
90 | 91 |
|
91 | 92 | } |
92 | 93 |
|
93 | | - static class NonReciprocalParentError extends ValidationError { |
94 | | - TestDescriptor parent; |
| 94 | + |
| 95 | + static class IllegalParentError extends ValidationError { |
| 96 | + static final long serialVersionUID = -7034897190745766939L; |
| 97 | + Optional<TestDescriptor> parent = Optional.empty(); |
95 | 98 | TestDescriptor child; |
96 | 99 |
|
97 | 100 | @Override |
98 | 101 | public String message() { |
99 | | - String parent2 = null; |
100 | | - if (child.getParent().isPresent()) { |
101 | | - parent2 = child.getParent().get().getUniqueId().toString(); |
102 | | - } |
103 | | - else { |
104 | | - parent2 = "<none>"; |
105 | | - } |
| 102 | + String parentStr = parent.map(v -> v.getUniqueId().toString()).orElse("<none>"); |
| 103 | + String parent2Str = child.getParent().map(v -> v.getUniqueId().toString()).orElse("<none>"); |
106 | 104 |
|
107 | 105 | return String.format("%s is ill-formed: parent should be %s\n" + "\tbut found %s", child.getUniqueId(), |
108 | | - parent.getUniqueId(), parent2); |
| 106 | + parentStr, parent2Str); |
109 | 107 | } |
110 | 108 | } |
111 | 109 |
|
112 | | - static class SelfReferringParentError extends ValidationError { |
113 | | - TestDescriptor node; |
114 | | - |
115 | | - @Override |
116 | | - public String message() { |
| 110 | + static class ValidatorExecution { |
| 111 | + TestDescriptor root; |
| 112 | + protected List<ValidationError> errors = new ArrayList<>(); |
117 | 113 |
|
118 | | - return String.format("%s is ill-formed: parent cannot be itself", node.getUniqueId()); |
| 114 | + ValidatorExecution(TestDescriptor root) { |
| 115 | + this.root = root; |
119 | 116 | } |
120 | | - } |
121 | | - |
122 | | - /** |
123 | | - * Perform common validation checks. |
124 | | - * |
125 | | - * @throws org.junit.platform.commons.PreconditionViolationException if any check fails |
126 | | - */ |
127 | | - void validate(TestEngine testEngine, TestDescriptor root) { |
128 | | - Preconditions.notNull(root, |
129 | | - () -> String.format( |
130 | | - "The discover() method for TestEngine with ID '%s' must return a non-null root TestDescriptor.", |
131 | | - testEngine.getId())); |
132 | | - Optional<String> msgs = getValidationErrorMsg(root); |
133 | | - msgs.ifPresent(s -> Preconditions.condition(true, |
134 | | - () -> String.format("The discover() method for TestEngine with ID '%s' returned a cyclic graph:\n" + "%s", |
135 | | - testEngine.getId(), s))); |
136 | 117 |
|
137 | | - } |
| 118 | + void verifyParentChild(Optional<TestDescriptor> parent, TestDescriptor child) { |
| 119 | + Optional<TestDescriptor> effectiveParent = parent.filter(v -> !v.equals(child)); |
| 120 | + Optional<TestDescriptor> parent2 = child.getParent(); |
| 121 | + if (!parent2.equals(effectiveParent)) { |
| 122 | + IllegalParentError error = new IllegalParentError(); |
| 123 | + error.parent = effectiveParent; |
| 124 | + error.child = child; |
138 | 125 |
|
139 | | - /** |
140 | | - * @return {@code true} if the tree does <em>not</em> contain a cycle; else {@code false}. |
141 | | - */ |
142 | | - boolean hasNoError(TestDescriptor root) { |
143 | | - return getValidationErrors(root).isEmpty(); |
144 | | - } |
| 126 | + errors.add(error); |
145 | 127 |
|
146 | | - static LinkedList<TestDescriptor> getPath(TestDescriptor v) { |
| 128 | + throw error; |
| 129 | + } |
| 130 | + } |
147 | 131 |
|
148 | | - LinkedList<TestDescriptor> path = new LinkedList<>(); |
149 | | - path.add(v); |
| 132 | + LinkedList<TestDescriptor> getPath(TestDescriptor v) { |
150 | 133 |
|
151 | | - Optional<TestDescriptor> next = v.getParent(); |
152 | | - while (next.isPresent()) { |
153 | | - TestDescriptor parent = next.get(); |
154 | | - path.add(next.get()); |
155 | | - next = parent.getParent(); |
156 | | - } |
| 134 | + LinkedList<TestDescriptor> path = new LinkedList<>(); |
| 135 | + path.add(v); |
157 | 136 |
|
158 | | - return path; |
159 | | - } |
| 137 | + Optional<TestDescriptor> next = v.getParent(); |
| 138 | + while (next.isPresent()) { |
| 139 | + TestDescriptor current = next.get(); |
| 140 | + path.add(next.get()); |
| 141 | + next = current.getParent(); |
| 142 | + verifyParentChild(next, current); |
| 143 | + } |
160 | 144 |
|
161 | | - Optional<String> getValidationErrorMsg(TestDescriptor root) { |
| 145 | + return path; |
| 146 | + } |
162 | 147 |
|
163 | | - List<ValidationError> errors = getValidationErrors(root); |
| 148 | + List<ValidationError> getErrors() { |
164 | 149 |
|
165 | | - Stream<String> strs = errors.stream().map(ValidationError::message); |
| 150 | + Map<UniqueId, TestDescriptor> visited = new HashMap<>(); |
| 151 | + visited.put(root.getUniqueId(), root); |
166 | 152 |
|
167 | | - return strs.reduce((a, b) -> a.concat("\n").concat(b)); |
168 | | - } |
| 153 | + Queue<TestDescriptor> queue = new ArrayDeque<>(); |
| 154 | + queue.add(root); |
169 | 155 |
|
170 | | - List<ValidationError> getValidationErrors(TestDescriptor root) { |
171 | | - Map<UniqueId, TestDescriptor> visited = new HashMap<>(); |
172 | | - visited.put(root.getUniqueId(), root); |
| 156 | + try { |
| 157 | + while (!queue.isEmpty()) { |
| 158 | + TestDescriptor next = queue.remove(); |
| 159 | + for (TestDescriptor child : next.getChildren()) { |
| 160 | + UniqueId uid = child.getUniqueId(); |
173 | 161 |
|
174 | | - Queue<TestDescriptor> queue = new ArrayDeque<>(); |
175 | | - queue.add(root); |
| 162 | + verifyParentChild(Optional.of(next), child); |
176 | 163 |
|
177 | | - List<ValidationError> errors = new ArrayList<>(); |
| 164 | + TestDescriptor existing = visited.put(uid, child); |
| 165 | + if (existing != null) { |
| 166 | + CyclicGraphError error = new CyclicGraphError(); |
| 167 | + error.p1 = getPath(existing); |
| 168 | + error.p2 = getPath(child); |
178 | 169 |
|
179 | | - while (!queue.isEmpty()) { |
180 | | - TestDescriptor next = queue.remove(); |
181 | | - for (TestDescriptor child : next.getChildren()) { |
182 | | - UniqueId uid = child.getUniqueId(); |
183 | | - Optional<TestDescriptor> next2 = child.getParent(); |
184 | | - if (!next2.equals(Optional.of(next))) { |
185 | | - NonReciprocalParentError error = new NonReciprocalParentError(); |
186 | | - error.parent = next; |
187 | | - error.child = child; |
188 | | - errors.add(error); |
| 170 | + errors.add(error); |
189 | 171 |
|
190 | | - return errors; |
| 172 | + throw error; |
| 173 | + } |
| 174 | + if (child.isContainer()) { |
| 175 | + queue.add(child); |
| 176 | + } |
| 177 | + } |
191 | 178 | } |
| 179 | + } |
| 180 | + catch ( ValidationError ignored) { |
| 181 | + } |
| 182 | + return errors; |
| 183 | + } |
192 | 184 |
|
193 | | - if (next2.equals(Optional.of(child))) { |
194 | | - SelfReferringParentError error = new SelfReferringParentError(); |
195 | | - error.node = child; |
196 | | - errors.add(error); |
197 | 185 |
|
198 | | - return errors; |
199 | | - } |
| 186 | + Optional<String> getErrorMsg() { |
200 | 187 |
|
201 | | - TestDescriptor existingOrNull = visited.put(uid, child); |
202 | | - if (existingOrNull != null) { |
203 | | - CyclicGraphError error = new CyclicGraphError(); |
204 | | - error.node1 = existingOrNull; |
205 | | - error.node2 = child; |
| 188 | + List<ValidationError> errors = getErrors(); |
206 | 189 |
|
207 | | - errors.add(error); |
| 190 | + Stream<String> strs = errors.stream().map(ValidationError::message); |
208 | 191 |
|
209 | | - return errors; // id already known: cycle detected! |
210 | | - } |
211 | | - if (child.isContainer()) { |
212 | | - queue.add(child); |
213 | | - } |
214 | | - } |
| 192 | + return strs.reduce((a, b) -> a.concat("\n").concat(b)); |
215 | 193 | } |
216 | | - return errors; |
217 | 194 | } |
218 | 195 |
|
| 196 | + /** |
| 197 | + * Perform common validation checks. |
| 198 | + * |
| 199 | + * @throws org.junit.platform.commons.PreconditionViolationException if any check fails |
| 200 | + */ |
| 201 | + void validate(TestEngine testEngine, TestDescriptor root) { |
| 202 | + Preconditions.notNull(root, |
| 203 | + () -> String.format( |
| 204 | + "The discover() method for TestEngine with ID '%s' must return a non-null root TestDescriptor.", |
| 205 | + testEngine.getId())); |
| 206 | + Optional<String> msgs = new ValidatorExecution(root).getErrorMsg(); |
| 207 | + msgs.ifPresent(s -> Preconditions.condition(true, |
| 208 | + () -> String.format("The discover() method for TestEngine with ID '%s' returned a cyclic graph:\n" + "%s", |
| 209 | + testEngine.getId(), s))); |
| 210 | + } |
| 211 | + |
| 212 | + Optional<String> getErrorMsg(TestDescriptor root) { |
| 213 | + return new ValidatorExecution(root).getErrorMsg(); |
| 214 | + } |
| 215 | + |
| 216 | + /** |
| 217 | + * @return {@code true} if the tree does <em>not</em> contain a cycle; else {@code false}. |
| 218 | + */ |
| 219 | + boolean hasNoError(TestDescriptor root) { |
| 220 | + return new ValidatorExecution(root).getErrors().isEmpty(); |
| 221 | + } |
219 | 222 | } |
0 commit comments