|
1 # This file contains test code for the formatting of parsed statements back to |
|
2 # make file "source." It essentially verifies to to_source() functions |
|
3 # scattered across the tree. |
|
4 |
|
5 import glob |
|
6 import logging |
|
7 import os.path |
|
8 import unittest |
|
9 |
|
10 from pymake.data import Expansion |
|
11 from pymake.data import StringExpansion |
|
12 from pymake.functions import BasenameFunction |
|
13 from pymake.functions import SubstitutionRef |
|
14 from pymake.functions import VariableRef |
|
15 from pymake.functions import WordlistFunction |
|
16 from pymake.parserdata import Include |
|
17 from pymake.parserdata import SetVariable |
|
18 from pymake.parser import parsestring |
|
19 from pymake.parser import SyntaxError |
|
20 |
|
21 class TestBase(unittest.TestCase): |
|
22 pass |
|
23 |
|
24 class VariableRefTest(TestBase): |
|
25 def test_string_name(self): |
|
26 e = StringExpansion('foo', None) |
|
27 v = VariableRef(None, e) |
|
28 |
|
29 self.assertEqual(v.to_source(), '$(foo)') |
|
30 |
|
31 def test_special_variable(self): |
|
32 e = StringExpansion('<', None) |
|
33 v = VariableRef(None, e) |
|
34 |
|
35 self.assertEqual(v.to_source(), '$<') |
|
36 |
|
37 def test_expansion_simple(self): |
|
38 e = Expansion() |
|
39 e.appendstr('foo') |
|
40 e.appendstr('bar') |
|
41 |
|
42 v = VariableRef(None, e) |
|
43 |
|
44 self.assertEqual(v.to_source(), '$(foobar)') |
|
45 |
|
46 class StandardFunctionTest(TestBase): |
|
47 def test_basename(self): |
|
48 e1 = StringExpansion('foo', None) |
|
49 v = VariableRef(None, e1) |
|
50 e2 = Expansion(None) |
|
51 e2.appendfunc(v) |
|
52 |
|
53 b = BasenameFunction(None) |
|
54 b.append(e2) |
|
55 |
|
56 self.assertEqual(b.to_source(), '$(basename $(foo))') |
|
57 |
|
58 def test_wordlist(self): |
|
59 e1 = StringExpansion('foo', None) |
|
60 e2 = StringExpansion('bar ', None) |
|
61 e3 = StringExpansion(' baz', None) |
|
62 |
|
63 w = WordlistFunction(None) |
|
64 w.append(e1) |
|
65 w.append(e2) |
|
66 w.append(e3) |
|
67 |
|
68 self.assertEqual(w.to_source(), '$(wordlist foo,bar , baz)') |
|
69 |
|
70 def test_curly_brackets(self): |
|
71 e1 = Expansion(None) |
|
72 e1.appendstr('foo') |
|
73 |
|
74 e2 = Expansion(None) |
|
75 e2.appendstr('foo ( bar') |
|
76 |
|
77 f = WordlistFunction(None) |
|
78 f.append(e1) |
|
79 f.append(e2) |
|
80 |
|
81 self.assertEqual(f.to_source(), '${wordlist foo,foo ( bar}') |
|
82 |
|
83 class StringExpansionTest(TestBase): |
|
84 def test_simple(self): |
|
85 e = StringExpansion('foobar', None) |
|
86 self.assertEqual(e.to_source(), 'foobar') |
|
87 |
|
88 e = StringExpansion('$var', None) |
|
89 self.assertEqual(e.to_source(), '$var') |
|
90 |
|
91 def test_escaping(self): |
|
92 e = StringExpansion('$var', None) |
|
93 self.assertEqual(e.to_source(escape_variables=True), '$$var') |
|
94 |
|
95 e = StringExpansion('this is # not a comment', None) |
|
96 self.assertEqual(e.to_source(escape_comments=True), |
|
97 'this is \# not a comment') |
|
98 |
|
99 def test_empty(self): |
|
100 e = StringExpansion('', None) |
|
101 self.assertEqual(e.to_source(), '') |
|
102 |
|
103 e = StringExpansion(' ', None) |
|
104 self.assertEqual(e.to_source(), ' ') |
|
105 |
|
106 class ExpansionTest(TestBase): |
|
107 def test_single_string(self): |
|
108 e = Expansion() |
|
109 e.appendstr('foo') |
|
110 |
|
111 self.assertEqual(e.to_source(), 'foo') |
|
112 |
|
113 def test_multiple_strings(self): |
|
114 e = Expansion() |
|
115 e.appendstr('hello') |
|
116 e.appendstr('world') |
|
117 |
|
118 self.assertEqual(e.to_source(), 'helloworld') |
|
119 |
|
120 def test_string_escape(self): |
|
121 e = Expansion() |
|
122 e.appendstr('$var') |
|
123 self.assertEqual(e.to_source(), '$var') |
|
124 self.assertEqual(e.to_source(escape_variables=True), '$$var') |
|
125 |
|
126 e = Expansion() |
|
127 e.appendstr('foo') |
|
128 e.appendstr(' $bar') |
|
129 self.assertEqual(e.to_source(escape_variables=True), 'foo $$bar') |
|
130 |
|
131 class SubstitutionRefTest(TestBase): |
|
132 def test_simple(self): |
|
133 name = StringExpansion('foo', None) |
|
134 c = StringExpansion('%.c', None) |
|
135 o = StringExpansion('%.o', None) |
|
136 s = SubstitutionRef(None, name, c, o) |
|
137 |
|
138 self.assertEqual(s.to_source(), '$(foo:%.c=%.o)') |
|
139 |
|
140 class SetVariableTest(TestBase): |
|
141 def test_simple(self): |
|
142 v = SetVariable(StringExpansion('foo', None), '=', 'bar', None, None) |
|
143 self.assertEqual(v.to_source(), 'foo = bar') |
|
144 |
|
145 def test_multiline(self): |
|
146 s = 'hello\nworld' |
|
147 foo = StringExpansion('FOO', None) |
|
148 |
|
149 v = SetVariable(foo, '=', s, None, None) |
|
150 |
|
151 self.assertEqual(v.to_source(), 'define FOO\nhello\nworld\nendef') |
|
152 |
|
153 def test_multiline_immediate(self): |
|
154 source = 'define FOO :=\nhello\nworld\nendef' |
|
155 |
|
156 statements = parsestring(source, 'foo.mk') |
|
157 self.assertEqual(statements.to_source(), source) |
|
158 |
|
159 def test_target_specific(self): |
|
160 foo = StringExpansion('FOO', None) |
|
161 bar = StringExpansion('BAR', None) |
|
162 |
|
163 v = SetVariable(foo, '+=', 'value', None, bar) |
|
164 |
|
165 self.assertEqual(v.to_source(), 'BAR: FOO += value') |
|
166 |
|
167 class IncludeTest(TestBase): |
|
168 def test_include(self): |
|
169 e = StringExpansion('rules.mk', None) |
|
170 i = Include(e, True, False) |
|
171 self.assertEqual(i.to_source(), 'include rules.mk') |
|
172 |
|
173 i = Include(e, False, False) |
|
174 self.assertEqual(i.to_source(), '-include rules.mk') |
|
175 |
|
176 class IfdefTest(TestBase): |
|
177 def test_simple(self): |
|
178 source = 'ifdef FOO\nbar := $(value)\nendif' |
|
179 |
|
180 statements = parsestring(source, 'foo.mk') |
|
181 self.assertEqual(statements[0].to_source(), source) |
|
182 |
|
183 def test_nested(self): |
|
184 source = 'ifdef FOO\nifdef BAR\nhello = world\nendif\nendif' |
|
185 |
|
186 statements = parsestring(source, 'foo.mk') |
|
187 self.assertEqual(statements[0].to_source(), source) |
|
188 |
|
189 def test_negation(self): |
|
190 source = 'ifndef FOO\nbar += value\nendif' |
|
191 |
|
192 statements = parsestring(source, 'foo.mk') |
|
193 self.assertEqual(statements[0].to_source(), source) |
|
194 |
|
195 class IfeqTest(TestBase): |
|
196 def test_simple(self): |
|
197 source = 'ifeq ($(foo),bar)\nhello = $(world)\nendif' |
|
198 |
|
199 statements = parsestring(source, 'foo.mk') |
|
200 self.assertEqual(statements[0].to_source(), source) |
|
201 |
|
202 def test_negation(self): |
|
203 source = 'ifneq (foo,bar)\nhello = world\nendif' |
|
204 |
|
205 statements = parsestring(source, 'foo.mk') |
|
206 self.assertEqual(statements.to_source(), source) |
|
207 |
|
208 class ConditionBlocksTest(TestBase): |
|
209 def test_mixed_conditions(self): |
|
210 source = 'ifdef FOO\nifeq ($(FOO),bar)\nvar += $(value)\nendif\nendif' |
|
211 |
|
212 statements = parsestring(source, 'foo.mk') |
|
213 self.assertEqual(statements.to_source(), source) |
|
214 |
|
215 def test_extra_statements(self): |
|
216 source = 'ifdef FOO\nF := 1\nifdef BAR\nB += 1\nendif\nC = 1\nendif' |
|
217 |
|
218 statements = parsestring(source, 'foo.mk') |
|
219 self.assertEqual(statements.to_source(), source) |
|
220 |
|
221 def test_whitespace_preservation(self): |
|
222 source = "ifeq ' x' 'x '\n$(error stripping)\nendif" |
|
223 |
|
224 statements = parsestring(source, 'foo.mk') |
|
225 self.assertEqual(statements.to_source(), source) |
|
226 |
|
227 source = 'ifneq (x , x)\n$(error stripping)\nendif' |
|
228 statements = parsestring(source, 'foo.mk') |
|
229 self.assertEqual(statements.to_source(), |
|
230 'ifneq (x,x)\n$(error stripping)\nendif') |
|
231 |
|
232 class MakefileCorupusTest(TestBase): |
|
233 """Runs the make files from the pymake corpus through the formatter. |
|
234 |
|
235 All the above tests are child's play compared to this. |
|
236 """ |
|
237 |
|
238 # Our reformatting isn't perfect. We ignore files with known failures until |
|
239 # we make them work. |
|
240 # TODO Address these formatting corner cases. |
|
241 _IGNORE_FILES = [ |
|
242 # We are thrown off by backslashes at end of lines. |
|
243 'comment-parsing.mk', |
|
244 'escape-chars.mk', |
|
245 'include-notfound.mk', |
|
246 ] |
|
247 |
|
248 def _get_test_files(self): |
|
249 ourdir = os.path.dirname(os.path.abspath(__file__)) |
|
250 |
|
251 for makefile in glob.glob(os.path.join(ourdir, '*.mk')): |
|
252 if os.path.basename(makefile) in self._IGNORE_FILES: |
|
253 continue |
|
254 |
|
255 source = None |
|
256 with open(makefile, 'rU') as fh: |
|
257 source = fh.read() |
|
258 |
|
259 try: |
|
260 yield (makefile, source, parsestring(source, makefile)) |
|
261 except SyntaxError: |
|
262 continue |
|
263 |
|
264 def test_reparse_consistency(self): |
|
265 for filename, source, statements in self._get_test_files(): |
|
266 reformatted = statements.to_source() |
|
267 |
|
268 # We should be able to parse the reformatted source fine. |
|
269 new_statements = parsestring(reformatted, filename) |
|
270 |
|
271 # If we do the formatting again, the representation shouldn't |
|
272 # change. i.e. the only lossy change should be the original |
|
273 # (whitespace and some semantics aren't preserved). |
|
274 reformatted_again = new_statements.to_source() |
|
275 self.assertEqual(reformatted, reformatted_again, |
|
276 '%s has lossless reformat.' % filename) |
|
277 |
|
278 self.assertEqual(len(statements), len(new_statements)) |
|
279 |
|
280 for i in xrange(0, len(statements)): |
|
281 original = statements[i] |
|
282 formatted = new_statements[i] |
|
283 |
|
284 self.assertEqual(original, formatted, '%s %d: %s != %s' % (filename, |
|
285 i, original, formatted)) |
|
286 |
|
287 if __name__ == '__main__': |
|
288 logging.basicConfig(level=logging.DEBUG) |
|
289 unittest.main() |