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