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 }