Coverage Report - org.tap4j.parser.Tap13Parser
 
Classes in this File Line Coverage Branch Coverage Complexity
Tap13Parser
87%
157/180
75%
47/62
3.526
 
 1  
 /*
 2  
  * The MIT License
 3  
  *
 4  
  * Copyright (c) <2010> <tap4j>
 5  
  * 
 6  
  * Permission is hereby granted, free of charge, to any person obtaining a copy
 7  
  * of this software and associated documentation files (the "Software"), to deal
 8  
  * in the Software without restriction, including without limitation the rights
 9  
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  
  * copies of the Software, and to permit persons to whom the Software is
 11  
  * furnished to do so, subject to the following conditions:
 12  
  * 
 13  
  * The above copyright notice and this permission notice shall be included in
 14  
  * all copies or substantial portions of the Software.
 15  
  * 
 16  
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22  
  * THE SOFTWARE.
 23  
  */
 24  
 package org.tap4j.parser;
 25  
 
 26  
 import java.io.File;
 27  
 import java.util.Scanner;
 28  
 import java.util.regex.Matcher;
 29  
 
 30  
 import org.apache.commons.lang.StringUtils;
 31  
 import org.tap4j.model.BailOut;
 32  
 import org.tap4j.model.Comment;
 33  
 import org.tap4j.model.Directive;
 34  
 import org.tap4j.model.Footer;
 35  
 import org.tap4j.model.Header;
 36  
 import org.tap4j.model.Plan;
 37  
 import org.tap4j.model.SkipPlan;
 38  
 import org.tap4j.model.TestResult;
 39  
 import org.tap4j.model.TestSet;
 40  
 import org.tap4j.model.Text;
 41  
 import org.tap4j.util.DirectiveValues;
 42  
 import org.tap4j.util.StatusValues;
 43  
 
 44  
 /**
 45  
  * TAP 13 parser.
 46  
  * 
 47  
  * @since 1.0
 48  
  */
 49  
 public class Tap13Parser implements Parser {
 50  
 
 51  23
     private boolean isFirstLine = true;
 52  
 
 53  23
     private boolean planBeforeTestResult = false;
 54  
 
 55  
     // Helper String to check the Footer
 56  23
     private String lastLine = null;
 57  
 
 58  
     /**
 59  
      * Test Set.
 60  
      */
 61  
     private TestSet testSet;
 62  
 
 63  
     /**
 64  
      * Default constructor. Calls the init method.
 65  
      */
 66  
     public Tap13Parser() {
 67  23
         super();
 68  23
         this.init();
 69  23
     }
 70  
 
 71  
     /**
 72  
      * Called from the constructor and everytime a new TAP Stream (file or
 73  
      * string) is processed.
 74  
      */
 75  
     public final void init() {
 76  45
         this.isFirstLine = true;
 77  45
         this.planBeforeTestResult = false;
 78  45
         this.lastLine = null;
 79  45
         this.testSet = new TestSet();
 80  45
     }
 81  
 
 82  
     /*
 83  
      * (non-Javadoc)
 84  
      * @see org.tap4j.TapConsumer#getTestSet()
 85  
      */
 86  
     public TestSet getTestSet() {
 87  14
         return this.testSet;
 88  
     }
 89  
 
 90  
     /*
 91  
      * (non-Javadoc)
 92  
      * @see org.tap4j.TapConsumer#parseLine(java.lang.String)
 93  
      */
 94  
     public void parseLine(String tapLine) {
 95  87
         if (StringUtils.isEmpty(tapLine)) {
 96  0
             return;
 97  
         }
 98  
 
 99  87
         Matcher matcher = null;
 100  
 
 101  
         // Comment
 102  87
         matcher = COMMENT_PATTERN.matcher(tapLine);
 103  87
         if (matcher.matches()) {
 104  5
             this.extractComment(matcher);
 105  5
             return;
 106  
         }
 107  
 
 108  
         // Last line that is not a comment.
 109  82
         lastLine = tapLine;
 110  
 
 111  
         // Header
 112  82
         matcher = HEADER_PATTERN.matcher(tapLine);
 113  82
         if (matcher.matches()) {
 114  
 
 115  17
             this.checkTAPHeaderParsingLocationAndDuplicity();
 116  
 
 117  13
             this.extractHeader(matcher);
 118  13
             this.isFirstLine = false;
 119  13
             return;
 120  
         }
 121  
 
 122  
         // Plan
 123  65
         matcher = PLAN_PATTERN.matcher(tapLine);
 124  65
         if (matcher.matches()) {
 125  
 
 126  17
             this.checkTAPPlanDuplicity();
 127  
 
 128  17
             this.checkIfTAPPlanIsSetBeforeTestResultsOrBailOut();
 129  
 
 130  17
             this.extractPlan(matcher);
 131  17
             this.isFirstLine = false;
 132  17
             return;
 133  
         }
 134  
 
 135  
         // Test Result
 136  48
         matcher = TEST_RESULT_PATTERN.matcher(tapLine);
 137  48
         if (matcher.matches()) {
 138  41
             this.extractTestResult(matcher);
 139  41
             return;
 140  
         }
 141  
 
 142  
         // Bail Out
 143  7
         matcher = BAIL_OUT_PATTERN.matcher(tapLine);
 144  7
         if (matcher.matches()) {
 145  2
             this.extractBailOut(matcher);
 146  2
             return;
 147  
         }
 148  
 
 149  
         // Footer
 150  5
         matcher = FOOTER_PATTERN.matcher(tapLine);
 151  5
         if (matcher.matches()) {
 152  4
             this.extractFooter(matcher);
 153  4
             return;
 154  
         }
 155  
 
 156  
         // Any text. It should not be parsed by the consumer.
 157  1
         final Text text = new Text(tapLine);
 158  1
         this.testSet.getTapLines().add(text);
 159  1
     }
 160  
 
 161  
     /**
 162  
      * Checks if the TAP Plan is set before any Test Result or Bail Out.
 163  
      */
 164  
     protected void checkIfTAPPlanIsSetBeforeTestResultsOrBailOut() {
 165  17
         if (this.testSet.getTestResults().size() <= 0 &&
 166  
             this.testSet.getBailOuts().size() <= 0) {
 167  14
             this.planBeforeTestResult = true;
 168  
         }
 169  17
     }
 170  
 
 171  
     /**
 172  
      * Checks the Header location and duplicity. The Header must be the first
 173  
      * element and cannot occurs more than on time. However the Header is
 174  
      * optional.
 175  
      */
 176  
     protected void checkTAPHeaderParsingLocationAndDuplicity() {
 177  17
         if (this.testSet.getHeader() != null) {
 178  1
             throw new ParserException("Duplicated TAP Header found.");
 179  
         }
 180  16
         if (!isFirstLine) {
 181  3
             throw new ParserException(
 182  
                                       "Invalid position of TAP Header. It must be the first element (apart of Comments) in the TAP Stream.");
 183  
         }
 184  13
     }
 185  
 
 186  
     /**
 187  
      * Checks if there are more than one TAP Plan in the TAP Stream.
 188  
      */
 189  
     protected void checkTAPPlanDuplicity() {
 190  17
         if (this.testSet.getPlan() != null) {
 191  0
             throw new ParserException("Duplicated TAP Plan found.");
 192  
         }
 193  17
     }
 194  
 
 195  
     /**
 196  
      * This method is called after the TAP Stream has already been parsed. So we
 197  
      * just check if the plan was found before test result or bail outs. If so,
 198  
      * skip this check. Otherwise, we shall check if the last line is the TAP
 199  
      * Plan.
 200  
      * 
 201  
      * @deprecated
 202  
      */
 203  
     protected void checkTAPPlanPosition() {
 204  0
         if (!this.planBeforeTestResult) {
 205  0
             Matcher matcher = PLAN_PATTERN.matcher(lastLine);
 206  
 
 207  0
             if (matcher.matches()) {
 208  0
                 return; // OK
 209  
             }
 210  
 
 211  0
             throw new ParserException("Invalid position of TAP Plan.");
 212  
         }
 213  0
     }
 214  
 
 215  
     /**
 216  
      * Checks if TAP Plan has been set.
 217  
      * 
 218  
      * @throws ParserException if TAP Plan has not been set.
 219  
      */
 220  
     protected void checkTAPPlanIsSet() {
 221  18
         if (this.testSet.getPlan() == null) {
 222  4
             throw new ParserException("Missing TAP Plan.");
 223  
         }
 224  14
     }
 225  
 
 226  
     /**
 227  
      * Extracts the Header from a TAP Line.
 228  
      * 
 229  
      * @param matcher REGEX Matcher.
 230  
      */
 231  
     protected void extractHeader(Matcher matcher) {
 232  13
         final Integer version = Integer.parseInt(matcher.group(1));
 233  
 
 234  13
         final Header header = new Header(version);
 235  
 
 236  13
         final String commentToken = matcher.group(2);
 237  
 
 238  13
         if (commentToken != null) {
 239  1
             String text = matcher.group(3);
 240  1
             final Comment comment = new Comment(text);
 241  1
             header.setComment(comment);
 242  
         }
 243  
 
 244  13
         this.testSet.setHeader(header);
 245  13
     }
 246  
 
 247  
     /**
 248  
      * @param matcher REGEX Matcher.
 249  
      */
 250  
     protected void extractPlan(Matcher matcher) {
 251  17
         Integer initialTest = Integer.parseInt(matcher.group(1));
 252  17
         Integer lastTest = Integer.parseInt(matcher.group(3));
 253  
 
 254  17
         Plan plan = null;
 255  17
         plan = new Plan(initialTest, lastTest);
 256  
 
 257  17
         String skipToken = matcher.group(4);
 258  17
         if (skipToken != null) {
 259  2
             String reason = matcher.group(5);
 260  2
             final SkipPlan skip = new SkipPlan(reason);
 261  2
             plan.setSkip(skip);
 262  
         }
 263  
 
 264  17
         String commentToken = matcher.group(6);
 265  17
         if (commentToken != null) {
 266  1
             String text = matcher.group(7);
 267  1
             final Comment comment = new Comment(text);
 268  1
             plan.setComment(comment);
 269  
         }
 270  
 
 271  17
         this.testSet.setPlan(plan);
 272  17
     }
 273  
 
 274  
     /**
 275  
      * @param matcher REGEX Matcher.
 276  
      */
 277  
     protected void extractTestResult(Matcher matcher) {
 278  41
         TestResult testResult = null;
 279  
 
 280  41
         final String okOrNotOk = matcher.group(1);
 281  41
         StatusValues status = null;
 282  41
         if (okOrNotOk.trim().equals("ok")) {
 283  32
             status = StatusValues.OK;
 284  
         } else // regex mate...
 285  
         {
 286  9
             status = StatusValues.NOT_OK;
 287  
         }
 288  
 
 289  41
         Integer testNumber = this.getTestNumber(matcher.group(2));
 290  
 
 291  41
         testResult = new TestResult(status, testNumber);
 292  
 
 293  41
         testResult.setDescription(matcher.group(3));
 294  
 
 295  41
         String directiveToken = matcher.group(4);
 296  41
         if (directiveToken != null) {
 297  0
             String directiveText = matcher.group(5);
 298  0
             DirectiveValues directiveValue = null;
 299  0
             if (directiveText.trim().equalsIgnoreCase("todo")) {
 300  0
                 directiveValue = DirectiveValues.TODO;
 301  
             } else {
 302  0
                 directiveValue = DirectiveValues.SKIP;
 303  
             }
 304  0
             String reason = matcher.group(6);
 305  0
             Directive directive = new Directive(directiveValue, reason);
 306  0
             testResult.setDirective(directive);
 307  
         }
 308  
 
 309  41
         String commentToken = matcher.group(7);
 310  41
         if (commentToken != null) {
 311  0
             String text = matcher.group(8);
 312  0
             final Comment comment = new Comment(text);
 313  0
             comment.setInline(Boolean.TRUE);
 314  0
             testResult.addComment(comment);
 315  
         }
 316  
 
 317  41
         this.testSet.addTestResult(testResult);
 318  41
         this.testSet.addTapLine(testResult);
 319  41
     }
 320  
 
 321  
     /**
 322  
      * Returns the test number out from an input String. If the string is null
 323  
      * or equals "" this method returns the next test result number. Otherwise
 324  
      * it will return the input String value parsed to an Integer.
 325  
      * 
 326  
      * @param testNumber
 327  
      * @return
 328  
      */
 329  
     private Integer getTestNumber(String testNumber) {
 330  41
         Integer integerTestNumber = null;
 331  41
         if (StringUtils.isEmpty(testNumber)) {
 332  0
             integerTestNumber = (this.testSet.getTestResults().size() + 1);
 333  
         } else {
 334  41
             integerTestNumber = Integer.parseInt(testNumber);
 335  
         }
 336  41
         return integerTestNumber;
 337  
     }
 338  
 
 339  
     /**
 340  
      * @param matcher REGEX Matcher.
 341  
      */
 342  
     protected void extractBailOut(Matcher matcher) {
 343  2
         String reason = matcher.group(1);
 344  
 
 345  2
         BailOut bailOut = new BailOut(reason);
 346  
 
 347  2
         String commentToken = matcher.group(2);
 348  
 
 349  2
         if (commentToken != null) {
 350  1
             String text = matcher.group(3);
 351  1
             Comment comment = new Comment(text);
 352  1
             bailOut.setComment(comment);
 353  
         }
 354  
 
 355  2
         this.testSet.addBailOut(bailOut);
 356  2
         this.testSet.addTapLine(bailOut);
 357  2
     }
 358  
 
 359  
     /**
 360  
      * @param matcher REGEX Matcher.
 361  
      */
 362  
     protected void extractComment(Matcher matcher) {
 363  5
         String text = matcher.group(1);
 364  5
         Comment comment = new Comment(text);
 365  
 
 366  5
         this.testSet.addComment(comment);
 367  5
         this.testSet.addTapLine(comment);
 368  5
     }
 369  
 
 370  
     /**
 371  
      * Simply extracts the footer from the TAP line.
 372  
      * 
 373  
      * @param matcher REGEX Matcher.
 374  
      */
 375  
     protected void extractFooter(Matcher matcher) {
 376  4
         String text = matcher.group(1);
 377  4
         Footer footer = new Footer(text);
 378  
 
 379  4
         final String commentToken = matcher.group(2);
 380  
 
 381  4
         if (commentToken != null) {
 382  1
             String commentText = matcher.group(3);
 383  1
             final Comment comment = new Comment(commentText);
 384  1
             footer.setComment(comment);
 385  
         }
 386  
 
 387  4
         this.testSet.setFooter(footer);
 388  4
     }
 389  
 
 390  
     /*
 391  
      * (non-Javadoc)
 392  
      * @see org.tap4j.TapConsumer#parseTapStream(java.lang.String)
 393  
      */
 394  
     public TestSet parseTapStream(String tapStream) {
 395  
 
 396  2
         this.init();
 397  
 
 398  2
         Scanner scanner = null;
 399  
 
 400  
         try {
 401  2
             scanner = new Scanner(tapStream);
 402  2
             String line = null;
 403  
 
 404  9
             while (scanner.hasNextLine()) {
 405  7
                 line = scanner.nextLine();
 406  7
                 if (StringUtils.isNotEmpty(line)) {
 407  7
                     this.parseLine(line);
 408  
                 }
 409  
             }
 410  2
             this.postProcess();
 411  0
         } catch (Exception e) {
 412  0
             throw new ParserException("Error parsing TAP Stream: " +
 413  
                                       e.getMessage(), e);
 414  
         } finally {
 415  2
             if (scanner != null) {
 416  2
                 scanner.close();
 417  
             }
 418  
         }
 419  
 
 420  2
         return this.getTestSet();
 421  
 
 422  
     }
 423  
 
 424  
     /*
 425  
      * (non-Javadoc)
 426  
      * @see org.tap4j.TapConsumer#parseFile(java.io.File)
 427  
      */
 428  
     public TestSet parseFile(File tapFile) {
 429  
 
 430  20
         this.init();
 431  
 
 432  20
         Scanner scanner = null;
 433  
 
 434  
         try {
 435  20
             scanner = new Scanner(tapFile);
 436  20
             String line = null;
 437  
 
 438  109
             while (scanner.hasNextLine()) {
 439  93
                 line = scanner.nextLine();
 440  93
                 if (StringUtils.isNotBlank(line)) {
 441  80
                     this.parseLine(line);
 442  
                 }
 443  
             }
 444  16
             this.postProcess();
 445  8
         } catch (Exception e) {
 446  8
             throw new ParserException("Error parsing TAP Stream: " +
 447  
                                       e.getMessage(), e);
 448  
         } finally {
 449  20
             if (scanner != null) {
 450  20
                 scanner.close();
 451  
             }
 452  
         }
 453  
 
 454  12
         return this.getTestSet();
 455  
     }
 456  
 
 457  
     /**
 458  
      * @throws org.tap4j.consumer.TapConsumerException
 459  
      */
 460  
     protected void postProcess() {
 461  
         // deprecated for better interoperability with Perl done_testing()
 462  
         // this.checkTAPPlanPosition();
 463  18
         this.checkTAPPlanIsSet();
 464  14
     }
 465  
 
 466  
 }