0
|
1 import sys
|
|
2 import logging
|
|
3 import yaml
|
|
4 import pytest
|
|
5 from fontaine.document import (
|
|
6 FontaineSceneElement,
|
|
7 TYPE_ACTION, TYPE_CHARACTER, TYPE_DIALOG, TYPE_PARENTHETICAL)
|
|
8 from fontaine.parser import FontaineParser, FontaineParserError
|
|
9
|
|
10
|
|
11 def pytest_addoption(parser):
|
|
12 parser.addoption(
|
|
13 '--log-debug',
|
|
14 action='store_true',
|
|
15 help="Sets the Fontaine logger to output debug info to stdout.")
|
|
16
|
|
17
|
|
18 def pytest_configure(config):
|
|
19 if config.getoption('--log-debug'):
|
|
20 hdl = logging.StreamHandler(stream=sys.stdout)
|
|
21 logging.getLogger('fontaine').addHandler(hdl)
|
|
22 logging.getLogger('fontaine').setLevel(logging.DEBUG)
|
|
23
|
|
24
|
|
25 def pytest_collect_file(parent, path):
|
|
26 if path.ext == '.yaml' and path.basename.startswith("test"):
|
|
27 return FontaineScriptTestFile(path, parent)
|
|
28 return None
|
|
29
|
|
30
|
|
31 def assert_scenes(actual, scenes):
|
|
32 assert len(actual) == len(scenes)
|
|
33 for a, e in zip(actual, scenes):
|
|
34 assert_scene(a, e[0], e[1:])
|
|
35
|
|
36
|
|
37 def assert_scene(actual, header, paragraphs):
|
|
38 if header is not None:
|
|
39 assert actual.header == header
|
|
40 assert len(actual.paragraphs) == len(paragraphs)
|
|
41 for a, e in zip(actual.paragraphs, paragraphs):
|
|
42 assert_paragraph(a, e)
|
|
43
|
|
44
|
|
45 def assert_paragraph(actual, expected):
|
|
46 if isinstance(expected, str):
|
|
47 assert isinstance(actual, FontaineSceneElement)
|
|
48 assert actual.type == TYPE_ACTION
|
|
49 assert actual.text == expected
|
|
50 elif isinstance(expected, FontaineSceneElement):
|
|
51 assert isinstance(actual, FontaineSceneElement)
|
|
52 assert actual.type == expected.type
|
|
53 assert actual.text == expected.text
|
|
54 else:
|
|
55 raise NotImplementedError("Don't know what this is: %s" % expected)
|
|
56
|
|
57
|
|
58 def _c(name):
|
|
59 return FontaineSceneElement(TYPE_CHARACTER, name)
|
|
60
|
|
61
|
|
62 def _p(text):
|
|
63 return FontaineSceneElement(TYPE_PARENTHETICAL, text)
|
|
64
|
|
65
|
|
66 def _d(text):
|
|
67 return FontaineSceneElement(TYPE_DIALOG, text)
|
|
68
|
|
69
|
|
70 class FontaineScriptTestFile(pytest.File):
|
|
71 def collect(self):
|
|
72 spec = yaml.load_all(self.fspath.open(encoding='utf8'))
|
|
73 for i, item in enumerate(spec):
|
|
74 name = '%s_%d' % (self.fspath.basename, i)
|
|
75 if 'test_name' in item:
|
|
76 name += '_%s' % item['test_name']
|
|
77 yield FontaineScriptTestItem(name, self, item)
|
|
78
|
|
79
|
|
80 class FontaineScriptTestItem(pytest.Item):
|
|
81 def __init__(self, name, parent, spec):
|
|
82 super().__init__(name, parent)
|
|
83 self.spec = spec
|
|
84
|
|
85 def reportinfo(self):
|
|
86 return self.fspath, 0, "fontaine script test: %s" % self.name
|
|
87
|
|
88 def runtest(self):
|
|
89 intext = self.spec.get('in')
|
|
90 expected = self.spec.get('out')
|
|
91 title = self.spec.get('title')
|
|
92 if intext is None or expected is None:
|
|
93 raise Exception("No 'in' or 'out' specified.")
|
|
94
|
|
95 parser = FontaineParser()
|
|
96 doc = parser.parseString(intext)
|
|
97 if title is not None:
|
|
98 assert title == doc.title_values
|
|
99 assert_scenes(doc.scenes, make_scenes(expected))
|
|
100
|
|
101 def repr_failure(self, excinfo):
|
|
102 if isinstance(excinfo.value, FontaineParserError):
|
|
103 return ('\n'.join(
|
|
104 ['Parser error:', str(excinfo.value)]))
|
|
105 return super().repr_failure(excinfo)
|
|
106
|
|
107
|
|
108 def make_scenes(spec):
|
|
109 if not isinstance(spec, list):
|
|
110 raise Exception("Script specs must be lists.")
|
|
111
|
|
112 out = []
|
|
113 cur_header = None
|
|
114 cur_paras = []
|
|
115
|
|
116 for item in spec:
|
|
117 token = item[:1]
|
|
118 if token == '.':
|
|
119 if cur_header or cur_paras:
|
|
120 out.append([cur_header] + cur_paras)
|
|
121 cur_header = item[1:]
|
|
122 elif token == '!':
|
|
123 cur_paras.append(item[1:])
|
|
124 elif token == '@':
|
|
125 cur_paras.append(_c(item[1:]))
|
|
126 elif token == '=':
|
|
127 cur_paras.append(_d(item[1:]))
|
|
128 elif token == '_':
|
|
129 cur_paras.append(_p(item[1:]))
|
|
130 else:
|
|
131 raise Exception("Unknown token: %s" % token)
|
|
132 if cur_header or cur_paras:
|
|
133 out.append([cur_header] + cur_paras)
|
|
134 return out
|