Coverage Report - org.tap4j.parser.Tap13YamlParser
 
Classes in this File Line Coverage Branch Coverage Complexity
Tap13YamlParser
87%
241/274
74%
76/102
3.741
 
 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.Map;
 28  
 import java.util.Scanner;
 29  
 import java.util.Stack;
 30  
 import java.util.regex.Matcher;
 31  
 import java.util.regex.Pattern;
 32  
 
 33  
 import org.apache.commons.lang.StringUtils;
 34  
 import org.tap4j.model.BailOut;
 35  
 import org.tap4j.model.Comment;
 36  
 import org.tap4j.model.Directive;
 37  
 import org.tap4j.model.Footer;
 38  
 import org.tap4j.model.Header;
 39  
 import org.tap4j.model.Plan;
 40  
 import org.tap4j.model.SkipPlan;
 41  
 import org.tap4j.model.TapElement;
 42  
 import org.tap4j.model.TestResult;
 43  
 import org.tap4j.model.TestSet;
 44  
 import org.tap4j.model.Text;
 45  
 import org.tap4j.util.DirectiveValues;
 46  
 import org.tap4j.util.StatusValues;
 47  
 import org.yaml.snakeyaml.Yaml;
 48  
 
 49  
 /**
 50  
  * TAP 13 parser with support to YAML.
 51  
  * 
 52  
  * @since 1.0
 53  
  */
 54  
 public class Tap13YamlParser implements Parser {
 55  
 
 56  1
     protected static final Pattern INDENTANTION_PATTERN = Pattern
 57  
         .compile("((\\s|\\t)*)?.*");
 58  
 
 59  
     private TestSet testSet;
 60  
 
 61  16
     private Stack<Memento> mementos = new Stack<Memento>();
 62  
 
 63  
     private boolean firstLine;
 64  
 
 65  
     private boolean planBeforeTestResult;
 66  
 
 67  
     private boolean currentlyInYAML;
 68  
 
 69  
     // Helper String to check the Footer
 70  16
     private String lastLine = null;
 71  
 
 72  
     private TapElement lastParsedElement;
 73  
 
 74  
     /**
 75  
      * Indicator of the base indentation level. Usually defined by the TAP
 76  
      * Header.
 77  
      */
 78  
     private int baseIndentationLevel;
 79  
 
 80  
     /**
 81  
      * Helper indicator of in what indentantion level we are working at moment.
 82  
      * It is helpful specially when you have many nested elements, like a META
 83  
      * element with some multiline text.
 84  
      */
 85  
     private int currentIndentationLevel;
 86  
 
 87  
     /**
 88  
      * YAML parser and emitter.
 89  
      */
 90  
     private Yaml yaml;
 91  
 
 92  
     private StringBuilder diagnosticBuffer;
 93  
 
 94  
     public Tap13YamlParser() {
 95  16
         super();
 96  16
         this.init();
 97  16
     }
 98  
 
 99  
     /**
 100  
      * Called from the constructor and everytime a new TAP Stream (file or
 101  
      * string) is processed.
 102  
      */
 103  
     public final void init() {
 104  37
         this.baseIndentationLevel = -1;
 105  37
         this.currentIndentationLevel = -1;
 106  37
         this.currentlyInYAML = Boolean.FALSE;
 107  37
         this.diagnosticBuffer = new StringBuilder();
 108  37
         this.lastParsedElement = null;
 109  37
         this.firstLine = Boolean.TRUE;
 110  37
         this.planBeforeTestResult = Boolean.FALSE;
 111  37
         this.testSet = new TestSet();
 112  37
         yaml = new Yaml();
 113  37
     }
 114  
 
 115  
     /**
 116  
      * Save the parser memento.
 117  
      */
 118  
     private void saveMemento() {
 119  4
         Memento memento = new Memento();
 120  4
         memento.setBaseIndentationLevel(this.baseIndentationLevel);
 121  4
         memento.setCurrentIndentationLevel(this.currentIndentationLevel);
 122  4
         memento.setCurrentlyInYaml(this.currentlyInYAML);
 123  4
         memento.setDiagnosticBuffer(this.diagnosticBuffer);
 124  4
         memento.setLastParsedElement(this.lastParsedElement);
 125  4
         memento.setFirstLine(this.firstLine);
 126  4
         memento.setPlanBeforeTestResult(this.planBeforeTestResult);
 127  4
         memento.setTestSet(this.testSet);
 128  4
         this.mementos.push(memento);
 129  4
     }
 130  
 
 131  
     /**
 132  
      * Load the parser memento.
 133  
      */
 134  
     private void loadMemento() {
 135  4
         Memento memento = this.mementos.pop();
 136  4
         this.baseIndentationLevel = memento.getBaseIndentationLevel();
 137  4
         this.currentIndentationLevel = memento.getCurrentIndentationLevel();
 138  4
         this.currentlyInYAML = memento.isCurrentlyInYaml();
 139  4
         this.diagnosticBuffer = memento.getDiagnosticBuffer();
 140  4
         this.lastParsedElement = memento.getLastParsedElement();
 141  4
         this.firstLine = memento.isFirstLine();
 142  4
         this.planBeforeTestResult = memento.isPlanBeforeTestResult();
 143  4
         this.testSet = memento.getTestSet();
 144  4
     }
 145  
 
 146  
     /*
 147  
      * (non-Javadoc)
 148  
      * @see org.tap4j.TapConsumer#getTestSet()
 149  
      */
 150  
     public TestSet getTestSet() {
 151  14
         return this.testSet;
 152  
     }
 153  
 
 154  
     /*
 155  
      * (non-Javadoc)
 156  
      * @see org.tap4j.consumer.DefaultTapConsumer#parseLine(java.lang.String)
 157  
      */
 158  
     public void parseLine(String tapLine) {
 159  172
         Matcher matcher = null;
 160  
 
 161  
         // Comment
 162  172
         matcher = COMMENT_PATTERN.matcher(tapLine);
 163  172
         if (matcher.matches()) {
 164  6
             this.extractComment(matcher);
 165  6
             return; // NOPMD by Bruno on 12/01/11 07:47
 166  
         }
 167  
 
 168  
         // Last line that is not a comment.
 169  166
         lastLine = tapLine;
 170  
 
 171  
         // Check if we already know the indentation level... if so, try to find
 172  
         // out the indentation level of the current line in the TAP Stream.
 173  
         // If the line indentation level is greater than the pre-defined
 174  
         // one, than we know it is a) a META, b)
 175  166
         if (this.isBaseIndentationAlreadyDefined()) {
 176  146
             matcher = INDENTANTION_PATTERN.matcher(tapLine);
 177  146
             if (matcher.matches()) {
 178  146
                 String spaces = matcher.group(1);
 179  146
                 int indentation = spaces.length();
 180  146
                 this.currentIndentationLevel = indentation;
 181  146
                 if (indentation > this.baseIndentationLevel) {
 182  
                     // we are at the start of the meta tags, but we should
 183  
                     // ignore
 184  
                     // the --- or ...
 185  
                     // TBD: check how snakeyaml can handle these tokens.
 186  98
                     if (tapLine.trim().equals("---")) {
 187  9
                         this.currentlyInYAML = true;
 188  9
                         return;
 189  89
                     } else if (tapLine.trim().equals("...")) {
 190  5
                         this.currentlyInYAML = false;
 191  5
                         return;
 192  84
                     } else if (this.currentlyInYAML) {
 193  79
                         this.appendTapLineToDiagnosticBuffer(tapLine);
 194  79
                         return; // NOPMD by Bruno on 12/01/11 07:47
 195  
                     } else {
 196  
                         // If we are in a different level, but it is not YAML,
 197  
                         // Then it must be a subtest! Yay!
 198  5
                         if (this.lastParsedElement instanceof TestResult) {
 199  4
                             indentation = baseIndentationLevel;
 200  4
                             TestResult lastTestResult = (TestResult) this.lastParsedElement;
 201  4
                             TestSet newTestSet = new TestSet();
 202  4
                             lastTestResult.setSubtest(newTestSet);
 203  4
                             this.saveMemento();
 204  4
                             this.init();
 205  4
                             this.testSet = newTestSet;
 206  
                         }
 207  
                     }
 208  
                 }
 209  
 
 210  
                 // indentation cannot be less then the base indentation level
 211  53
                 this.checkIndentationLevel(indentation, tapLine);
 212  
             }
 213  
         }
 214  
 
 215  
         // Check if we have some diagnostic set in the buffer
 216  72
         this.checkAndParseTapDiagnostic();
 217  
 
 218  
         // Header
 219  72
         matcher = HEADER_PATTERN.matcher(tapLine);
 220  72
         if (matcher.matches()) {
 221  3
             this.setIndentationLevelIfNotDefined(tapLine);
 222  
 
 223  3
             this.currentIndentationLevel = this.baseIndentationLevel;
 224  
 
 225  3
             this.checkTAPHeaderParsingLocationAndDuplicity();
 226  
 
 227  3
             this.extractHeader(matcher);
 228  3
             this.firstLine = false;
 229  
 
 230  3
             this.lastParsedElement = this.testSet.getHeader();
 231  
 
 232  3
             return; // NOPMD by Bruno on 12/01/11 07:47
 233  
         }
 234  
 
 235  
         // Check if the header was set
 236  
         // this.checkHeader();
 237  
 
 238  
         // Plan
 239  69
         matcher = PLAN_PATTERN.matcher(tapLine);
 240  69
         if (matcher.matches()) {
 241  19
             this.checkTAPPlanDuplicity();
 242  
 
 243  19
             this.checkIfTAPPlanIsSetBeforeTestResultsOrBailOut();
 244  
 
 245  19
             this.setIndentationLevelIfNotDefined(tapLine);
 246  
 
 247  19
             this.extractPlan(matcher);
 248  19
             this.firstLine = false;
 249  
 
 250  19
             this.lastParsedElement = this.testSet.getPlan();
 251  
 
 252  19
             return; // NOPMD by Bruno on 12/01/11 07:47
 253  
         }
 254  
 
 255  
         // Test Result
 256  50
         matcher = TEST_RESULT_PATTERN.matcher(tapLine);
 257  50
         if (matcher.matches()) {
 258  39
             this.setIndentationLevelIfNotDefined(tapLine);
 259  
 
 260  39
             this.extractTestResult(matcher);
 261  
 
 262  39
             this.lastParsedElement = this.testSet.getTapLines()
 263  
                 .get((this.testSet.getTapLines().size() - 1));
 264  
 
 265  39
             return; // NOPMD by Bruno on 12/01/11 07:47
 266  
         }
 267  
 
 268  
         // Bail Out
 269  11
         matcher = BAIL_OUT_PATTERN.matcher(tapLine);
 270  11
         if (matcher.matches()) {
 271  
 
 272  1
             this.setIndentationLevelIfNotDefined(tapLine);
 273  
 
 274  1
             this.extractBailOut(matcher);
 275  
 
 276  1
             this.lastParsedElement = this.testSet.getTapLines()
 277  
                 .get((this.testSet.getTapLines().size() - 1));
 278  
 
 279  1
             return; // NOPMD by Bruno on 12/01/11 07:47
 280  
         }
 281  
 
 282  
         // Footer
 283  10
         matcher = FOOTER_PATTERN.matcher(tapLine);
 284  10
         if (matcher.matches()) {
 285  1
             this.extractFooter(matcher);
 286  
 
 287  1
             this.lastParsedElement = this.testSet.getFooter();
 288  
 
 289  1
             return; // NOPMD by Bruno on 12/01/11 07:47
 290  
         }
 291  
 
 292  
         // Any text. It should not be parsed by the consumer.
 293  9
         final Text text = new Text(tapLine);
 294  9
         this.lastParsedElement = text;
 295  9
         this.testSet.addTapLine(text);
 296  
 
 297  9
     }
 298  
 
 299  
     /**
 300  
      * Checks if the TAP Plan is set before any Test Result or Bail Out.
 301  
      */
 302  
     protected void checkIfTAPPlanIsSetBeforeTestResultsOrBailOut() {
 303  19
         if (this.testSet.getTestResults().size() <= 0 &&
 304  
             this.testSet.getBailOuts().size() <= 0) {
 305  18
             this.planBeforeTestResult = true;
 306  
         }
 307  19
     }
 308  
 
 309  
     /**
 310  
      * Checks the Header location and duplicity. The Header must be the first
 311  
      * element and cannot occurs more than on time. However the Header is
 312  
      * optional.
 313  
      */
 314  
     protected void checkTAPHeaderParsingLocationAndDuplicity() {
 315  3
         if (this.testSet.getHeader() != null) {
 316  0
             throw new ParserException("Duplicated TAP Header found.");
 317  
         }
 318  3
         if (!firstLine) {
 319  0
             throw new ParserException(
 320  
                                       "Invalid position of TAP Header. It must be the first element (apart of Comments) in the TAP Stream.");
 321  
         }
 322  3
     }
 323  
 
 324  
     /**
 325  
      * Checks if there are more than one TAP Plan in the TAP Stream.
 326  
      */
 327  
     protected void checkTAPPlanDuplicity() {
 328  19
         if (this.testSet.getPlan() != null) {
 329  0
             throw new ParserException("Duplicated TAP Plan found.");
 330  
         }
 331  19
     }
 332  
 
 333  
     /**
 334  
      * This method is called after the TAP Stream has already been parsed. So we
 335  
      * just check if the plan was found before test result or bail outs. If so,
 336  
      * skip this check. Otherwise, we shall check if the last line is the TAP
 337  
      * Plan.
 338  
      * 
 339  
      * @deprecated
 340  
      */
 341  
     protected void checkTAPPlanPosition() {
 342  0
         if (!this.planBeforeTestResult) {
 343  0
             Matcher matcher = PLAN_PATTERN.matcher(lastLine);
 344  
 
 345  0
             if (matcher.matches()) {
 346  0
                 return; // OK
 347  
             }
 348  
 
 349  0
             throw new ParserException("Invalid position of TAP Plan.");
 350  
         }
 351  0
     }
 352  
 
 353  
     /**
 354  
      * Checks if TAP Plan has been set.
 355  
      * 
 356  
      * @throws ParserException if TAP Plan has not been set.
 357  
      */
 358  
     protected void checkTAPPlanIsSet() {
 359  16
         if (this.testSet.getPlan() == null) {
 360  2
             throw new ParserException("Missing TAP Plan.");
 361  
         }
 362  14
     }
 363  
 
 364  
     /**
 365  
      * Extracts the Header from a TAP Line.
 366  
      * 
 367  
      * @param matcher REGEX Matcher.
 368  
      */
 369  
     protected void extractHeader(Matcher matcher) {
 370  3
         final Integer version = Integer.parseInt(matcher.group(1));
 371  
 
 372  3
         final Header header = new Header(version);
 373  
 
 374  3
         final String commentToken = matcher.group(2);
 375  
 
 376  3
         if (commentToken != null) {
 377  0
             String text = matcher.group(3);
 378  0
             final Comment comment = new Comment(text);
 379  0
             header.setComment(comment);
 380  
         }
 381  
 
 382  3
         this.testSet.setHeader(header);
 383  3
     }
 384  
 
 385  
     /**
 386  
      * @param matcher REGEX Matcher.
 387  
      */
 388  
     protected void extractPlan(Matcher matcher) {
 389  19
         Integer initialTest = Integer.parseInt(matcher.group(1));
 390  19
         Integer lastTest = Integer.parseInt(matcher.group(3));
 391  
 
 392  19
         Plan plan = null;
 393  19
         plan = new Plan(initialTest, lastTest);
 394  
 
 395  19
         String skipToken = matcher.group(4);
 396  19
         if (skipToken != null) {
 397  0
             String reason = matcher.group(5);
 398  0
             final SkipPlan skip = new SkipPlan(reason);
 399  0
             plan.setSkip(skip);
 400  
         }
 401  
 
 402  19
         String commentToken = matcher.group(6);
 403  19
         if (commentToken != null) {
 404  0
             String text = matcher.group(7);
 405  0
             final Comment comment = new Comment(text);
 406  0
             plan.setComment(comment);
 407  
         }
 408  
 
 409  19
         this.testSet.setPlan(plan);
 410  19
     }
 411  
 
 412  
     /**
 413  
      * @param matcher REGEX Matcher.
 414  
      */
 415  
     protected void extractTestResult(Matcher matcher) {
 416  39
         TestResult testResult = null;
 417  
 
 418  39
         final String okOrNotOk = matcher.group(1);
 419  39
         StatusValues status = null;
 420  39
         if (okOrNotOk.trim().equals("ok")) {
 421  29
             status = StatusValues.OK;
 422  
         } else // regex mate...
 423  
         {
 424  10
             status = StatusValues.NOT_OK;
 425  
         }
 426  
 
 427  39
         Integer testNumber = this.getTestNumber(matcher.group(2));
 428  
 
 429  39
         testResult = new TestResult(status, testNumber);
 430  
 
 431  39
         testResult.setDescription(matcher.group(3));
 432  
 
 433  39
         String directiveToken = matcher.group(4);
 434  39
         if (directiveToken != null) {
 435  8
             String directiveText = matcher.group(5);
 436  8
             DirectiveValues directiveValue = null;
 437  8
             if (directiveText.trim().equalsIgnoreCase("todo")) {
 438  3
                 directiveValue = DirectiveValues.TODO;
 439  
             } else {
 440  5
                 directiveValue = DirectiveValues.SKIP;
 441  
             }
 442  8
             String reason = matcher.group(6);
 443  8
             Directive directive = new Directive(directiveValue, reason);
 444  8
             testResult.setDirective(directive);
 445  
         }
 446  
 
 447  39
         String commentToken = matcher.group(7);
 448  39
         if (commentToken != null) {
 449  0
             String text = matcher.group(8);
 450  0
             final Comment comment = new Comment(text);
 451  0
             comment.setInline(Boolean.TRUE);
 452  0
             testResult.addComment(comment);
 453  
         }
 454  
 
 455  39
         this.testSet.addTestResult(testResult);
 456  39
     }
 457  
 
 458  
     /**
 459  
      * Returns the test number out from an input String. If the string is null
 460  
      * or equals "" this method returns the next test result number. Otherwise
 461  
      * it will return the input String value parsed to an Integer.
 462  
      * 
 463  
      * @param testNumber
 464  
      * @return
 465  
      */
 466  
     private Integer getTestNumber(String testNumber) {
 467  39
         Integer integerTestNumber = null;
 468  39
         if (StringUtils.isEmpty(testNumber)) {
 469  0
             integerTestNumber = (this.testSet.getTestResults().size() + 1);
 470  
         } else {
 471  39
             integerTestNumber = Integer.parseInt(testNumber);
 472  
         }
 473  39
         return integerTestNumber;
 474  
     }
 475  
 
 476  
     /**
 477  
      * @param matcher REGEX Matcher.
 478  
      */
 479  
     protected void extractBailOut(Matcher matcher) {
 480  1
         String reason = matcher.group(1);
 481  
 
 482  1
         BailOut bailOut = new BailOut(reason);
 483  
 
 484  1
         String commentToken = matcher.group(2);
 485  
 
 486  1
         if (commentToken != null) {
 487  0
             String text = matcher.group(3);
 488  0
             Comment comment = new Comment(text);
 489  0
             bailOut.setComment(comment);
 490  
         }
 491  
 
 492  1
         this.testSet.addBailOut(bailOut);
 493  1
     }
 494  
 
 495  
     /**
 496  
      * @param matcher REGEX Matcher.
 497  
      */
 498  
     protected void extractComment(Matcher matcher) {
 499  6
         String text = matcher.group(1);
 500  6
         Comment comment = new Comment(text);
 501  
 
 502  6
         this.testSet.addComment(comment);
 503  
 
 504  6
         if (lastParsedElement instanceof TestResult) {
 505  5
             TestResult lastTestResult = (TestResult) lastParsedElement;
 506  5
             lastTestResult.addComment(comment);
 507  
         }
 508  6
     }
 509  
 
 510  
     /**
 511  
      * Simply extracts the footer from the TAP line.
 512  
      * 
 513  
      * @param matcher REGEX Matcher.
 514  
      */
 515  
     protected void extractFooter(Matcher matcher) {
 516  1
         String text = matcher.group(1);
 517  1
         Footer footer = new Footer(text);
 518  
 
 519  1
         final String commentToken = matcher.group(2);
 520  
 
 521  1
         if (commentToken != null) {
 522  0
             String commentText = matcher.group(3);
 523  0
             final Comment comment = new Comment(commentText);
 524  0
             footer.setComment(comment);
 525  
         }
 526  
 
 527  1
         this.testSet.setFooter(footer);
 528  1
     }
 529  
 
 530  
     /*
 531  
      * (non-Javadoc)
 532  
      * @see org.tap4j.TapConsumer#parseTapStream(java.lang.String)
 533  
      */
 534  
     public TestSet parseTapStream(String tapStream) {
 535  
 
 536  4
         this.init();
 537  
 
 538  4
         Scanner scanner = null;
 539  
 
 540  
         try {
 541  4
             scanner = new Scanner(tapStream);
 542  4
             String line = null;
 543  
 
 544  21
             while (scanner.hasNextLine()) {
 545  17
                 line = scanner.nextLine();
 546  17
                 if (StringUtils.isNotEmpty(line)) {
 547  17
                     this.parseLine(line);
 548  
                 }
 549  
             }
 550  4
             this.postProcess();
 551  1
         } catch (Exception e) {
 552  1
             throw new ParserException("Error parsing TAP Stream: " +
 553  
                                       e.getMessage(), e);
 554  
         } finally {
 555  4
             if (scanner != null) {
 556  4
                 scanner.close();
 557  
             }
 558  
         }
 559  
 
 560  3
         return this.getTestSet();
 561  
 
 562  
     }
 563  
 
 564  
     /*
 565  
      * (non-Javadoc)
 566  
      * @see org.tap4j.TapConsumer#parseFile(java.io.File)
 567  
      */
 568  
     public TestSet parseFile(File tapFile) {
 569  
 
 570  13
         this.init();
 571  
 
 572  13
         Scanner scanner = null;
 573  
 
 574  
         try {
 575  13
             scanner = new Scanner(tapFile);
 576  13
             String line = null;
 577  
 
 578  168
             while (scanner.hasNextLine()) {
 579  156
                 line = scanner.nextLine();
 580  156
                 if (StringUtils.isNotBlank(line)) {
 581  155
                     this.parseLine(line);
 582  
                 }
 583  
             }
 584  12
             this.postProcess();
 585  2
         } catch (Exception e) {
 586  2
             throw new ParserException("Error parsing TAP Stream: " +
 587  
                                       e.getMessage(), e);
 588  
         } finally {
 589  13
             if (scanner != null) {
 590  13
                 scanner.close();
 591  
             }
 592  
         }
 593  
 
 594  11
         return this.getTestSet();
 595  
     }
 596  
 
 597  
     /**
 598  
          * 
 599  
          */
 600  
     private void setIndentationLevelIfNotDefined(String tapLine) {
 601  62
         if (this.isBaseIndentationAlreadyDefined() == Boolean.FALSE) {
 602  20
             this.baseIndentationLevel = this.getIndentationLevel(tapLine);
 603  
         }
 604  62
     }
 605  
 
 606  
     /**
 607  
      * Checks if the indentation is greater than the
 608  
      * {@link #baseIndentationLevel}
 609  
      * 
 610  
      * @param indentation indentation level
 611  
      * @throws org.tap4j.consumer.TapConsumerException if indentation is less
 612  
      *         then the {@link #baseIndentationLevel} .
 613  
      */
 614  
     private void checkIndentationLevel(int indentation, String tapLine) {
 615  53
         if (indentation < this.baseIndentationLevel) {
 616  3
             if (!this.currentlyInYAML &&
 617  
                 this.mementos.isEmpty() == Boolean.FALSE) {
 618  
                 while (!this.mementos.isEmpty() &&
 619  6
                        indentation < this.baseIndentationLevel) {
 620  4
                     this.loadMemento();
 621  
                 }
 622  
             } else {
 623  1
                 throw new ParserException("Invalid indentantion. " +
 624  
                                           "Check your TAP Stream. Line: " +
 625  
                                           tapLine);
 626  
             }
 627  
         }
 628  52
     }
 629  
 
 630  
     /**
 631  
      * Gets the indentation level of a line.
 632  
      * 
 633  
      * @param tapLine line.
 634  
      * @return indentation level of a line.
 635  
      */
 636  
     private int getIndentationLevel(String tapLine) {
 637  20
         int indentationLevel = 0;
 638  
 
 639  20
         final Matcher indentMatcher = INDENTANTION_PATTERN.matcher(tapLine);
 640  
 
 641  20
         if (indentMatcher.matches()) {
 642  20
             String spaces = indentMatcher.group(1);
 643  20
             indentationLevel = spaces.length();
 644  
         }
 645  20
         return indentationLevel;
 646  
     }
 647  
 
 648  
     /**
 649  
      * <p>
 650  
      * Checks if there is any diagnostic information on the diagnostic buffer.
 651  
      * </p>
 652  
      * <p>
 653  
      * If so, tries to parse it using snakeyaml.
 654  
      * </p>
 655  
      * 
 656  
      * @throws org.tap4j.consumer.TapConsumerException
 657  
      */
 658  
     private void checkAndParseTapDiagnostic() {
 659  
         // If we found any meta, then process it with SnakeYAML
 660  86
         if (diagnosticBuffer.length() > 0) {
 661  
 
 662  7
             if (this.lastParsedElement == null) {
 663  0
                 throw new ParserException(
 664  
                                           "Found diagnostic information without a previous TAP element.");
 665  
             }
 666  
 
 667  
             try {
 668  
                 // Iterable<?> metaIterable = (Iterable<?>)yaml.loadAll(
 669  
                 // diagnosticBuffer.toString() );
 670  
                 @SuppressWarnings("unchecked")
 671  7
                 Map<String, Object> metaIterable = (Map<String, Object>) yaml
 672  
                     .load(diagnosticBuffer.toString());
 673  7
                 this.lastParsedElement.setDiagnostic(metaIterable);
 674  0
             } catch (Exception ex) {
 675  0
                 throw new ParserException("Error parsing YAML [" +
 676  
                                           diagnosticBuffer.toString() + "]: " +
 677  
                                           ex.getMessage(), ex);
 678  7
             }
 679  
 
 680  7
             diagnosticBuffer = new StringBuilder();
 681  
         }
 682  86
     }
 683  
 
 684  
     /*
 685  
      * Checks if the Header was set.
 686  
      * @throws org.tap4j.consumer.TapConsumerException
 687  
      * @deprecated
 688  
      */
 689  
     // void checkHeader()
 690  
     // throws TapConsumerException
 691  
     // {
 692  
     // if ( this.header == null )
 693  
     // {
 694  
     // throw new TapConsumerException("Missing required TAP Header element.");
 695  
     // }
 696  
     // }
 697  
 
 698  
     /**
 699  
      * Appends a diagnostic line to diagnostic buffer. If the diagnostic line
 700  
      * contains --- or ... then it ignores this line. In the end of each line it
 701  
      * appends a break line.
 702  
      * 
 703  
      * @param diagnosticLine diagnostic line
 704  
      */
 705  
     private void appendTapLineToDiagnosticBuffer(String diagnosticLine) {
 706  79
         if (diagnosticLine.trim().equals("---") ||
 707  
             diagnosticLine.trim().equals("...")) {
 708  0
             return;
 709  
         }
 710  
 
 711  79
         if (this.currentlyInYAML) {
 712  79
             diagnosticBuffer.append(diagnosticLine);
 713  79
             diagnosticBuffer.append('\n');
 714  
         }
 715  79
     }
 716  
 
 717  
     /**
 718  
      * @return true if the base indentation is already defined, false otherwise.
 719  
      */
 720  
     protected boolean isBaseIndentationAlreadyDefined() {
 721  228
         return this.baseIndentationLevel >= 0;
 722  
     }
 723  
 
 724  
     /*
 725  
      * (non-Javadoc)
 726  
      * @see org.tap4j.consumer.DefaultTapConsumer#postProcess()
 727  
      */
 728  
     protected void postProcess() {
 729  16
         this.checkTAPPlanIsSet();
 730  14
         this.checkAndParseTapDiagnostic();
 731  14
     }
 732  
 
 733  
 }