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