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 }