001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krms.framework;
017
018 import static junit.framework.Assert.assertEquals;
019 import static junit.framework.Assert.assertTrue;
020 import static junit.framework.Assert.fail;
021
022 import java.util.Arrays;
023 import java.util.Collections;
024 import java.util.HashMap;
025 import java.util.HashSet;
026 import java.util.LinkedList;
027 import java.util.List;
028 import java.util.Map;
029 import java.util.Set;
030
031 import org.apache.commons.lang.StringUtils;
032 import org.junit.Before;
033 import org.junit.Test;
034 import org.kuali.rice.krms.api.engine.Term;
035 import org.kuali.rice.krms.api.engine.TermResolutionEngine;
036 import org.kuali.rice.krms.api.engine.TermResolutionException;
037 import org.kuali.rice.krms.api.engine.TermResolver;
038 import org.kuali.rice.krms.framework.engine.TermResolutionEngineImpl;
039 import org.springframework.util.CollectionUtils;
040
041
042 public class TermResolutionEngineTest {
043
044 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(TermResolutionEngineTest.class);
045
046 private TermResolutionEngine termResolutionEngine = null;
047
048 @Before
049 public void setUp() {
050 termResolutionEngine = new TermResolutionEngineImpl();
051 }
052
053 @Test
054 public void testNoResolution() {
055 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
056
057 // GIVENS:
058 testHelper.addGivens("A");
059
060 testHelper.logScenarioDescription();
061
062 testHelper.assertSuccess("A");
063 }
064
065 @Test
066 public void testSimpleResolution() {
067 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
068
069 // GIVENS:
070 testHelper.addGivens("A");
071
072 // RESOLVERS:
073 testHelper.addResolver("B", /* <-- */ "A");
074
075 testHelper.logScenarioDescription();
076
077 testHelper.assertSuccess("B");
078 }
079
080 @Test
081 public void testTwoStepResolution() {
082 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
083
084 // GIVENS:
085 testHelper.addGivens("A");
086
087 // RESOLVERS:
088 testHelper.addResolver("B", /* <-- */ "A");
089 testHelper.addResolver("C", /* <-- */ "B");
090
091 testHelper.logScenarioDescription();
092
093 testHelper.assertSuccess("C");
094 }
095
096 @Test
097 public void testForkingResolution() {
098 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
099
100 // GIVENS:
101 testHelper.addGivens("A", "Z");
102
103 // RESOLVERS:
104 testHelper.addResolver("D", /* <-- */ "B","C");
105 testHelper.addResolver("C", /* <-- */ "Z");
106 testHelper.addResolver("B", /* <-- */ "A");
107
108 testHelper.logScenarioDescription();
109
110 testHelper.assertSuccess("D");
111 }
112
113 @Test
114 public void testMultipleValidPaths() {
115 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
116
117 // GIVENS:
118 testHelper.addGivens("A", "Z");
119
120 // RESOLVERS:
121 testHelper.addResolver("D", /* <-- */ "B","C");
122 testHelper.addResolver("C", /* <-- */ "Z");
123 testHelper.addResolver("B", /* <-- */ "A");
124 testHelper.addResolver("D", /* <-- */ "A");
125
126 testHelper.logScenarioDescription();
127
128 testHelper.assertSuccess("D");
129 }
130
131 @Test
132 public void testDiamond() {
133 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
134
135 // GIVENS:
136 testHelper.addGivens("A");
137
138 // RESOLVERS:
139 testHelper.addResolver("D", /* <-- */ "B","C");
140 testHelper.addResolver("C", /* <-- */ "A");
141 testHelper.addResolver("B", /* <-- */ "A");
142
143 testHelper.logScenarioDescription();
144
145 testHelper.assertSuccess("D");
146
147 }
148
149 @Test
150 public void testComplexPath() {
151 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
152
153 // GIVENS:
154 testHelper.addGivens("Q","R","S");
155
156 // RESOLVERS:
157 testHelper.addResolver("A", /* <-- */ "B","F");
158 testHelper.addResolver("A", /* <-- */ "Z");
159 testHelper.addResolver("B", /* <-- */ "D");
160 testHelper.addResolver("B", /* <-- */ "C");
161 testHelper.addResolver("C", /* <-- */ "S");
162 testHelper.addResolver("D", /* <-- */ "E");
163 testHelper.addResolver("E", /* <-- */ "S");
164 testHelper.addResolver("F", /* <-- */ "G","Q");
165 testHelper.addResolver("G", /* <-- */ "R");
166
167 testHelper.logScenarioDescription();
168
169 testHelper.assertSuccess("A");
170
171 }
172
173 @Test
174 public void testCycle() {
175 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
176
177 // GIVENS:
178 // none
179
180 // RESOLVERS:
181 testHelper.addResolver("D", /* <-- */ "C");
182 testHelper.addResolver("C", /* <-- */ "D");
183
184 testHelper.logScenarioDescription();
185
186 testHelper.assertException("D");
187
188 }
189
190
191 @Test
192 public void testUnreachableTerm() {
193 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
194
195 // GIVENS:
196 // none
197
198 // RESOLVERS:
199 testHelper.addResolver("D", /* <-- */ "C");
200 testHelper.addResolver("C", /* <-- */ "B");
201
202 testHelper.logScenarioDescription();
203
204 testHelper.assertException("D");
205
206 }
207
208 @Test
209 public void testRedHerringPath() {
210 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
211
212 // GIVENS:
213 testHelper.addGivens("Q");
214
215 // RESOLVERS:
216 testHelper.addResolver("A", /* <-- */ "B");
217 // this is the shortest path, but C can't be resolved
218 testHelper.addResolver("B", /* <-- */ "C");
219
220 testHelper.addResolver("B", /* <-- */ "D");
221 testHelper.addResolver("D", /* <-- */ "Q");
222
223 testHelper.logScenarioDescription();
224
225 testHelper.assertSuccess("A");
226 }
227
228 @Test
229 public void testResolveParamaterizedTerm() {
230 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
231
232 // GIVENS:
233 testHelper.addGivens("Q");
234
235 // RESOLVERS:
236 testHelper.addResolver(1, "A", new String[] {"param"}, /* <-- */ "B");
237 testHelper.addResolver("B", /* <-- */ "Q");
238
239 testHelper.logScenarioDescription();
240
241 Map<String,String> params = new HashMap();
242 params.put("param", "value");
243 testHelper.assertSuccess(testHelper.getTerm("A", params));
244
245 // termName A needs parameters, so this shouldn't work
246 testHelper.assertException(new Term("A"));
247
248 }
249
250 @Test
251 public void testIntermediateParamaterizedTerm() {
252 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
253
254 // GIVENS:
255 testHelper.addGivens("Q");
256
257 // RESOLVERS:
258 testHelper.addResolver(1, "A", new String[] {"foo"}, /* <-- */ "B");
259 testHelper.addResolver(1, "B", new String[] {"bar"}, /* <-- */ "Q");
260
261 testHelper.logScenarioDescription();
262
263 Map<String,String> params = new HashMap();
264 params.put("foo", "foovalue");
265
266 // Resolver for termName b requires parameters, so this shouldn't work
267 testHelper.assertException(testHelper.getTerm("A", params));
268 }
269
270 private static class WhiteBoxTermResolutionEngineImpl extends TermResolutionEngineImpl {
271
272 // expose this for testing purposes
273 @Override
274 public List<TermResolverKey> buildTermResolutionPlan(String termName) {
275 return super.buildTermResolutionPlan(termName);
276 }
277 }
278
279
280 @Test
281 public void testShortestPath() {
282
283 WhiteBoxTermResolutionEngineImpl whiteBoxTermResolutionService = new WhiteBoxTermResolutionEngineImpl();
284 TestScenarioHelper testHelper = new TestScenarioHelper(whiteBoxTermResolutionService);
285
286 // GIVENS:
287 testHelper.addGivens("Q");
288
289 // RESOLVERS:
290 // this one costs 3 (instead of default cost of 1)
291 testHelper.addResolver(3, "A", /* <-- */ "Q");
292
293 // the steps for the alternate path each cost 1, but total length is 4
294 testHelper.addResolver("A", /* <-- */ "B");
295 testHelper.addResolver("B", /* <-- */ "C");
296 testHelper.addResolver("C", /* <-- */ "D");
297 testHelper.addResolver("D", /* <-- */ "Q");
298
299 testHelper.logScenarioDescription();
300
301 List<?> plan = whiteBoxTermResolutionService.buildTermResolutionPlan("A");
302 LOG.info("resolutionPlan: " + StringUtils.join(plan, ", ") + " <-- should be length 1!");
303 assertTrue("didn't choose the shortest resolution path (of length 1)", plan.size() == 1);
304 }
305
306 /*
307 * TODO: test exception variants:
308 * - TermResolver throws TermResolutionException
309 * - TermResolutionEngine is passed a null termName
310 */
311
312 // TODO: what should the TermResolutionEngine do if a resolver throws a RuntimeException?
313 /*
314 @Test
315 public void testExplodingResolver() {
316 TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
317
318 // GIVENS:
319 testHelper.addGivens("Q");
320
321 // RESOLVERS:
322 TermResolverMock exploder = new TermResolverMock(testHelper.getTerm("A"), testHelper.getResult("A"), testHelper.getTerm("Q"));
323 exploder.setIsExploder(true);
324 termResolutionEngine.addTermResolver(exploder);
325
326 testHelper.logScenarioDescription();
327
328 testHelper.assertException("A");
329 }
330 */
331
332
333 private static class TermResolverMock<T> implements TermResolver<T> {
334 private String output;
335 private Set<String> params;
336 private T result;
337 private Set<String> prereqs;
338 private int cost;
339 private boolean isExploder = false;
340
341 public TermResolverMock(String output, T result, int cost, String ... prereqs) {
342 this(output, null, result, cost, prereqs);
343 }
344
345 public TermResolverMock(String output, Set<String> params, T result, int cost, String ... prereqs) {
346 this.output = output;
347 this.params = Collections.unmodifiableSet(params);
348 this.result = result;
349 this.prereqs = new HashSet<String>(Arrays.asList(prereqs));
350 this.cost = cost;
351 }
352
353 @Override
354 public int getCost() {
355 return cost;
356 }
357
358 @Override
359 public String getOutput() {
360 return output;
361 }
362
363 @Override
364 public Set<String> getPrerequisites() {
365 return prereqs;
366 }
367
368 @Override
369 public Set<String> getParameterNames() {
370 return params;
371 }
372
373 public void setIsExploder(boolean isExploder) {
374 this.isExploder = isExploder;
375 }
376
377 @Override
378 public T resolve(Map<String, Object> resolvedPrereqs, Map<String, String> parameters) {
379
380 if (isExploder) {
381 throw new RuntimeException("I'm the exploder, coo coo catchoo");
382 }
383
384 // get all prereqs first
385 for (String prereq : prereqs) {
386 Object result = resolvedPrereqs.get(prereq);
387 if (result == null) fail("got back null for prereq " + prereq);
388 }
389
390 LOG.info("resolving " + output);
391 return result;
392 }
393
394 @Override
395 public String toString() {
396 String paramStr = "";
397 if (!CollectionUtils.isEmpty(params)) {
398 paramStr = "+(" + StringUtils.join(params, ",") + ")";
399 }
400 return getClass().getSimpleName()+"[ "+output+paramStr+ " <- " + StringUtils.join(prereqs.iterator(), ",") + " ]";
401 }
402
403 }
404
405
406 private static class TestScenarioHelper {
407
408 private final TermResolutionEngine ars;
409
410 private List<String> givens = new LinkedList<String>();
411 private List<String> resolvers = new LinkedList<String>();
412
413 public TestScenarioHelper(TermResolutionEngine tre) {
414 this.ars = tre;
415 }
416
417 public void addGivens(String ... names) {
418 for (String name : names) {
419 ars.addTermValue(new Term(name, null), getResult(name));
420 givens.add(name);
421 }
422 }
423
424 public String getResult(String name) {
425 return getResult(new Term(name, null));
426 }
427
428 public String getResult(Term term) {
429 return term.getName() +"-result";
430 }
431
432 public Term getTerm(String name, Map<String,String> params) {
433 return new Term(name, params);
434 }
435
436 public void addResolver(String out, String ... prereqs) {
437 addResolver(1, out, prereqs);
438 }
439
440 public void addResolver(int cost, String out, String ... prereqs) {
441 addResolver(1, out, null, prereqs);
442 }
443
444 public void addResolver(int cost, String out, String[] params, String ... prereqs) {
445 String [] prereqTerms = new String [prereqs.length];
446
447 for (int i=0; i<prereqs.length; i++) prereqTerms[i] = prereqs[i];
448
449 Set<String> paramSet = Collections.emptySet();
450 if (params != null) paramSet = new HashSet<String>(Arrays.asList(params));
451
452 ars.addTermResolver(new TermResolverMock<Object>(out, paramSet, getResult(out), cost, prereqTerms));
453 resolvers.add("(" + out + " <- " + StringUtils.join(prereqs, ",") + ")");
454 }
455
456 public void logScenarioDescription() {
457 StringBuilder sb = new StringBuilder();
458
459 sb.append("givens: " + StringUtils.join(givens.iterator(), ", ") + "\n\n");
460 sb.append("resolvers:\n----------------------\n");
461 if (resolvers == null || resolvers.size() == 0) {
462 sb.append("none");
463 } else {
464 sb.append(StringUtils.join(resolvers.iterator(), "\n"));
465 }
466 sb.append("\n");
467
468 LOG.info("Test Scenario:\n\n" + sb.toString());
469 }
470
471 public void assertSuccess(String toResolve) {
472 assertSuccess(new Term(toResolve, null));
473 }
474
475 public void assertSuccess(Term toResolve) {
476 LOG.info("Testing resolution of " + toResolve);
477 try {
478 assertEquals(getResult(toResolve), ars.resolveTerm(toResolve));
479 LOG.info("Success!");
480 } catch (TermResolutionException e) {
481 fail("Should resolve the termName w/o exceptions");
482 }
483 }
484
485 public void assertException(String toResolve) {
486 assertException(new Term(toResolve, null));
487 }
488
489 public void assertException(Term toResolve) {
490 LOG.info("Testing resolution of " + toResolve);
491 try {
492 ars.resolveTerm(toResolve);
493 fail("Should throw TermResolutionException");
494 } catch (TermResolutionException e) {
495 LOG.info("Success! threw " + e);
496 // Good, this is what we expect
497 }
498 }
499 }
500
501 }