Coverage Report - org.jbehave.core.reporters.PrintStreamOutput
 
Classes in this File Line Coverage Branch Coverage Complexity
PrintStreamOutput
79%
170/213
65%
45/69
1.979
PrintStreamOutput$1
100%
6/6
80%
4/5
1.979
PrintStreamOutput$2
100%
2/2
N/A
1.979
PrintStreamOutput$Format
100%
2/2
N/A
1.979
 
 1  
 package org.jbehave.core.reporters;
 2  
 
 3  
 import java.io.ByteArrayOutputStream;
 4  
 import java.io.OutputStream;
 5  
 import java.io.PrintStream;
 6  
 import java.text.MessageFormat;
 7  
 import java.text.SimpleDateFormat;
 8  
 import java.util.Arrays;
 9  
 import java.util.Date;
 10  
 import java.util.List;
 11  
 import java.util.Locale;
 12  
 import java.util.Map;
 13  
 import java.util.Properties;
 14  
 
 15  
 import org.apache.commons.collections.CollectionUtils;
 16  
 import org.apache.commons.collections.Transformer;
 17  
 import org.apache.commons.lang.ArrayUtils;
 18  
 import org.apache.commons.lang.StringUtils;
 19  
 import org.apache.commons.lang.builder.ToStringBuilder;
 20  
 import org.apache.commons.lang.builder.ToStringStyle;
 21  
 import org.jbehave.core.configuration.Keywords;
 22  
 import org.jbehave.core.failures.KnownFailure;
 23  
 import org.jbehave.core.failures.UUIDExceptionWrapper;
 24  
 import org.jbehave.core.model.ExamplesTable;
 25  
 import org.jbehave.core.model.GivenStories;
 26  
 import org.jbehave.core.model.GivenStory;
 27  
 import org.jbehave.core.model.Lifecycle;
 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  
 import org.jbehave.core.model.StoryDuration;
 35  
 
 36  
 import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
 37  
 import static org.apache.commons.lang.StringEscapeUtils.escapeXml;
 38  
 import static org.apache.commons.lang.StringUtils.substringBetween;
 39  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_TABLE_END;
 40  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_TABLE_START;
 41  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_END;
 42  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_NEWLINE;
 43  
 import static org.jbehave.core.steps.StepCreator.PARAMETER_VALUE_START;
 44  
 
 45  
 /**
 46  
  * <p>
 47  
  * Abstract story reporter that outputs to a PrintStream.
 48  
  * </p>
 49  
  * <p>
 50  
  * The output of the reported event is configurable via:
 51  
  * <ul>
 52  
  * <li>custom output patterns, providing only the patterns that differ from
 53  
  * default</li>
 54  
  * <li>keywords localised for different languages, providing the i18n Locale</li>
 55  
  * <li>flag to report failure trace</li>
 56  
  * </ul>
 57  
  * </p>
 58  
  * <p>
 59  
  * Let's look at example of providing custom output patterns, e.g. for the
 60  
  * failed event. <br/>
 61  
  * we'd need to provide the custom pattern, say we want to have something like
 62  
  * "(step being executed) <<< FAILED", keyed on the method name:
 63  
  * 
 64  
  * <pre>
 65  
  * Properties patterns = new Properties();
 66  
  * patterns.setProperty(&quot;failed&quot;, &quot;{0} &lt;&lt;&lt; {1}&quot;);
 67  
  * </pre>
 68  
  * 
 69  
  * The pattern is by default processed and formatted by the
 70  
  * {@link MessageFormat}. Both the
 71  
  * {@link #format(String key, String defaultPattern, Object... args)} and
 72  
  * {@link #lookupPattern(String key, String defaultPattern)} methods are
 73  
  * override-able and a different formatter or pattern lookup can be used by
 74  
  * subclasses.
 75  
  * </p>
 76  
  * <p>
 77  
  * If the keyword "FAILED" (or any other keyword used by the reporter) needs to
 78  
  * be expressed in a different language, all we need to do is to provide an
 79  
  * instance of {@link org.jbehave.core.i18n.LocalizedKeywords} using the
 80  
  * appropriate {@link Locale}, e.g.
 81  
  * 
 82  
  * <pre>
 83  
  * Keywords keywords = new LocalizedKeywords(new Locale(&quot;it&quot;));
 84  
  * </pre>
 85  
  * 
 86  
  * </p>
 87  
  */
 88  
 public abstract class PrintStreamOutput implements StoryReporter {
 89  
 
 90  
     private static final String EMPTY = "";
 91  
 
 92  5
     public enum Format {
 93  1
         TXT, HTML, XML
 94  
     }
 95  
 
 96  
     private final Format format;
 97  
     private final PrintStream output;
 98  
     private final Properties outputPatterns;
 99  
     private final Keywords keywords;
 100  243
     private ThreadLocal<Boolean> reportFailureTrace = new ThreadLocal<Boolean>();
 101  243
     private ThreadLocal<Boolean> compressFailureTrace = new ThreadLocal<Boolean>();
 102  243
     private ThreadLocal<Throwable> cause = new ThreadLocal<Throwable>();
 103  
 
 104  
     protected PrintStreamOutput(Format format, PrintStream output, Properties outputPatterns, Keywords keywords,
 105  243
             boolean reportFailureTrace, boolean compressFailureTrace) {
 106  243
         this.format = format;
 107  243
         this.output = output;
 108  243
         this.outputPatterns = outputPatterns;
 109  243
         this.keywords = keywords;
 110  243
         doReportFailureTrace(reportFailureTrace);
 111  243
         doCompressFailureTrace(compressFailureTrace);
 112  243
     }
 113  
 
 114  
     public void beforeStep(String step) {
 115  103
     }
 116  
 
 117  
     public void successful(String step) {
 118  203
         print(format("successful", "{0}\n", step));
 119  203
     }
 120  
 
 121  
     public void ignorable(String step) {
 122  12
         print(format("ignorable", "{0}\n", step));
 123  12
     }
 124  
 
 125  
     public void pending(String step) {
 126  28
         print(format("pending", "{0} ({1})\n", step, keywords.pending()));
 127  28
     }
 128  
 
 129  
     public void notPerformed(String step) {
 130  22
         print(format("notPerformed", "{0} ({1})\n", step, keywords.notPerformed()));
 131  22
     }
 132  
 
 133  
     public void failed(String step, Throwable storyFailure) {
 134  
         // storyFailure be used if a subclass has rewritten the "failed" pattern
 135  
         // to have a {3} as WebDriverHtmlOutput (jbehave-web) does.
 136  57
         if (storyFailure instanceof UUIDExceptionWrapper) {
 137  57
             this.cause.set(storyFailure.getCause());
 138  114
             print(format("failed", "{0} ({1})\n({2})\n", step, keywords.failed(), storyFailure.getCause(),
 139  57
                     ((UUIDExceptionWrapper) storyFailure).getUUID()));
 140  
         } else {
 141  0
             throw new ClassCastException(storyFailure + " should be an instance of UUIDExceptionWrapper");
 142  
         }
 143  57
     }
 144  
 
 145  
     public void failedOutcomes(String step, OutcomesTable table) {
 146  13
         failed(step, table.failureCause());
 147  13
         print(table);
 148  13
     }
 149  
 
 150  
     private void print(OutcomesTable table) {
 151  13
         print(format("outcomesTableStart", "\n"));
 152  13
         List<Outcome<?>> rows = table.getOutcomes();
 153  13
         print(format("outcomesTableHeadStart", "|"));
 154  13
         for (String field : table.getOutcomeFields()) {
 155  52
             print(format("outcomesTableHeadCell", "{0}|", field));
 156  52
         }
 157  13
         print(format("outcomesTableHeadEnd", "\n"));
 158  13
         print(format("outcomesTableBodyStart", EMPTY));
 159  13
         for (Outcome<?> outcome : rows) {
 160  25
             print(format("outcomesTableRowStart", "|", outcome.isVerified() ? "verified" : "notVerified"));
 161  25
             print(format("outcomesTableCell", "{0}|", outcome.getDescription()));
 162  25
             print(format("outcomesTableCell", "{0}|", renderOutcomeValue(outcome.getValue(), table.getDateFormat())));
 163  25
             print(format("outcomesTableCell", "{0}|", outcome.getMatcher()));
 164  25
             print(format("outcomesTableCell", "{0}|", (outcome.isVerified() ? keywords.yes() : keywords.no())));
 165  25
             print(format("outcomesTableRowEnd", "\n"));
 166  25
         }
 167  13
         print(format("outcomesTableBodyEnd", "\n"));
 168  13
         print(format("outcomesTableEnd", "\n"));
 169  13
     }
 170  
 
 171  
     private Object renderOutcomeValue(Object value, String dateFormat) {
 172  25
         if (value instanceof Date) {
 173  13
             return new SimpleDateFormat(dateFormat).format(value);
 174  
         } else {
 175  12
             return value;
 176  
         }
 177  
     }
 178  
 
 179  
     public void storyNotAllowed(Story story, String filter) {
 180  0
         print(format("filter", "{0}\n", filter));
 181  0
     }
 182  
 
 183  
     public void storyCancelled(Story story, StoryDuration storyDuration) {
 184  72
         print(format("storyCancelled", "{0}: {1} ({2} s)\n", keywords.storyCancelled(), keywords.duration(),
 185  36
                 storyDuration.getDurationInSecs()));
 186  36
     }
 187  
 
 188  
     public void beforeStory(Story story, boolean givenStory) {
 189  195
         print(format("beforeStory", "{0}\n({1})\n", story.getDescription().asString(), story.getPath()));
 190  195
         if (!story.getMeta().isEmpty()) {
 191  15
             Meta meta = story.getMeta();
 192  15
             print(meta);
 193  
         }
 194  195
     }
 195  
 
 196  
     public void narrative(Narrative narrative) {
 197  118
         if (!narrative.isEmpty()) {
 198  12
             if (!narrative.isAlternative()) {
 199  24
                 print(format("narrative", "{0}\n{1} {2}\n{3} {4}\n{5} {6}\n", keywords.narrative(),
 200  12
                         keywords.inOrderTo(), narrative.inOrderTo(), keywords.asA(), narrative.asA(),
 201  12
                         keywords.iWantTo(), narrative.iWantTo()));
 202  
             } else {
 203  0
                 print(format("narrative", "{0}\n{1} {2}\n{3} {4}\n{5} {6}\n", keywords.narrative(), keywords.asA(),
 204  0
                         narrative.asA(), keywords.iWantTo(), narrative.iWantTo(), keywords.soThat(), narrative.soThat()));
 205  
             }
 206  
         }
 207  118
     }
 208  
 
 209  
     public void lifecyle(Lifecycle lifecycle) {
 210  106
         if (!lifecycle.isEmpty()) {
 211  0
             print(format("lifecycleStart", "{0}\n", keywords.lifecycle()));
 212  0
             if (!lifecycle.getBeforeSteps().isEmpty()) {
 213  0
                 print(format("lifecycleBeforeStart", "{0}\n", keywords.before()));
 214  0
                 print(lifecycle.getBeforeSteps());
 215  0
                 print(format("lifecycleBeforeEnd", ""));
 216  
             }
 217  0
             if (!lifecycle.getAfterSteps().isEmpty()) {
 218  0
                 print(format("lifecycleAfterStart", "{0}\n", keywords.after()));
 219  0
                 for ( org.jbehave.core.annotations.AfterScenario.Outcome outcome : lifecycle.getOutcomes() ){
 220  0
                         print(format("lifecycleOutcome", "{0} {1}\n", keywords.outcome(), i18n(outcome)));
 221  0
                         print(lifecycle.getAfterSteps(outcome));
 222  0
                 }
 223  0
                 print(format("lifecycleAfterEnd", ""));
 224  
             }
 225  0
             print(format("lifecycleEnd", "\n"));
 226  
         }
 227  106
     }
 228  
 
 229  
     private String i18n(
 230  
                         org.jbehave.core.annotations.AfterScenario.Outcome outcome) {
 231  1
             switch ( outcome ){
 232  0
             case ANY: return keywords.outcomeAny();
 233  0
             case SUCCESS: return keywords.outcomeSuccess();
 234  0
             case FAILURE: return keywords.outcomeFailure();
 235  0
             default: return outcome.name();
 236  
             }
 237  
         }
 238  
 
 239  
         private void print(List<String> steps) {
 240  0
         for (String step : steps) {
 241  0
             print(format("lifecycleStep", "{0}\n", step));
 242  0
         }
 243  0
     }
 244  
 
 245  
     private void print(Meta meta) {
 246  15
         print(format("metaStart", "{0}\n", keywords.meta()));
 247  15
         for (String name : meta.getPropertyNames()) {
 248  30
             print(format("metaProperty", "{0}{1} {2}", keywords.metaProperty(), name, meta.getProperty(name)));
 249  30
         }
 250  15
         print(format("metaEnd", "\n"));
 251  15
     }
 252  
 
 253  
     public void afterStory(boolean givenStory) {
 254  192
         print(format("afterStory", "\n"));
 255  
         // take care not to close System.out
 256  
         // which is used for ConsoleOutput
 257  192
         if (!givenStory && output != System.out) {
 258  134
             output.close();
 259  
         }
 260  192
     }
 261  
 
 262  
     public void givenStories(GivenStories givenStories) {
 263  12
         print(format("givenStoriesStart", "{0}\n", keywords.givenStories()));
 264  12
         for (GivenStory givenStory : givenStories.getStories()) {
 265  48
             print(format("givenStory", "{0} {1}\n", givenStory.asString(),
 266  24
                     (givenStory.hasAnchor() ? givenStory.getParameters() : "")));
 267  24
         }
 268  12
         print(format("givenStoriesEnd", "\n"));
 269  12
     }
 270  
 
 271  
     public void givenStories(List<String> storyPaths) {
 272  12
         givenStories(new GivenStories(StringUtils.join(storyPaths, ",")));
 273  12
     }
 274  
 
 275  
     public void scenarioNotAllowed(Scenario scenario, String filter) {
 276  3
         print(format("filter", "{0}\n", filter));
 277  3
     }
 278  
 
 279  
     public void beforeScenario(String title) {
 280  135
         cause.set(null);
 281  135
         print(format("beforeScenario", "{0} {1}\n", keywords.scenario(), title));
 282  135
     }
 283  
 
 284  
     public void scenarioMeta(Meta meta) {
 285  0
         if (!meta.isEmpty()) {
 286  0
             print(meta);
 287  
         }
 288  0
     }
 289  
 
 290  
     public void afterScenario() {
 291  116
         if (cause.get() != null && !(cause.get() instanceof KnownFailure) && reportFailureTrace() ) {
 292  8
             print(format("afterScenarioWithFailure", "\n{0}\n",
 293  4
                     new StackTraceFormatter(compressFailureTrace()).stackTrace(cause.get())));
 294  
         } else {
 295  112
             print(format("afterScenario", "\n"));
 296  
         }
 297  116
     }
 298  
 
 299  
     public void beforeExamples(List<String> steps, ExamplesTable table) {
 300  12
         print(format("beforeExamples", "{0}\n", keywords.examplesTable()));
 301  12
         for (String step : steps) {
 302  24
             print(format("examplesStep", "{0}\n", step));
 303  24
         }
 304  12
         print(formatTable(table));
 305  12
     }
 306  
 
 307  
     public void example(Map<String, String> tableRow) {
 308  24
         print(format("example", "\n{0} {1}\n", keywords.examplesTableRow(), tableRow));
 309  24
     }
 310  
 
 311  
     public void afterExamples() {
 312  12
         print(format("afterExamples", "\n"));
 313  12
     }
 314  
 
 315  
     public void dryRun() {
 316  12
         print(format("dryRun", "{0}\n", keywords.dryRun()));
 317  12
     }
 318  
 
 319  
     public void pendingMethods(List<String> methods) {
 320  12
         for (String method : methods) {
 321  24
             print(format("pendingMethod", "{0}\n", method));
 322  24
         }
 323  12
     }
 324  
 
 325  
     public void restarted(String step, Throwable cause) {
 326  12
         print(format("restarted", "{0} {1}\n", step, cause.getMessage()));
 327  12
     }
 328  
     
 329  
     public void restartedStory(Story story, Throwable cause) {
 330  12
         print(format("restartedStory", "{0} {1}\n", story.getPath(), cause.getMessage()));
 331  12
     }
 332  
 
 333  
     /**
 334  
      * Formats event output by key, usually equal to the method name.
 335  
      * 
 336  
      * @param key the event key
 337  
      * @param defaultPattern the default pattern to return if a custom pattern
 338  
      *            is not found
 339  
      * @param args the args used to format output
 340  
      * @return A formatted event output
 341  
      */
 342  
     protected String format(String key, String defaultPattern, Object... args) {
 343  14075
         String escape = escape(defaultPattern);
 344  14075
         String s = lookupPattern(key, escape);
 345  14075
         Object[] objects = escapeAll(args);
 346  14075
         return MessageFormat.format(s, objects);
 347  
     }
 348  
 
 349  
     protected String formatTable(ExamplesTable table) {
 350  12
         OutputStream formatted = new ByteArrayOutputStream();
 351  12
         PrintStream out = new PrintStream(formatted);
 352  12
         out.print(format("examplesTableStart", "\n"));
 353  12
         List<Map<String, String>> rows = table.getRows();
 354  12
         List<String> headers = table.getHeaders();
 355  12
         out.print(format("examplesTableHeadStart", "|"));
 356  12
         for (String header : headers) {
 357  24
             out.print(format("examplesTableHeadCell", "{0}|", header));
 358  24
         }
 359  12
         out.print(format("examplesTableHeadEnd", "\n"));
 360  12
         out.print(format("examplesTableBodyStart", EMPTY));
 361  12
         for (Map<String, String> row : rows) {
 362  24
             out.print(format("examplesTableRowStart", "|"));
 363  24
             for (String header : headers) {
 364  48
                 out.print(format("examplesTableCell", "{0}|", row.get(header)));
 365  48
             }
 366  24
             out.print(format("examplesTableRowEnd", "\n"));
 367  24
         }
 368  12
         out.print(format("examplesTableBodyEnd", ""));
 369  12
         out.print(format("examplesTableEnd", ""));
 370  12
         return formatted.toString();
 371  
     }
 372  
 
 373  
     private String escape(String defaultPattern) {
 374  14075
         return (String) escapeAll(defaultPattern)[0];
 375  
     }
 376  
 
 377  
     private Object[] escapeAll(Object... args) {
 378  28150
         return escape(format, args);
 379  
     }
 380  
 
 381  
     /**
 382  
      * Escapes args' string values according to format
 383  
      * 
 384  
      * @param format the Format used by the PrintStream
 385  
      * @param args the array of args to escape
 386  
      * @return The cloned and escaped array of args
 387  
      */
 388  
     protected Object[] escape(final Format format, Object... args) {
 389  
         // Transformer that escapes HTML and XML strings
 390  28150
         Transformer escapingTransformer = new Transformer() {
 391  
             public Object transform(Object object) {
 392  16067
                 switch (format) {
 393  
                 case HTML:
 394  5157
                     return escapeHtml(asString(object));
 395  
                 case XML:
 396  3131
                     return escapeXml(asString(object));
 397  
                 default:
 398  7779
                     return object;
 399  
                 }
 400  
             }
 401  
 
 402  
             private String asString(Object object) {
 403  8288
                 return (object != null ? object.toString() : EMPTY);
 404  
             }
 405  
         };
 406  28150
         List<?> list = Arrays.asList(ArrayUtils.clone(args));
 407  28150
         CollectionUtils.transform(list, escapingTransformer);
 408  28150
         return list.toArray();
 409  
     }
 410  
 
 411  
     /**
 412  
      * Looks up the format pattern for the event output by key, conventionally
 413  
      * equal to the method name. The pattern is used by the
 414  
      * {#format(String,String,Object...)} method and by default is formatted
 415  
      * using the {@link MessageFormat#format(String, Object...)} method. If no
 416  
      * pattern is found for key or needs to be overridden, the default pattern
 417  
      * should be returned.
 418  
      * 
 419  
      * @param key the format pattern key
 420  
      * @param defaultPattern the default pattern if no pattern is
 421  
      * @return The format patter for the given key
 422  
      */
 423  
     protected String lookupPattern(String key, String defaultPattern) {
 424  14089
         if (outputPatterns.containsKey(key)) {
 425  6345
             return outputPatterns.getProperty(key);
 426  
         }
 427  7744
         return defaultPattern;
 428  
     }
 429  
 
 430  
     public boolean reportFailureTrace() {
 431  29
         Boolean reportFailure = reportFailureTrace.get();
 432  29
         if ( reportFailure != null ){
 433  28
             return reportFailure;
 434  
         }
 435  1
         return false;
 436  
     }
 437  
 
 438  
     public PrintStreamOutput doReportFailureTrace(boolean reportFailureTrace) {
 439  438
         this.reportFailureTrace.set(reportFailureTrace);
 440  438
         return this;
 441  
     }
 442  
 
 443  
     public boolean compressFailureTrace() {
 444  5
         return compressFailureTrace.get();
 445  
     }
 446  
 
 447  
     public PrintStreamOutput doCompressFailureTrace(boolean compressFailureTrace) {
 448  438
         this.compressFailureTrace.set(compressFailureTrace);
 449  438
         return this;
 450  
     }
 451  
 
 452  
     protected void overwritePattern(String key, String pattern) {
 453  7
         outputPatterns.put(key, pattern);
 454  7
     }
 455  
 
 456  
     /**
 457  
      * Prints text to output stream, replacing parameter start and end
 458  
      * placeholders
 459  
      * 
 460  
      * @param text the String to print
 461  
      */
 462  
     protected void print(String text) {
 463  1543
         if (containsTable(text)) {
 464  0
             String tableStart = format(PARAMETER_TABLE_START, PARAMETER_TABLE_START);
 465  0
             String tableEnd = format(PARAMETER_TABLE_END, PARAMETER_TABLE_END);
 466  0
             String tableAsString = substringBetween(text, tableStart, tableEnd);
 467  0
             output.print(text
 468  0
                     .replace(tableAsString, formatTable(new ExamplesTable(tableAsString)))
 469  0
                     .replace(tableStart, format("parameterValueStart", EMPTY))
 470  0
                     .replace(tableEnd, format("parameterValueEnd", EMPTY))
 471  0
                     .replace(format(PARAMETER_VALUE_START, PARAMETER_VALUE_START), format("parameterValueStart", EMPTY))
 472  0
                     .replace(format(PARAMETER_VALUE_END, PARAMETER_VALUE_END), format("parameterValueEnd", EMPTY))
 473  0
                     .replace(format(PARAMETER_VALUE_NEWLINE, PARAMETER_VALUE_NEWLINE),
 474  0
                             format("parameterValueNewline", "\n")));
 475  0
         } else {
 476  3086
             output.print(text
 477  1543
                     .replace(format(PARAMETER_VALUE_START, PARAMETER_VALUE_START), format("parameterValueStart", EMPTY))
 478  1543
                     .replace(format(PARAMETER_VALUE_END, PARAMETER_VALUE_END), format("parameterValueEnd", EMPTY))
 479  3086
                     .replace(format(PARAMETER_VALUE_NEWLINE, PARAMETER_VALUE_NEWLINE),
 480  1543
                             format("parameterValueNewline", "\n")));
 481  
         }
 482  1543
     }
 483  
 
 484  
     private boolean containsTable(String text) {
 485  1543
         String tableStart = format(PARAMETER_TABLE_START, PARAMETER_TABLE_START);
 486  1543
         String tableEnd = format(PARAMETER_TABLE_END, PARAMETER_TABLE_END);
 487  1543
         return text.contains(tableStart) && text.contains(tableEnd);
 488  
     }
 489  
 
 490  
     @Override
 491  
     public String toString() {
 492  0
         return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append(format).append(output).toString();
 493  
     }
 494  
 
 495  
 }