Coverage Report - org.jbehave.core.reporters.PrintStreamOutput
 
Classes in this File Line Coverage Branch Coverage Complexity
PrintStreamOutput
100%
125/125
79%
27/34
1.676
PrintStreamOutput$1
100%
6/6
80%
4/5
1.676
PrintStreamOutput$2
100%
1/1
N/A
1.676
PrintStreamOutput$Format
100%
1/1
N/A
1.676
 
 1  
 package org.jbehave.core.reporters;
 2  
 
 3  
 import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
 4  
 import static org.apache.commons.lang.StringEscapeUtils.escapeXml;
 5  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_END;
 6  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_NEWLINE;
 7  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_START;
 8  
 
 9  
 import java.io.ByteArrayOutputStream;
 10  
 import java.io.PrintStream;
 11  
 import java.text.MessageFormat;
 12  
 import java.util.Arrays;
 13  
 import java.util.List;
 14  
 import java.util.Locale;
 15  
 import java.util.Map;
 16  
 import java.util.Properties;
 17  
 
 18  
 import org.apache.commons.collections.CollectionUtils;
 19  
 import org.apache.commons.collections.Transformer;
 20  
 import org.apache.commons.lang.ArrayUtils;
 21  
 import org.apache.commons.lang.StringUtils;
 22  
 import org.apache.commons.lang.builder.ToStringBuilder;
 23  
 import org.apache.commons.lang.builder.ToStringStyle;
 24  
 import org.jbehave.core.configuration.Keywords;
 25  
 import org.jbehave.core.model.ExamplesTable;
 26  
 import org.jbehave.core.model.GivenStories;
 27  
 import org.jbehave.core.model.GivenStory;
 28  
 import org.jbehave.core.model.Meta;
 29  
 import org.jbehave.core.model.Narrative;
 30  
 import org.jbehave.core.model.OutcomesTable;
 31  
 import org.jbehave.core.model.OutcomesTable.Outcome;
 32  
 import org.jbehave.core.model.Scenario;
 33  
 import org.jbehave.core.model.Story;
 34  
 
 35  
 /**
 36  
  * <p>
 37  
  * Abstract story reporter that outputs to a PrintStream.
 38  
  * </p>
 39  
  * <p>
 40  
  * The output of the reported event is configurable via:
 41  
  * <ul>
 42  
  * <li>custom output patterns, providing only the patterns that differ from
 43  
  * default</li>
 44  
  * <li>keywords localised for different languages, providing the i18n Locale</li>
 45  
  * <li>flag to report failure trace</li>
 46  
  * </ul>
 47  
  * </p>
 48  
  * <p>
 49  
  * Let's look at example of providing custom output patterns, e.g. for the
 50  
  * failed event. <br/>
 51  
  * we'd need to provide the custom pattern, say we want to have something like
 52  
  * "(step being executed) <<< FAILED", keyed on the method name:
 53  
  * 
 54  
  * <pre>
 55  
  * Properties patterns = new Properties();
 56  
  * patterns.setProperty(&quot;failed&quot;, &quot;{0} &lt;&lt;&lt; {1}&quot;);
 57  
  * </pre>
 58  
  * 
 59  
  * The pattern is by default processed and formatted by the
 60  
  * {@link MessageFormat}. Both the {@link #format(String key, String defaultPattern, Object... args)} and
 61  
  * {@link #lookupPattern(String key, String defaultPattern)} methods are override-able and a different formatter
 62  
  * or pattern lookup can be used by subclasses.
 63  
  * </p>
 64  
  * <p>
 65  
  * If the keyword "FAILED" (or any other keyword used by the reporter) needs to
 66  
  * be expressed in a different language, all we need to do is to provide an
 67  
  * instance of {@link org.jbehave.core.i18n.LocalizedKeywords} using the appropriate {@link Locale}, e.g.
 68  
  * 
 69  
  * <pre>
 70  
  * Keywords keywords = new LocalizedKeywords(new Locale(&quot;it&quot;));
 71  
  * </pre>
 72  
  * 
 73  
  * </p>
 74  
  */
 75  
 public abstract class PrintStreamOutput implements StoryReporter {
 76  
 
 77  
     private static final String EMPTY = "";
 78  
 
 79  5
     public enum Format { TXT, HTML, XML }
 80  
     
 81  
     private final Format format;    
 82  
     private final PrintStream output;
 83  
     private final Properties outputPatterns;
 84  
     private final Keywords keywords;
 85  
     private boolean reportFailureTrace;
 86  
     private Throwable cause;
 87  
     
 88  
     protected PrintStreamOutput(Format format, PrintStream output, Properties outputPatterns,
 89  439
             Keywords keywords, boolean reportFailureTrace) {
 90  439
         this.format = format;
 91  439
         this.output = output;
 92  439
         this.outputPatterns = outputPatterns;
 93  439
         this.keywords = keywords;
 94  439
         this.reportFailureTrace = reportFailureTrace;   
 95  439
     }
 96  
 
 97  
     public void successful(String step) {
 98  44
         print(format("successful", "{0}\n", step));
 99  44
     }
 100  
 
 101  
     public void ignorable(String step) {
 102  12
         print(format("ignorable", "{0}\n", step));
 103  12
     }
 104  
 
 105  
     public void pending(String step) {
 106  16
         print(format("pending", "{0} ({1})\n", step, keywords.pending()));
 107  16
     }
 108  
 
 109  
     public void notPerformed(String step) {
 110  16
         print(format("notPerformed", "{0} ({1})\n", step, keywords.notPerformed()));
 111  16
     }
 112  
 
 113  
     public void failed(String step, Throwable cause) {
 114  17
         this.cause = cause;
 115  17
         print(format("failed", "{0} ({1})\n({2})\n", step, keywords.failed(), cause));        
 116  17
     }
 117  
 
 118  
     public void failedOutcomes(String step, OutcomesTable table) {
 119  12
             failed(step, table.failureCause());
 120  12
         print(table);
 121  12
     }
 122  
     
 123  
         private void print(OutcomesTable table) {
 124  12
                 print(format("outcomesTableStart", "\n"));
 125  12
         List<Outcome<?>> rows = table.getOutcomes();
 126  12
         print(format("outcomesTableHeadStart", "|"));
 127  
         //TODO i18n outcome fields
 128  12
         for (String field : table.getOutcomeFields()) {
 129  48
             print(format("outcomesTableHeadCell", "{0}|", field));
 130  
         }
 131  12
         print(format("outcomesTableHeadEnd", "\n"));
 132  12
         print(format("outcomesTableBodyStart", EMPTY));
 133  12
         for (Outcome<?> outcome : rows) {
 134  12
             print(format("outcomesTableRowStart", "|", outcome.isVerified()?"verified":"notVerified"));
 135  12
             print(format("outcomesTableCell", "{0}|", outcome.getDescription()));
 136  12
             print(format("outcomesTableCell", "{0}|", outcome.getValue()));
 137  12
             print(format("outcomesTableCell", "{0}|", outcome.getMatcher()));
 138  12
             print(format("outcomesTableCell", "{0}|", outcome.isVerified()));
 139  12
             print(format("outcomesTableRowEnd", "\n"));
 140  
         }
 141  12
         print(format("outcomesTableBodyEnd", "\n"));
 142  12
         print(format("outcomesTableEnd", "\n"));
 143  12
         }
 144  
 
 145  
     public void storyNotAllowed(Story story, String filter) {
 146  3
         print(format("beforeStory", "{0}\n({1})\n", story.getDescription().asString(), story.getPath()));
 147  3
         if (!story.getMeta().isEmpty()) {
 148  3
             Meta meta = story.getMeta();
 149  3
             print(meta);
 150  
         }
 151  3
         print(format("filter", "{0}\n", filter));
 152  3
     }
 153  
 
 154  
     public void beforeStory(Story story, boolean givenStory) {
 155  12
         print(format("beforeStory", "{0}\n({1})\n", story.getDescription().asString(), story.getPath()));
 156  12
         if (!story.getMeta().isEmpty()) {
 157  12
             Meta meta = story.getMeta();
 158  12
             print(meta);
 159  
         }
 160  12
         if (!story.getNarrative().isEmpty()) {
 161  12
             Narrative narrative = story.getNarrative();
 162  12
             print(format("narrative", "{0}\n{1} {2}\n{3} {4}\n{5} {6}\n", keywords.narrative(), keywords.inOrderTo(),
 163  
                     narrative.inOrderTo(), keywords.asA(), narrative.asA(), keywords.iWantTo(), narrative.iWantTo()));
 164  
         }
 165  12
     }
 166  
 
 167  
     private void print(Meta meta) {
 168  18
         print(format("metaStart", "{0}\n", keywords.meta()));
 169  18
         for (String name : meta.getPropertyNames() ){
 170  36
             print(format("metaProperty", "{0}{1} {2}", keywords.metaProperty(), name, meta.getProperty(name)));                
 171  
         }
 172  18
         print(format("metaEnd", "\n"));
 173  18
     }
 174  
 
 175  
     public void afterStory(boolean givenStory) {
 176  12
         print(format("afterStory", "\n"));
 177  12
     }
 178  
 
 179  
     public void givenStories(GivenStories givenStories) {
 180  12
         print(format("givenStoriesStart", "{0}\n", keywords.givenStories()));
 181  12
         for (GivenStory givenStory : givenStories.getStories()) {
 182  24
             print(format("givenStory", "{0} {1}\n", givenStory.asString(), (givenStory.hasAnchor() ? givenStory.getParameters() : "")));
 183  
         }
 184  12
         print(format("givenStoriesEnd", "\n"));
 185  12
     }
 186  
 
 187  
     public void givenStories(List<String> storyPaths) {
 188  12
         givenStories(new GivenStories(StringUtils.join(storyPaths, ",")));
 189  12
     }
 190  
 
 191  
     public void scenarioNotAllowed(Scenario scenario, String filter) {
 192  3
         print(format("beforeScenario", "{0} {1}\n", keywords.scenario(), scenario.getTitle()));
 193  3
         scenarioMeta(scenario.getMeta());
 194  3
         print(format("filter", "{0}\n", filter));
 195  3
     }
 196  
 
 197  
     public void beforeScenario(String title) {
 198  14
         cause = null;
 199  14
         print(format("beforeScenario", "{0} {1}\n", keywords.scenario(), title));
 200  14
     }
 201  
 
 202  
     public void scenarioMeta(Meta meta) {
 203  3
         if (!meta.isEmpty()) {
 204  3
             print(meta);
 205  
         }
 206  3
     }
 207  
 
 208  
     public void afterScenario() {
 209  14
         if (cause != null && reportFailureTrace) {
 210  3
             print(format("afterScenarioWithFailure", "\n{0}\n", stackTrace(cause)));
 211  
         } else {
 212  11
             print(format("afterScenario", "\n"));
 213  
         }
 214  14
     }
 215  
 
 216  
     private String stackTrace(Throwable cause) {
 217  3
         ByteArrayOutputStream out = new ByteArrayOutputStream();        
 218  3
         cause.printStackTrace(new PrintStream(out));
 219  3
         return out.toString();
 220  
     }
 221  
 
 222  
     public void beforeExamples(List<String> steps, ExamplesTable table) {
 223  12
         print(format("beforeExamples", "{0}\n", keywords.examplesTable()));
 224  12
         for (String step : steps) {
 225  24
             print(format("examplesStep", "{0}\n", step));
 226  
         }
 227  12
         print(table);
 228  12
     }
 229  
 
 230  
         private void print(ExamplesTable table) {
 231  12
                 print(format("examplesTableStart", "\n"));
 232  12
         List<Map<String, String>> rows = table.getRows();
 233  12
         List<String> headers = table.getHeaders();
 234  12
         print(format("examplesTableHeadStart", "|"));
 235  12
         for (String header : headers) {
 236  24
             print(format("examplesTableHeadCell", "{0}|", header));
 237  
         }
 238  12
         print(format("examplesTableHeadEnd", "\n"));
 239  12
         print(format("examplesTableBodyStart", EMPTY));
 240  12
         for (Map<String, String> row : rows) {
 241  24
             print(format("examplesTableRowStart", "|"));
 242  24
             for (String header : headers) {
 243  48
                 print(format("examplesTableCell", "{0}|", row.get(header)));
 244  
             }
 245  24
             print(format("examplesTableRowEnd", "\n"));
 246  
         }
 247  12
         print(format("examplesTableBodyEnd", "\n"));
 248  12
         print(format("examplesTableEnd", "\n"));
 249  12
         }
 250  
 
 251  
     public void example(Map<String, String> tableRow) {
 252  24
         print(format("example", "\n{0} {1}\n", keywords.examplesTableRow(), tableRow));
 253  24
     }
 254  
 
 255  
     public void afterExamples() {
 256  12
         print(format("afterExamples", "\n"));
 257  12
     }
 258  
 
 259  
         public void dryRun() {
 260  12
                 print(format("dryRun", "{0}\n", keywords.dryRun()));
 261  12
         }
 262  
 
 263  
     /**
 264  
      * Formats event output by key, usually equal to the method name.
 265  
      * 
 266  
      * @param key the event key
 267  
      * @param defaultPattern the default pattern to return if a custom pattern
 268  
      *            is not found
 269  
      * @param args the args used to format output
 270  
      * @return A formatted event output
 271  
      */
 272  
     protected String format(String key, String defaultPattern, Object... args) {
 273  5383
         return MessageFormat.format(lookupPattern(key, escape(defaultPattern)), escapeAll(args));
 274  
     }
 275  
 
 276  
     private String escape(String defaultPattern) {
 277  5383
         return (String) escapeAll(defaultPattern)[0];
 278  
     }
 279  
 
 280  
     private Object[] escapeAll(Object... args) {
 281  10766
         return escape(format, args);
 282  
     }
 283  
 
 284  
     /**
 285  
      * Escapes args' string values according to format
 286  
      * 
 287  
      * @param format the Format used by the PrintStream
 288  
      * @param args the array of args to escape
 289  
      * @return The cloned and escaped array of args
 290  
      */
 291  
     protected Object[] escape(final Format format, Object... args) {
 292  
         // Transformer that escapes HTML and XML strings
 293  10766
         Transformer escapingTransformer = new Transformer( ) {
 294  
             public Object transform(Object object) {
 295  6173
                 switch ( format ){
 296  1984
                     case HTML: return escapeHtml(asString(object));
 297  574
                     case XML: return escapeXml(asString(object));
 298  3615
                     default: return object;
 299  
                 }
 300  
             }
 301  
 
 302  
             private String asString(Object object) {
 303  2558
                 return  ( object != null ? object.toString() : EMPTY );
 304  
             }
 305  
         };
 306  10766
         List<?> list = Arrays.asList( ArrayUtils.clone( args ) );
 307  10766
         CollectionUtils.transform( list, escapingTransformer );
 308  10766
         return list.toArray();
 309  
     }
 310  
 
 311  
     /**
 312  
      * Looks up the format pattern for the event output by key, conventionally
 313  
      * equal to the method name. The pattern is used by the
 314  
      * {#format(String,String,Object...)} method and by default is formatted
 315  
      * using the {@link MessageFormat#format(String, Object...)} method. If no pattern is found
 316  
      * for key or needs to be overridden, the default pattern should be
 317  
      * returned.
 318  
      * 
 319  
      * @param key the format pattern key
 320  
      * @param defaultPattern the default pattern if no pattern is
 321  
      * @return The format patter for the given key
 322  
      */
 323  
     protected String lookupPattern(String key, String defaultPattern) {
 324  5383
         if (outputPatterns.containsKey(key)) {
 325  3076
             return outputPatterns.getProperty(key);
 326  
         }
 327  2307
         return defaultPattern;
 328  
     }
 329  
 
 330  
     public PrintStreamOutput doReportFailureTrace(boolean reportFailureTrace){
 331  13
             this.reportFailureTrace = reportFailureTrace;
 332  13
             return this;
 333  
     }
 334  
     
 335  
     /**
 336  
      * Prints text to output stream, replacing parameter start and end placeholders
 337  
      * 
 338  
      * @param text the String to print
 339  
      */
 340  
     protected void print(String text) {
 341  769
         output.print(text.replace(format(PARAMETER_VALUE_START, PARAMETER_VALUE_START), format("parameterValueStart", EMPTY))
 342  
                          .replace(format(PARAMETER_VALUE_END, PARAMETER_VALUE_END), format("parameterValueEnd", EMPTY))
 343  
                          .replace(format(PARAMETER_VALUE_NEWLINE, PARAMETER_VALUE_NEWLINE), format("parameterValueNewline", "\n")));
 344  769
     }
 345  
     
 346  
         @Override
 347  
         public String toString() {
 348  1
                 return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
 349  
         }
 350  
 
 351  
 }