View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krms.framework;
17  
18  import static junit.framework.Assert.assertEquals;
19  import static junit.framework.Assert.assertTrue;
20  import static junit.framework.Assert.fail;
21  
22  import java.util.Arrays;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.apache.commons.lang.StringUtils;
32  import org.junit.Before;
33  import org.junit.Test;
34  import org.kuali.rice.krms.api.engine.Term;
35  import org.kuali.rice.krms.api.engine.TermResolutionEngine;
36  import org.kuali.rice.krms.api.engine.TermResolutionException;
37  import org.kuali.rice.krms.api.engine.TermResolver;
38  import org.kuali.rice.krms.framework.engine.TermResolutionEngineImpl;
39  import org.springframework.util.CollectionUtils;
40  
41  
42  public class TermResolutionEngineTest {
43  	
44  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(TermResolutionEngineTest.class);
45  
46  	private TermResolutionEngine termResolutionEngine = null;
47  	
48  	@Before
49  	public void setUp() {
50  		termResolutionEngine = new TermResolutionEngineImpl();
51  	}
52  	
53  	@Test
54  	public void testNoResolution() {
55  		TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
56  		
57  		// GIVENS:
58  		testHelper.addGivens("A");
59  
60  		testHelper.logScenarioDescription();
61  		
62  		testHelper.assertSuccess("A");
63  	}
64  	
65  	@Test
66  	public void testSimpleResolution() {
67  		TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
68  		
69  		// GIVENS:
70  		testHelper.addGivens("A");
71  		
72  		// RESOLVERS:
73  		testHelper.addResolver("B", /* <-- */ "A");
74  		
75  		testHelper.logScenarioDescription();
76  		
77  		testHelper.assertSuccess("B");
78  	}
79  	
80  	@Test
81  	public void testTwoStepResolution() {
82  		TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
83  		
84  		// GIVENS:
85  		testHelper.addGivens("A");
86  		
87  		// RESOLVERS:
88  		testHelper.addResolver("B", /* <-- */ "A");
89  		testHelper.addResolver("C", /* <-- */ "B");
90  		
91  		testHelper.logScenarioDescription();
92  		
93  		testHelper.assertSuccess("C");
94  	}
95  
96  	@Test
97  	public void testForkingResolution() {
98  		TestScenarioHelper testHelper = new TestScenarioHelper(termResolutionEngine);
99  
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 }