package org.jbehave.core.embedder; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.jbehave.core.io.CodeLocations.codeLocationFromClass; import static org.jbehave.core.reporters.Format.CONSOLE; import static org.jbehave.core.reporters.Format.HTML; import static org.jbehave.core.reporters.Format.XML; import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.jbehave.core.annotations.Given; import org.jbehave.core.annotations.When; import org.jbehave.core.configuration.Configuration; import org.jbehave.core.configuration.MostUsefulConfiguration; import org.jbehave.core.embedder.Embedder.RunningEmbeddablesFailed; import org.jbehave.core.embedder.StoryManager.StoryExecutionFailed; import org.jbehave.core.embedder.StoryManager.StoryTimedOut; import org.jbehave.core.embedder.StoryTimeouts.TimeoutFormatException; import org.jbehave.core.io.LoadFromClasspath; import org.jbehave.core.io.StoryFinder; import org.jbehave.core.io.StoryLoader; import org.jbehave.core.junit.JUnitStories; import org.jbehave.core.junit.JUnitStory; import org.jbehave.core.reporters.CrossReference; import org.jbehave.core.reporters.Format; import org.jbehave.core.reporters.StoryReporter; import org.jbehave.core.reporters.StoryReporterBuilder; import org.jbehave.core.reporters.TxtOutput; import org.jbehave.core.reporters.XmlOutput; import org.jbehave.core.steps.InjectableStepsFactory; import org.jbehave.core.steps.InstanceStepsFactory; import org.junit.Test; public class ConcurrencyBehaviour { @Test public void shouldCompleteXmlReportWhenStoryIsCancelled() { Embedder embedder = new Embedder(); embedder.embedderControls().useStoryTimeouts("1"); try { embedder.runAsEmbeddables(asList(XmlFormat.class.getName())); fail("Exception was not thrown"); } catch (RunningEmbeddablesFailed e) { String xmlOutput = XmlFormat.out.toString(); assertThat(xmlOutput, containsString("</scenario>")); assertThat(xmlOutput, containsString("</story>")); } } @Test(expected = RunningEmbeddablesFailed.class) public void shouldAllowStoriesToBeCancelled() { Embedder embedder = new Embedder(); embedder.embedderControls().useStoryTimeouts("1"); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); } @Test(expected = RunningEmbeddablesFailed.class) public void shouldAllowStoriesToBeCancelledByPaths() { Embedder embedder = new Embedder(); embedder.embedderControls().useStoryTimeouts("**/*.story:1"); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); } @Test public void shouldAllowStoriesToBeTimed() { Embedder embedder = new Embedder(); embedder.embedderControls().useStoryTimeouts("10").useThreads(2).doVerboseFailures(true); try { embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); } finally { embedder.generateCrossReference(); } } @Test public void shouldFailOnTimeout() { Embedder embedder = new Embedder(); embedder.embedderControls().useStoryTimeouts("1") .doFailOnStoryTimeout(true); try { embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); fail("Exception was not thrown"); } catch (RunningEmbeddablesFailed e) { assertThat(e.getCause(), instanceOf(StoryExecutionFailed.class)); assertThat(e.getCause().getCause(), instanceOf(StoryTimedOut.class)); } } @Test public void shouldFailOnTimeoutWhenSpecifiedByPath() { Embedder embedder = new Embedder(); embedder.embedderControls().useStoryTimeouts("**/*.story:1").doFailOnStoryTimeout(true); try { embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); fail("Exception was not thrown"); } catch (RunningEmbeddablesFailed e) { assertThat(e.getCause(), instanceOf(StoryExecutionFailed.class)); assertThat(e.getCause().getCause(), instanceOf(StoryTimedOut.class)); } } @Test public void shouldUseDefaultTimeoutWhenNoTimeoutsAreSpecified() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); ThreadsStories.setCustomStoryPath("**/a_short.story"); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); assertThat(out.toString(), containsString("Using timeout for story a_short.story of 300")); ThreadsStories.setCustomStoryPath(null); } @Test public void shouldUseTimeoutByPathIfSpecifiedOrDefaultOtherwise() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); try { embedder.embedderControls().useStoryTimeouts("**/another_long.story:1").doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); fail("Exception was not thrown"); } catch (RunningEmbeddablesFailed e) { assertThat(out.toString(), containsString("Using timeout for story a_short.story of 300")); assertThat(out.toString(), containsString("Using timeout for story a_long.story of 300")); assertThat(out.toString(), containsString("Using timeout for story another_long.story of 1")); } } @Test public void shouldUseTheTimeoutTypeSpecified() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); try { embedder.embedderControls().useStoryTimeouts("10,**/a_short.story:1,**/another_long.story:2").doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); fail("Exception was not thrown"); } catch (RunningEmbeddablesFailed e) { assertThat(out.toString(), containsString("Using timeout for story a_short.story of 1")); assertThat(out.toString(), containsString("Using timeout for story a_long.story of 10")); assertThat(out.toString(), containsString("Using timeout for story another_long.story of 2")); } } @Test public void shouldHandleBothTimeoutTypesSpecifiedAsTheSame() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); ThreadsStories.setCustomStoryPath("**/a_short.story"); embedder.embedderControls().useStoryTimeouts("7,**/a_short.story:7").doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); assertThat(out.toString(), containsString("Using timeout for story a_short.story of 7")); ThreadsStories.setCustomStoryPath(null); } @Test public void shouldAllowTimeoutByPathToBeZeroMeaningNoTimeLimit() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); ThreadsStories.setCustomStoryPath("**/another_long.story"); embedder.embedderControls().useStoryTimeouts("**/another_long.story:0").doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); assertThat(out.toString(), containsString("Using timeout for story another_long.story of 0")); ThreadsStories.setCustomStoryPath(null); } @Test public void shouldUseDefaultTimeoutWhenTimeoutByPathIsEmpty() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); ThreadsStories.setCustomStoryPath("**/a_short.story"); embedder.embedderControls().useStoryTimeouts("").doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); assertThat(out.toString(), containsString("Using timeout for story a_short.story of 300")); ThreadsStories.setCustomStoryPath(null); } @Test public void shouldAllowMultipleTimeoutsByPathToBeSpecified() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); // Note: **/another_long_TEST.story is an invalid path and therefore won't be considered String timeoutsByPath = "**/a_short.story:10,**/a_long.story:15,**/another_long_TEST.story:77"; embedder.embedderControls().useStoryTimeouts(timeoutsByPath).doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); assertThat(out.toString(), containsString("Using timeout for story a_short.story of 10")); assertThat(out.toString(), containsString("Using timeout for story a_long.story of 15")); assertThat(out.toString(), containsString("Using timeout for story another_long.story of 300")); } @Test public void shouldNotAllowTimeoutByPathToBeInvalidFormats() { assertThatStoryTimeoutIsInvalid("**/a_long.story:adhgaldsh"); assertThatStoryTimeoutIsInvalid("**/a_long.story: "); } private void assertThatStoryTimeoutIsInvalid(String storyTimeouts) { try { Embedder embedder = new Embedder(); embedder.embedderControls().useStoryTimeouts(storyTimeouts) .doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); fail("Exception was not thrown"); } catch (RunningEmbeddablesFailed e) { assertThat(e.getCause(), instanceOf(TimeoutFormatException.class)); } } @Test public void shouldHandleRepeatedTimeoutsByPath() { OutputStream out = new ByteArrayOutputStream(); Embedder embedder = new Embedder(embedderMonitor(out)); ThreadsStories.setCustomStoryPath("**/a_short.story"); embedder.embedderControls().useStoryTimeouts("**/a_short.story:25,**/a_short.story:25").doFailOnStoryTimeout(true); embedder.runAsEmbeddables(asList(ThreadsStories.class.getName())); assertThat(out.toString(), containsString("Using timeout for story a_short.story of 25")); ThreadsStories.setCustomStoryPath(null); } private EmbedderMonitor embedderMonitor(OutputStream out) { return new PrintStreamEmbedderMonitor(new PrintStream(out)); } public static class ThreadsStories extends JUnitStories { /* Since some Unit Tests require stories to run to completion, 'customStoryPath' can be used to run specific tests and limit test running time */ private static String customStoryPath = null; @Override public Configuration configuration() { return new MostUsefulConfiguration().useStoryLoader( new LoadFromClasspath(this.getClass())) .useStoryReporterBuilder( new StoryReporterBuilder().withFormats(CONSOLE, HTML, XML).withCrossReference(new CrossReference())); } @Override public InjectableStepsFactory stepsFactory() { return new InstanceStepsFactory(configuration(), new ThreadsSteps()); } public static void setCustomStoryPath(String customStoryPath) { ThreadsStories.customStoryPath = customStoryPath; } @Override protected List<String> storyPaths() { if(customStoryPath != null) { return new StoryFinder().findPaths( codeLocationFromClass(this.getClass()), customStoryPath, ""); } else { return new StoryFinder().findPaths( codeLocationFromClass(this.getClass()), "**/*.story", ""); } } } public static class ThreadsSteps { @When("$name counts to $n Mississippi") public void whenSomeoneCountsMississippis(String name, AtomicInteger n) throws InterruptedException { long start = System.currentTimeMillis(); System.out.println(name + " starts counting to " + n); for (int i = 0; i < n.intValue(); i++) { System.out.println(name + " says " + i + " Mississippi (" + (System.currentTimeMillis() - start) + " millis)"); TimeUnit.SECONDS.sleep(1); } } } public static class XmlFormat extends JUnitStory { static ByteArrayOutputStream out = new ByteArrayOutputStream(); final PrintStream printStream = new PrintStream(out); public XmlFormat() { Embedder embeeder = configuredEmbedder(); embeeder.embedderControls().useStoryTimeouts("1"); } @Override public Configuration configuration() { final XmlOutput xmlOutput = new XmlOutput(printStream); return new MostUsefulConfiguration().useStoryLoader(new MyStoryLoader()).useStoryReporterBuilder( new StoryReporterBuilder() { @Override public StoryReporter build(String storyPath) { if (storyPath.contains("format")) { return xmlOutput; } else { return new TxtOutput(new PrintStream(new ByteArrayOutputStream())); } } }.withFormats(Format.XML)); } @Override public InjectableStepsFactory stepsFactory() { return new InstanceStepsFactory(this.configuration(), this); } @Given("something too long") public void somethingLong() throws Exception { Thread.sleep(5000L); } public static class MyStoryLoader implements StoryLoader { public String loadStoryAsText(String storyPath) { return "Scenario: \nGiven something too long"; } public String loadResourceAsText(String resourcePath) { return null; } } } }