/* * Copyright (c) 2005, Rob Gordon. */ package org.oddjob.jobs.structural; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import javax.swing.SwingUtilities; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import junit.framework.TestCase; import org.apache.log4j.Logger; import org.oddjob.FailedToStopException; import org.oddjob.Oddjob; import org.oddjob.OddjobLookup; import org.oddjob.OddjobSessionFactory; import org.oddjob.Stateful; import org.oddjob.Structural; import org.oddjob.arooa.ArooaParseException; import org.oddjob.arooa.ArooaSession; import org.oddjob.arooa.ComponentTrinity; import org.oddjob.arooa.convert.ArooaConversionException; import org.oddjob.arooa.deploy.annotations.ArooaAttribute; import org.oddjob.arooa.parsing.MockArooaContext; import org.oddjob.arooa.reflect.ArooaPropertyException; import org.oddjob.arooa.registry.BeanRegistry; import org.oddjob.arooa.registry.ComponentPool; import org.oddjob.arooa.registry.Path; import org.oddjob.arooa.runtime.MockRuntimeConfiguration; import org.oddjob.arooa.runtime.RuntimeConfiguration; import org.oddjob.arooa.standard.StandardArooaSession; import org.oddjob.arooa.types.XMLConfigurationType; import org.oddjob.arooa.utils.DateHelper; import org.oddjob.arooa.xml.XMLConfiguration; import org.oddjob.framework.SimpleJob; import org.oddjob.images.IconHelper; import org.oddjob.monitor.context.ExplorerContext; import org.oddjob.monitor.model.EventThreadLaterExecutor; import org.oddjob.monitor.model.ExplorerContextFactory; import org.oddjob.monitor.model.ExplorerModel; import org.oddjob.monitor.model.JobTreeModel; import org.oddjob.monitor.model.JobTreeNode; import org.oddjob.monitor.model.MockExplorerContext; import org.oddjob.monitor.model.MockExplorerModel; import org.oddjob.persist.MockPersisterBase; import org.oddjob.state.FlagState; import org.oddjob.state.JobState; import org.oddjob.state.ParentState; import org.oddjob.state.StateEvent; import org.oddjob.state.StateListener; import org.oddjob.structural.StructuralEvent; import org.oddjob.structural.StructuralListener; import org.oddjob.tools.ConsoleCapture; import org.oddjob.tools.IconSteps; import org.oddjob.tools.OddjobTestHelper; import org.oddjob.tools.OurDirs; import org.oddjob.tools.StateSteps; /** * * @author Rob Gordon. */ public class ForEachJobTest extends TestCase { private static final Logger logger = Logger.getLogger(ForEachJobTest.class); @Override protected void setUp() throws Exception { super.setUp(); logger.info("-------------------- " + getName() + " ---------------"); } public static class OurJob extends SimpleJob { Object stuff; int index; boolean ran; @Override protected int execute() throws Throwable { ran = true; return 0; } @ArooaAttribute public void setStuff(Object stuff) { this.stuff = stuff; } public void setIndex(int index) { this.index = index; } @Override public String toString() { return getClass().getSimpleName() + " index " + index; } } private class ChildCatcher implements StructuralListener { final List<Object> children = new ArrayList<Object>(); public void childAdded(StructuralEvent event) { children.add(event.getIndex(), event.getChild()); } public void childRemoved(StructuralEvent event) { children.remove(event.getIndex()); } } public void testOneJobTwoValues() throws ArooaParseException { String xml = "<foreach id='foreach'>" + " <job>" + " <bean class='" + OurJob.class.getName() + "' stuff='${foreach.current}' index='${foreach.index}'/>" + " </job>" + "</foreach>"; ForEachJob test = new ForEachJob(); test.setArooaSession(new OddjobSessionFactory().createSession()); test.setConfiguration(new XMLConfiguration("XML", xml)); test.setValues(Arrays.asList("apple", "orange")); ChildCatcher children = new ChildCatcher(); test.addStructuralListener(children); test.run(); assertEquals(ParentState.COMPLETE, test.lastStateEvent().getState()); assertEquals(2, children.children.size()); OurJob job1 = (OurJob) children.children.get(0); OurJob job2 = (OurJob) children.children.get(1); assertEquals("apple", job1.stuff); assertEquals(0, job1.index); assertTrue(job1.ran); assertEquals("orange", job2.stuff); assertEquals(1, job2.index); assertTrue(job2.ran); } public void testWithEmptyList() { String xml = "<foreach/>"; ForEachJob test = new ForEachJob(); test.setArooaSession(new OddjobSessionFactory().createSession()); test.setConfiguration(new XMLConfiguration("XML", xml)); test.setValues(Collections.emptyList()); StateSteps state = new StateSteps(test); state.startCheck(ParentState.READY, ParentState.EXECUTING, ParentState.COMPLETE); test.run(); state.checkNow(); } public void testLoadOnJobTwoValues() throws ArooaParseException { String xml = "<foreach id='foreach'>" + " <job>" + "<bean class='" + OurJob.class.getName() + "' stuff='${foreach.current}' index='${foreach.index}'/>" + " </job>" + "</foreach>"; ForEachJob test = new ForEachJob(); test.setArooaSession(new OddjobSessionFactory().createSession()); test.setConfiguration(new XMLConfiguration("XML", xml)); test.setValues(Arrays.asList("apple", "orange")); ChildCatcher children = new ChildCatcher(); test.addStructuralListener(children); assertTrue(test.isLoadable()); test.load(); assertFalse(test.isLoadable()); assertEquals(2, children.children.size()); OurJob job1 = (OurJob) children.children.get(0); OurJob job2 = (OurJob) children.children.get(1); assertEquals("apple", job1.stuff); assertEquals(0, job1.index); assertFalse(job1.ran); assertEquals("orange", job2.stuff); assertEquals(1, job2.index); assertFalse(job2.ran); } public static class RegistryCheck extends SimpleJob { ArooaSession session; protected int execute() throws Throwable { session = getArooaSession(); return 0; } } private class OurContext extends MockArooaContext { ArooaSession session; OurContext(ArooaSession session) { this.session = session; } @Override public ArooaSession getSession() { return session; } } public void testPseudoRegistry() { String findMe = new String("Fruit is Healthy."); StandardArooaSession session = new StandardArooaSession(); session.getBeanRegistry().register( "fruit", findMe); String xml = "<foreach id='test'>" + " <job>" + " <bean class='" + RegistryCheck.class.getName() + "'/>" + " </job>" + "</foreach>"; ForEachJob test = new ForEachJob(); test.setValues(Arrays.asList("one")); test.setArooaSession(session); test.setConfiguration(new XMLConfiguration("XML", xml)); // so test can configure itself when run. ComponentPool pool = session.getComponentPool(); pool.registerComponent( new ComponentTrinity(test, test, new OurContext(session) { @Override public RuntimeConfiguration getRuntime() { return new MockRuntimeConfiguration() { @Override public void configure() { } }; } }), "test"); test.run(); ChildCatcher child = new ChildCatcher(); test.addStructuralListener(child); RegistryCheck instance = (RegistryCheck) child.children.get(0); BeanRegistry crRecovered = instance.session.getBeanRegistry(); Object bean = crRecovered.lookup("test"); assertNotNull(bean); assertEquals(ForEachJob.LocalBean.class, bean.getClass()); ForEachJob.LocalBean lb = (ForEachJob.LocalBean) bean; int index = lb.getIndex(); assertEquals(0, index); String current = (String) lb.getCurrent(); assertEquals("one", current); } public void testBasic() throws ParseException { checks = new Object[] { new String("hello"), DateHelper.parseDate("2005-12-25"), null, new File("file.txt") }; executed = 0; Oddjob oj = new Oddjob(); oj.setConfiguration( new XMLConfiguration("org/oddjob/jobs/structural/foreach-test.xml", getClass().getClassLoader())); oj.run(); // check doesn't get registered! Check check = (Check) new OddjobLookup(oj).lookup("check"); assertNull(check); assertEquals(4, executed); oj.destroy(); } static Object[] checks; static int executed; public static class Check extends SimpleJob { Object o; int i; @ArooaAttribute public void setObject(Object o) { this.o = o; } public void setIndex(int i) { this.i = i; } protected int execute() { executed++; logger.debug("Executing with object [" + o + "]"); assertEquals(checks[i], o); return 0; } } public void testReset() throws Exception { ChildCatcher childs = new ChildCatcher(); String xml = "<foreach xmlns:arooa='http://rgordon.co.uk/oddjob/arooa'>" + " <values>" + " <list>" + " <values>" + " <value value='COMPLETE'/>" + " <value value='INCOMPLETE'/>" + " <value value='COMPLETE'/>" + " </values>" + " </list>" + " </values>" + " <configuration>" + " <xml>" + " <foreach id='e'>" + " <job>" + " <bean class='" + FlagState.class.getName() + "' state='${e.current}'/>" + " </job>" + " </foreach>" + " </xml>" + " </configuration>" + "</foreach>"; ForEachJob test = (ForEachJob) OddjobTestHelper.createComponentFromXml(xml); test.addStructuralListener(childs); test.run(); assertEquals(JobState.COMPLETE, OddjobTestHelper.getJobState( childs.children.get(0))); assertEquals(JobState.INCOMPLETE, OddjobTestHelper.getJobState( childs.children.get(1))); assertEquals(JobState.READY, OddjobTestHelper.getJobState( childs.children.get(2))); assertEquals(ParentState.INCOMPLETE, OddjobTestHelper.getJobState( test)); test.softReset(); assertEquals(JobState.COMPLETE, OddjobTestHelper.getJobState( childs.children.get(0))); assertEquals(JobState.READY, OddjobTestHelper.getJobState( childs.children.get(1))); assertEquals(JobState.READY, OddjobTestHelper.getJobState( childs.children.get(2))); assertEquals(ParentState.READY, OddjobTestHelper.getJobState( test)); test.run(); Stateful child1 = (Stateful) childs.children.get(0); Stateful child2 = (Stateful) childs.children.get(1); assertEquals(JobState.COMPLETE, child1.lastStateEvent().getState()); assertEquals(JobState.INCOMPLETE, child2.lastStateEvent().getState()); assertEquals(ParentState.INCOMPLETE, test.lastStateEvent().getState()); assertEquals(3, test.getIndex()); test.hardReset(); assertEquals(0, test.getIndex()); assertEquals(JobState.DESTROYED, child1.lastStateEvent().getState()); assertEquals(JobState.DESTROYED, child2.lastStateEvent().getState()); assertEquals(0, childs.children.size()); test.run(); child1 = (Stateful) childs.children.get(0); child2 = (Stateful) childs.children.get(1); assertEquals(JobState.COMPLETE, child1.lastStateEvent().getState()); assertEquals(JobState.INCOMPLETE, child2.lastStateEvent().getState()); assertEquals(ParentState.INCOMPLETE, test.lastStateEvent().getState()); test.destroy(); assertEquals(0, childs.children.size()); assertEquals(JobState.DESTROYED, child1.lastStateEvent().getState()); assertEquals(JobState.DESTROYED, child2.lastStateEvent().getState()); } public void testIdenticalIdInForEachConfig() throws Exception { String config = "<foreach id='e'>" + " <job>" + " <echo>${e.current}</echo>" + " </job>" + "</foreach>"; String xml = "<oddjob>" + " <job>" + " <foreach id='e'>" + " <values>" + " <list>" + " <values>" + " <value value='apple'/>" + " <value value='orange'/>" + " </values>" + " </list>" + " </values>" + " <configuration>" + " <value value='${config}'/>" + " </configuration>" + " </foreach>" + " </job>" + "</oddjob>"; Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration("XML", xml)); XMLConfigurationType configType = new XMLConfigurationType(); configType.setXml(config); oddjob.setExport("config", configType); oddjob.run(); assertEquals(ParentState.COMPLETE, oddjob.lastStateEvent().getState()); } public void testSerializeForEach() throws IOException, ClassNotFoundException { String xml = "<foreach>" + " <job>" + " <echo/>" + " </job>" + "</foreach>"; ForEachJob test = new ForEachJob(); test.setArooaSession(new OddjobSessionFactory().createSession()); test.setConfiguration(new XMLConfiguration("XML", xml)); test.setValues(Arrays.asList("a", "b")); test.run(); assertEquals(ParentState.COMPLETE, test.lastStateEvent().getState()); ForEachJob copy = OddjobTestHelper.copy(test); assertEquals(ParentState.COMPLETE, copy.lastStateEvent().getState()); } private class OurPersister extends MockPersisterBase { @Override protected void persist(Path path, String id, Object component) { assertEquals(new Path("foreach-job-test"), path); assertEquals("fe", id); try { OddjobTestHelper.copy(component); } catch (IOException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } @Override protected Object restore(Path path, String id, ClassLoader classLoader) { assertEquals(new Path("foreach-job-test"), path); assertEquals("fe", id); return null; } } public void testForEachPersistenceButNoChildren() throws Exception { String config = "<foreach>" + " <job>" + " <echo id='x'>${fe.current}</echo>" + " </job>" + "</foreach>"; String xml = "<oddjob>" + " <job>" + " <foreach id='fe'>" + " <values>" + " <list>" + " <values>" + " <value value='apple'/>" + " <value value='orange'/>" + " </values>" + " </list>" + " </values>" + " <configuration>" + " <value value='${config}'/>" + " </configuration>" + " </foreach>" + " </job>" + "</oddjob>"; Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration("XML", xml)); XMLConfigurationType configType = new XMLConfigurationType(); configType.setXml(config); oddjob.setExport("config", configType); OurPersister persister = new OurPersister(); persister.setPath("foreach-job-test"); oddjob.setPersister(persister); oddjob.run(); assertEquals(ParentState.COMPLETE, oddjob.lastStateEvent().getState()); } public void testFileCopyExample() { OurDirs dirs = new OurDirs(); File toDir = dirs.relative("work/foreach"); if (toDir.exists()) { toDir.delete(); } toDir.mkdirs(); Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration( "org/oddjob/jobs/structural/ForEachFilesExample.xml", getClass().getClassLoader())); oddjob.setArgs(new String[] { dirs.base().getPath() }); oddjob.run(); assertEquals(ParentState.COMPLETE, oddjob.lastStateEvent().getState()); oddjob.destroy(); assertTrue(new File(toDir, "test1.txt").exists()); assertTrue(new File(toDir, "test2.txt").exists()); assertTrue(new File(toDir, "test3.txt").exists()); } public void testWithIds() throws ArooaPropertyException, ArooaConversionException { Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration( "org/oddjob/jobs/structural/ForEachWithIdsExample.xml", getClass().getClassLoader())); ConsoleCapture console = new ConsoleCapture(); try (ConsoleCapture.Close close = console.captureConsole()) { oddjob.run(); } assertEquals(ParentState.COMPLETE, oddjob.lastStateEvent().getState()); console.dump(logger); OddjobLookup lookup = new OddjobLookup(oddjob); Structural foreach = lookup.lookup("foreach", Structural.class); ChildCatcher catcher = new ChildCatcher(); foreach.addStructuralListener(catcher); assertEquals(3, catcher.children.size()); assertEquals("Red", catcher.children.get(0).toString()); assertEquals("Blue", catcher.children.get(1).toString()); assertEquals("Green", catcher.children.get(2).toString()); String[] lines = console.getLines(); assertEquals(3, lines.length); assertEquals("I'm number 0 and my name is Red", lines[0].trim()); assertEquals("I'm number 1 and my name is Blue", lines[1].trim()); assertEquals("I'm number 2 and my name is Green", lines[2].trim()); oddjob.destroy(); } public void testPropertiesInChildren() { String config = "<foreach id='fe'>" + " <job>" + " <sequential>" + " <jobs>" + " <properties>" + " <values>" + " <value key='my.fruit' value='${fe.current}'/>" + " </values>" + " </properties>" + " <echo>${my.fruit}</echo>" + " </jobs>" + " </sequential>" + " </job>" + "</foreach>"; String xml = "<oddjob>" + " <job>" + " <foreach>" + " <values>" + " <list>" + " <values>" + " <value value='apple'/>" + " <value value='orange'/>" + " </values>" + " </list>" + " </values>" + " <configuration>" + " <value value='${config}'/>" + " </configuration>" + " </foreach>" + " </job>" + "</oddjob>"; Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration("XML", xml)); XMLConfigurationType configType = new XMLConfigurationType(); configType.setXml(config); oddjob.setExport("config", configType); ConsoleCapture console = new ConsoleCapture(); try (ConsoleCapture.Close close = console.captureConsole()) { oddjob.run(); } assertEquals(ParentState.COMPLETE, oddjob.lastStateEvent().getState()); console.dump(logger); String[] lines = console.getLines(); assertEquals(2, lines.length); assertEquals("apple", lines[0].trim()); assertEquals("orange", lines[1].trim()); oddjob.destroy(); } public void testStop() throws InterruptedException { String xml = "<foreach id='each'>" + " <job>" + " <wait name='Wait ${each.current}'/>" + " </job>" + "</foreach>"; final ForEachJob test = new ForEachJob(); test.setArooaSession(new OddjobSessionFactory().createSession()); test.setConfiguration(new XMLConfiguration("XML", xml)); test.setValues(Arrays.asList("apple", "orange")); test.addStructuralListener(new StructuralListener() { @Override public void childRemoved(StructuralEvent event) { } @Override public void childAdded(StructuralEvent event) { Stateful child = (Stateful) event.getChild(); child.addStateListener(new StateListener() { @Override public void jobStateChange(StateEvent event) { if (event.getState().isStoppable()) { new Thread(new Runnable() { @Override public void run() { try { test.stop(); } catch (FailedToStopException e) { throw new RuntimeException(e); } } }).start(); } } }); } }); StateSteps testStates = new StateSteps(test); testStates.startCheck(ParentState.READY, ParentState.EXECUTING, ParentState.READY); IconSteps iconSteps = new IconSteps(test); iconSteps.startCheck(IconHelper.READY, IconHelper.EXECUTING, IconHelper.STOPPING, IconHelper.READY); test.run(); testStates.checkNow(); iconSteps.checkNow(); Object[] children = OddjobTestHelper.getChildren(test); assertEquals(2, children.length); assertEquals(JobState.COMPLETE, OddjobTestHelper.getJobState(children[0])); assertEquals(JobState.READY, OddjobTestHelper.getJobState(children[1])); } /** * Tracking down a bug where execution services weren't getting passed in to the * internal configuration. */ public void testAutoInject() { String forEachConfig = "<foreach id='test'>" + " <job>" + " <state:join xmlns:state='http://rgordon.co.uk/oddjob/state'>" + " <job>" + " <parallel>" + " <jobs>" + " <echo>Hello</echo>" + " </jobs>" + " </parallel>" + " </job>" + " </state:join>" + " </job>" + "</foreach>"; String ojConfig = "<oddjob xmlns:arooa='http://rgordon.co.uk/oddjob/arooa'>" + " <job>" + " <foreach>" + " <values>" + " <list>" + " <values>" + " <value value='1'/>" + " <value value='2'/>" + " </values>" + " </list>" + " </values>" + " <configuration>" + " <arooa:configuration>" + " <xml>" + " <xml>" + forEachConfig + "</xml>" + " </xml>" + " </arooa:configuration>" + " </configuration>" + " </foreach>" + " </job>" + "</oddjob>"; Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration("XML" , ojConfig)); ConsoleCapture console = new ConsoleCapture(); try (ConsoleCapture.Close close = console.captureConsole()) { oddjob.run(); } String[] lines = console.getLines(); assertEquals(2, lines.length); oddjob.destroy(); } private static class RunNowExecutor implements Executor { @Override public void execute(Runnable command) { command.run(); } } public static class SlowToDestroyJob extends SimpleJob { @Override protected int execute() throws Throwable { return 0; } @Override protected void onDestroy() { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } // Make sure the bug fix doesn't leave Old Jobs lying around private static class JobCounter implements StructuralListener { private Set<Object> jobs = new HashSet<Object>(); @Override public void childAdded(StructuralEvent event) { Object child = event.getChild(); jobs.add(child); if (child instanceof Structural) { ((Structural) child).addStructuralListener(this); } } @Override public void childRemoved(StructuralEvent event) { Object child = event.getChild(); jobs.remove(child); } } // Ditto the JobTreeModel. private static class NodeCounter implements TreeModelListener { private Set<Object> jobs = new HashSet<Object>(); @Override public void treeNodesChanged(TreeModelEvent e) { // Don't care about Icon notifications. } @Override public void treeStructureChanged(TreeModelEvent e) { throw new RuntimeException("Unexpected!"); } @Override public void treeNodesInserted(TreeModelEvent e) { assertEquals(1, e.getChildren().length); jobs.add(e.getChildren()[0]); } @Override public void treeNodesRemoved(TreeModelEvent e) { assertEquals(1, e.getChildren().length); jobs.remove(e.getChildren()[0]); } } /** * Tracking down a bug where complicated structures cause an exception in explorer. * @throws InvocationTargetException * @throws InterruptedException */ public void testDestroyWithComplicateStructure() throws InterruptedException, InvocationTargetException { String forEachConfig = "<foreach id='test'>" + " <job>" + " <sequential>" + " <jobs>" + " <bean class='" + SlowToDestroyJob.class.getName() + "'/>" + " <bean class='" + SlowToDestroyJob.class.getName() + "'/>" + " <bean class='" + SlowToDestroyJob.class.getName() + "'/>" + " <bean class='" + SlowToDestroyJob.class.getName() + "'/>" + " <bean class='" + SlowToDestroyJob.class.getName() + "'/>" + " </jobs>" + " </sequential>" + " </job>" + "</foreach>"; String ojConfig = "<oddjob xmlns:arooa='http://rgordon.co.uk/oddjob/arooa'>" + " <job>" + " <foreach id='foreach'>" + " <values>" + " <list>" + " <values>" + " <value value='1'/>" + " <value value='2'/>" + " </values>" + " </list>" + " </values>" + " <configuration>" + " <arooa:configuration>" + " <xml>" + " <xml>" + forEachConfig + "</xml>" + " </xml>" + " </arooa:configuration>" + " </configuration>" + " </foreach>" + " </job>" + "</oddjob>"; final Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration("XML" , ojConfig)); JobCounter jobCounter = new JobCounter(); oddjob.addStructuralListener(jobCounter); JobTreeModel model = new JobTreeModel(new RunNowExecutor()); NodeCounter nodeCounter = new NodeCounter(); model.addTreeModelListener(nodeCounter); JobTreeNode root = new JobTreeNode( new MockExplorerModel() { @Override public Oddjob getOddjob() { return oddjob; } }, model, new EventThreadLaterExecutor(), new ExplorerContextFactory() { @Override public ExplorerContext createFrom(ExplorerModel explorerModel) { return new MockExplorerContext() { @Override public ExplorerContext addChild(Object child) { return this; } }; } }); root.setVisible(true); oddjob.run(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { // Clear out queue. } }); assertEquals(13, jobCounter.jobs.size()); assertEquals(13, nodeCounter.jobs.size()); oddjob.destroy(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { // Clear out queue. } }); assertEquals(0, jobCounter.jobs.size()); assertEquals(0, nodeCounter.jobs.size()); } public static class EvenNumberHater extends SimpleJob { private int number; private boolean failedOnce; @Override protected int execute() throws Throwable { if (number % 2 == 0 && !failedOnce) { failedOnce = true; return 1; } System.out.println(number); return 0; } public void setNumber(int number) { this.number = number; } @Override public String toString() { return getClass().getSimpleName() + " " + number; } } /** * Fixing a problem where resetting after a failure would cause * execution to start after the failure, not to re-run it. */ public void testRetryRetriesFromCorrectValue() { ChildCatcher childs = new ChildCatcher(); String xml = "<foreach id='loop'>" + " <job>" + " <bean class='" + EvenNumberHater.class.getName() + "' number='${loop.current}'/>" + " </job>" + "</foreach>"; ForEachJob test = new ForEachJob(); test.setArooaSession(new StandardArooaSession()); test.setConfiguration(new XMLConfiguration("XML", xml)); test.setValues(Arrays.asList(8, 9, 10)); test.addStructuralListener(childs); ConsoleCapture console = new ConsoleCapture(); try (ConsoleCapture.Close close = console.captureConsole()) { test.run(); Stateful child1 = (Stateful) childs.children.get(0); Stateful child2 = (Stateful) childs.children.get(1); Stateful child3 = (Stateful) childs.children.get(2); assertEquals(JobState.INCOMPLETE, child1.lastStateEvent().getState()); assertEquals(JobState.READY, child2.lastStateEvent().getState()); assertEquals(JobState.READY, child3.lastStateEvent().getState()); assertEquals(ParentState.INCOMPLETE, test.lastStateEvent().getState()); test.softReset(); assertEquals(JobState.READY, child1.lastStateEvent().getState()); assertEquals(JobState.READY, child2.lastStateEvent().getState()); assertEquals(JobState.READY, child3.lastStateEvent().getState()); assertEquals(ParentState.READY, test.lastStateEvent().getState()); test.run(); assertEquals(JobState.COMPLETE, child1.lastStateEvent().getState()); assertEquals(JobState.COMPLETE, child2.lastStateEvent().getState()); assertEquals(JobState.INCOMPLETE, child3.lastStateEvent().getState()); assertEquals(ParentState.INCOMPLETE, test.lastStateEvent().getState()); test.softReset(); assertEquals(JobState.COMPLETE, child1.lastStateEvent().getState()); assertEquals(JobState.COMPLETE, child2.lastStateEvent().getState()); assertEquals(JobState.READY, child3.lastStateEvent().getState()); assertEquals(ParentState.READY, test.lastStateEvent().getState()); test.run(); assertEquals(JobState.COMPLETE, child1.lastStateEvent().getState()); assertEquals(JobState.COMPLETE, child2.lastStateEvent().getState()); assertEquals(JobState.COMPLETE, child3.lastStateEvent().getState()); assertEquals(ParentState.COMPLETE, test.lastStateEvent().getState()); } console.dump(logger); String[] lines = console.getLines(); assertEquals(3, lines.length); assertEquals("8", lines[0].trim()); assertEquals("9", lines[1].trim()); assertEquals("10", lines[2].trim()); test.destroy(); } }