7b2f096e6f649810389157be7fb6946c23dea4b9
[babeltrace.git] / tests / utils / python / tap / parser.py
1 # Copyright (c) 2016, Matt Layman
2
3 from io import StringIO
4 import re
5 import sys
6
7 from tap.directive import Directive
8 from tap.i18n import _
9 from tap.line import Bail, Diagnostic, Plan, Result, Unknown, Version
10
11
12 class Parser(object):
13 """A parser for TAP files and lines."""
14
15 # ok and not ok share most of the same characteristics.
16 result_base = r"""
17 \s* # Optional whitespace.
18 (?P<number>\d*) # Optional test number.
19 \s* # Optional whitespace.
20 (?P<description>[^#]*) # Optional description before #.
21 \#? # Optional directive marker.
22 \s* # Optional whitespace.
23 (?P<directive>.*) # Optional directive text.
24 """
25 ok = re.compile(r'^ok' + result_base, re.VERBOSE)
26 not_ok = re.compile(r'^not\ ok' + result_base, re.VERBOSE)
27 plan = re.compile(r"""
28 ^1..(?P<expected>\d+) # Match the plan details.
29 [^#]* # Consume any non-hash character to confirm only
30 # directives appear with the plan details.
31 \#? # Optional directive marker.
32 \s* # Optional whitespace.
33 (?P<directive>.*) # Optional directive text.
34 """, re.VERBOSE)
35 diagnostic = re.compile(r'^#')
36 bail = re.compile(r"""
37 ^Bail\ out!
38 \s* # Optional whitespace.
39 (?P<reason>.*) # Optional reason.
40 """, re.VERBOSE)
41 version = re.compile(r'^TAP version (?P<version>\d+)$')
42
43 TAP_MINIMUM_DECLARED_VERSION = 13
44
45 def parse_file(self, filename):
46 """Parse a TAP file to an iterable of tap.line.Line objects.
47
48 This is a generator method that will yield an object for each
49 parsed line. The file given by `filename` is assumed to exist.
50 """
51 return self.parse(open(filename, 'r'))
52
53 def parse_stdin(self):
54 """Parse a TAP stream from standard input.
55
56 Note: this has the side effect of closing the standard input
57 filehandle after parsing.
58 """
59 return self.parse(sys.stdin)
60
61 def parse_text(self, text):
62 """Parse a string containing one or more lines of TAP output."""
63 return self.parse(StringIO(text))
64
65 def parse(self, fh):
66 """Generate tap.line.Line objects, given a file-like object `fh`.
67
68 `fh` may be any object that implements both the iterator and
69 context management protocol (i.e. it can be used in both a
70 "with" statement and a "for...in" statement.)
71
72 Trailing whitespace and newline characters will be automatically
73 stripped from the input lines.
74 """
75 with fh:
76 for line in fh:
77 yield self.parse_line(line.rstrip())
78
79 def parse_line(self, text):
80 """Parse a line into whatever TAP category it belongs."""
81 match = self.ok.match(text)
82 if match:
83 return self._parse_result(True, match)
84
85 match = self.not_ok.match(text)
86 if match:
87 return self._parse_result(False, match)
88
89 if self.diagnostic.match(text):
90 return Diagnostic(text)
91
92 match = self.plan.match(text)
93 if match:
94 return self._parse_plan(match)
95
96 match = self.bail.match(text)
97 if match:
98 return Bail(match.group('reason'))
99
100 match = self.version.match(text)
101 if match:
102 return self._parse_version(match)
103
104 return Unknown()
105
106 def _parse_plan(self, match):
107 """Parse a matching plan line."""
108 expected_tests = int(match.group('expected'))
109 directive = Directive(match.group('directive'))
110
111 # Only SKIP directives are allowed in the plan.
112 if directive.text and not directive.skip:
113 return Unknown()
114
115 return Plan(expected_tests, directive)
116
117 def _parse_result(self, ok, match):
118 """Parse a matching result line into a result instance."""
119 return Result(
120 ok, match.group('number'), match.group('description').strip(),
121 Directive(match.group('directive')))
122
123 def _parse_version(self, match):
124 version = int(match.group('version'))
125 if version < self.TAP_MINIMUM_DECLARED_VERSION:
126 raise ValueError(_('It is an error to explicitly specify '
127 'any version lower than 13.'))
128 return Version(version)
This page took 0.031941 seconds and 4 git commands to generate.