JBehave
  1. JBehave
  2. JBEHAVE-911

XML format is broken when story is cancelled

    Details

    • Type: Bug Bug
    • Status: Resolved Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 3.8
    • Fix Version/s: 3.9
    • Component/s: Core
    • Labels:
      None
    • Number of attachments :
      0

      Description

      When the story is cancelled due to for example story timeout, XML reporter generates broken XML - just misses </scenario> end tag.

      Part of malformed report:

      <step outcome="successful">And the <parameter>Param</parameter> smth happened</step>
      </scenario>
      <scenario keyword="Scenario:" title="test Scenario1">
      <step outcome="successful">Given the user is on the index page</step>
      <step outcome="successful">And <parameter>1&apos;st</parameter> column width is <parameter>4U</parameter></step>
      <cancelled keyword="STORY CANCELLED" durationKeyword="DURATION" durationInSecs="301"/>
      </story>
      

        Activity

        Mauro Talevi made changes -
        Field Original Value New Value
        Fix Version/s 3.8.1 [ 19035 ]
        Hide
        Mauro Talevi added a comment -

        Can't seem to reproduce issue. Can you provide an example project that reproduces it?

        Show
        Mauro Talevi added a comment - Can't seem to reproduce issue. Can you provide an example project that reproduces it?
        Hide
        Yuriy Pilipenko added a comment -

        Sure. Will do in a day or two.

        Show
        Yuriy Pilipenko added a comment - Sure. Will do in a day or two.
        Hide
        Ghislain Nadeau added a comment - - edited

        Here's how to reproduce:

        JBehave911Test .java
        public class JBehave911Test extends JUnitStory{
        	private ByteArrayOutputStream output;
        	private PrintStream ps;
        	
        	public JBehave911Test(){
        		Embedder emb = configuredEmbedder();
        		emb.embedderControls().useStoryTimeoutInSecs(1L);
        		
        		output = new ByteArrayOutputStream();
        		ps = new PrintStream(output);
        	}
        	
        	@Override
            public Configuration configuration() {
        		
        		final XmlOutput output = new XmlOutput(ps);
        		
                return new MostUsefulConfiguration()
                    // where to find the stories
                    .useStoryLoader(new MyStoryLoader()) 
                    // CONSOLE and TXT reporting
                    .useStoryReporterBuilder(new StoryReporterBuilder(){
                    	@Override
                    	public StoryReporter build(String storyPath) {
                    		if(storyPath.contains("j_behave911_test.story")){
                    			return output;
                    		}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);
            }
            
            @AfterStories
            public void after() throws Exception{
            	String str = output.toString("UTF-8");
            	System.out.println();
            	System.out.println();
            	System.out.println(str);
            	System.out.println();
            	System.out.println();
            }
            
            public static class MyStoryLoader implements StoryLoader{
        
        		public String loadStoryAsText(String storyPath) {
        			return "Scenario: \n"
        					+ "Given something too long";
        		}
            	
            }
        }
        
        
        Show
        Ghislain Nadeau added a comment - - edited Here's how to reproduce: JBehave911Test .java public class JBehave911Test extends JUnitStory{ private ByteArrayOutputStream output; private PrintStream ps; public JBehave911Test(){ Embedder emb = configuredEmbedder(); emb.embedderControls().useStoryTimeoutInSecs(1L); output = new ByteArrayOutputStream(); ps = new PrintStream(output); } @Override public Configuration configuration() { final XmlOutput output = new XmlOutput(ps); return new MostUsefulConfiguration() // where to find the stories .useStoryLoader( new MyStoryLoader()) // CONSOLE and TXT reporting .useStoryReporterBuilder( new StoryReporterBuilder(){ @Override public StoryReporter build( String storyPath) { if (storyPath.contains( "j_behave911_test.story" )){ return output; } 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); } @AfterStories public void after() throws Exception{ String str = output.toString( "UTF-8" ); System .out.println(); System .out.println(); System .out.println(str); System .out.println(); System .out.println(); } public static class MyStoryLoader implements StoryLoader{ public String loadStoryAsText( String storyPath) { return "Scenario: \n" + "Given something too long " ; } } }
        Hide
        Ghislain Nadeau added a comment -

        There's 2 problem related to this:

        Once a cancellation has been detected, we print the calcellation tag, followed by the closing story tag, but since we were in a scenario, we must print thie close tag as well.
        I committed it in my repo.

        The second problem i more tricky...
        Once we reach the timeout, the main thread calls Future.cancel(true), on another thread which is running the story.
        The story running thread then throw an InterruptedException, which it catch itself to print the correct output.
        But, the main thread consider the running thread as DONE at the time it calls Future.cancel().
        This cause a race condition that may lead to the main thread terminating the JVM BEFORE the StoryRunner outputs its reports correctly.

        Note that an invalid .xml output may cause view generation to fail, and also other plugins that rely on the xml version of the report, such as the Jenkins plugin.

        Show
        Ghislain Nadeau added a comment - There's 2 problem related to this: Once a cancellation has been detected, we print the calcellation tag, followed by the closing story tag, but since we were in a scenario, we must print thie close tag as well. I committed it in my repo. The second problem i more tricky... Once we reach the timeout, the main thread calls Future.cancel(true), on another thread which is running the story. The story running thread then throw an InterruptedException, which it catch itself to print the correct output. But, the main thread consider the running thread as DONE at the time it calls Future.cancel(). This cause a race condition that may lead to the main thread terminating the JVM BEFORE the StoryRunner outputs its reports correctly. Note that an invalid .xml output may cause view generation to fail, and also other plugins that rely on the xml version of the report, such as the Jenkins plugin.
        Hide
        Mauro Talevi added a comment -

        Hi Ghislain,

        I added your proposed fix for reporting the end of the scenario. Also, transformed your error-reproducing story into a unit test (see ConcurrencyBehaviour).

        I do agree there is a potential race condition, but it's not possible/easy to fix in the current reporter architecture.

        The best way forward is to deprecate the StoryReporter and replace it with a rendering of a performable tree model object, as done in 4.x branch. There the rendering is done after the execution based on the state of the tree.

        Cheers

        Show
        Mauro Talevi added a comment - Hi Ghislain, I added your proposed fix for reporting the end of the scenario. Also, transformed your error-reproducing story into a unit test (see ConcurrencyBehaviour). I do agree there is a potential race condition, but it's not possible/easy to fix in the current reporter architecture. The best way forward is to deprecate the StoryReporter and replace it with a rendering of a performable tree model object, as done in 4.x branch. There the rendering is done after the execution based on the state of the tree. Cheers
        Mauro Talevi made changes -
        Status Open [ 1 ] Resolved [ 5 ]
        Resolution Fixed [ 1 ]

          People

          • Assignee:
            Unassigned
            Reporter:
            Yuriy Pilipenko
          • Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: