OLD | NEW |
| (Empty) |
1 #! /usr/bin/python | |
2 # | |
3 # Protocol Buffers - Google's data interchange format | |
4 # Copyright 2015 Google Inc. All rights reserved. | |
5 # https://developers.google.com/protocol-buffers/ | |
6 # | |
7 # Redistribution and use in source and binary forms, with or without | |
8 # modification, are permitted provided that the following conditions are | |
9 # met: | |
10 # | |
11 # * Redistributions of source code must retain the above copyright | |
12 # notice, this list of conditions and the following disclaimer. | |
13 # * Redistributions in binary form must reproduce the above | |
14 # copyright notice, this list of conditions and the following disclaimer | |
15 # in the documentation and/or other materials provided with the | |
16 # distribution. | |
17 # * Neither the name of Google Inc. nor the names of its | |
18 # contributors may be used to endorse or promote products derived from | |
19 # this software without specific prior written permission. | |
20 # | |
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
24 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
25 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
26 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
27 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
28 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
29 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
30 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
31 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
32 | |
33 """Tests for pddm.py.""" | |
34 | |
35 import io | |
36 import unittest | |
37 | |
38 import pddm | |
39 | |
40 | |
41 class TestParsingMacros(unittest.TestCase): | |
42 | |
43 def testParseEmpty(self): | |
44 f = io.StringIO(u'') | |
45 result = pddm.MacroCollection(f) | |
46 self.assertEqual(len(result._macros), 0) | |
47 | |
48 def testParseOne(self): | |
49 f = io.StringIO(u"""PDDM-DEFINE foo( ) | |
50 body""") | |
51 result = pddm.MacroCollection(f) | |
52 self.assertEqual(len(result._macros), 1) | |
53 macro = result._macros.get('foo') | |
54 self.assertIsNotNone(macro) | |
55 self.assertEquals(macro.name, 'foo') | |
56 self.assertEquals(macro.args, tuple()) | |
57 self.assertEquals(macro.body, 'body') | |
58 | |
59 def testParseGeneral(self): | |
60 # Tests multiple defines, spaces in all places, etc. | |
61 f = io.StringIO(u""" | |
62 PDDM-DEFINE noArgs( ) | |
63 body1 | |
64 body2 | |
65 | |
66 PDDM-DEFINE-END | |
67 | |
68 PDDM-DEFINE oneArg(foo) | |
69 body3 | |
70 PDDM-DEFINE twoArgs( bar_ , baz ) | |
71 body4 | |
72 body5""") | |
73 result = pddm.MacroCollection(f) | |
74 self.assertEqual(len(result._macros), 3) | |
75 macro = result._macros.get('noArgs') | |
76 self.assertIsNotNone(macro) | |
77 self.assertEquals(macro.name, 'noArgs') | |
78 self.assertEquals(macro.args, tuple()) | |
79 self.assertEquals(macro.body, 'body1\nbody2\n') | |
80 macro = result._macros.get('oneArg') | |
81 self.assertIsNotNone(macro) | |
82 self.assertEquals(macro.name, 'oneArg') | |
83 self.assertEquals(macro.args, ('foo',)) | |
84 self.assertEquals(macro.body, 'body3') | |
85 macro = result._macros.get('twoArgs') | |
86 self.assertIsNotNone(macro) | |
87 self.assertEquals(macro.name, 'twoArgs') | |
88 self.assertEquals(macro.args, ('bar_', 'baz')) | |
89 self.assertEquals(macro.body, 'body4\nbody5') | |
90 # Add into existing collection | |
91 f = io.StringIO(u""" | |
92 PDDM-DEFINE another(a,b,c) | |
93 body1 | |
94 body2""") | |
95 result.ParseInput(f) | |
96 self.assertEqual(len(result._macros), 4) | |
97 macro = result._macros.get('another') | |
98 self.assertIsNotNone(macro) | |
99 self.assertEquals(macro.name, 'another') | |
100 self.assertEquals(macro.args, ('a', 'b', 'c')) | |
101 self.assertEquals(macro.body, 'body1\nbody2') | |
102 | |
103 def testParseDirectiveIssues(self): | |
104 test_list = [ | |
105 # Unknown directive | |
106 (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINED foo\nbaz', | |
107 'Hit a line with an unknown directive: '), | |
108 # End without begin | |
109 (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nPDDM-DEFINE-END\n', | |
110 'Got DEFINE-END directive without an active macro: '), | |
111 # Line not in macro block | |
112 (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE-END\nmumble\n', | |
113 'Hit a line that wasn\'t a directive and no open macro definition: '), | |
114 # Redefine macro | |
115 (u'PDDM-DEFINE foo()\nbody\nPDDM-DEFINE foo(a)\nmumble\n', | |
116 'Attempt to redefine macro: '), | |
117 ] | |
118 for idx, (input_str, expected_prefix) in enumerate(test_list, 1): | |
119 f = io.StringIO(input_str) | |
120 try: | |
121 result = pddm.MacroCollection(f) | |
122 self.fail('Should throw exception, entry %d' % idx) | |
123 except pddm.PDDMError as e: | |
124 self.assertTrue(e.message.startswith(expected_prefix), | |
125 'Entry %d failed: %r' % (idx, e)) | |
126 | |
127 def testParseBeginIssues(self): | |
128 test_list = [ | |
129 # 1. No name | |
130 (u'PDDM-DEFINE\nmumble', | |
131 'Failed to parse macro definition: '), | |
132 # 2. No name (with spaces) | |
133 (u'PDDM-DEFINE \nmumble', | |
134 'Failed to parse macro definition: '), | |
135 # 3. No open paren | |
136 (u'PDDM-DEFINE foo\nmumble', | |
137 'Failed to parse macro definition: '), | |
138 # 4. No close paren | |
139 (u'PDDM-DEFINE foo(\nmumble', | |
140 'Failed to parse macro definition: '), | |
141 # 5. No close paren (with args) | |
142 (u'PDDM-DEFINE foo(a, b\nmumble', | |
143 'Failed to parse macro definition: '), | |
144 # 6. No name before args | |
145 (u'PDDM-DEFINE (a, b)\nmumble', | |
146 'Failed to parse macro definition: '), | |
147 # 7. No name before args | |
148 (u'PDDM-DEFINE foo bar(a, b)\nmumble', | |
149 'Failed to parse macro definition: '), | |
150 # 8. Empty arg name | |
151 (u'PDDM-DEFINE foo(a, ,b)\nmumble', | |
152 'Empty arg name in macro definition: '), | |
153 (u'PDDM-DEFINE foo(a,,b)\nmumble', | |
154 'Empty arg name in macro definition: '), | |
155 # 10. Duplicate name | |
156 (u'PDDM-DEFINE foo(a,b,a,c)\nmumble', | |
157 'Arg name "a" used more than once in macro definition: '), | |
158 # 11. Invalid arg name | |
159 (u'PDDM-DEFINE foo(a b,c)\nmumble', | |
160 'Invalid arg name "a b" in macro definition: '), | |
161 (u'PDDM-DEFINE foo(a.b,c)\nmumble', | |
162 'Invalid arg name "a.b" in macro definition: '), | |
163 (u'PDDM-DEFINE foo(a-b,c)\nmumble', | |
164 'Invalid arg name "a-b" in macro definition: '), | |
165 (u'PDDM-DEFINE foo(a,b,c.)\nmumble', | |
166 'Invalid arg name "c." in macro definition: '), | |
167 # 15. Extra stuff after the name | |
168 (u'PDDM-DEFINE foo(a,c) foo\nmumble', | |
169 'Failed to parse macro definition: '), | |
170 (u'PDDM-DEFINE foo(a,c) foo)\nmumble', | |
171 'Failed to parse macro definition: '), | |
172 ] | |
173 for idx, (input_str, expected_prefix) in enumerate(test_list, 1): | |
174 f = io.StringIO(input_str) | |
175 try: | |
176 result = pddm.MacroCollection(f) | |
177 self.fail('Should throw exception, entry %d' % idx) | |
178 except pddm.PDDMError as e: | |
179 self.assertTrue(e.message.startswith(expected_prefix), | |
180 'Entry %d failed: %r' % (idx, e)) | |
181 | |
182 | |
183 class TestExpandingMacros(unittest.TestCase): | |
184 | |
185 def testExpandBasics(self): | |
186 f = io.StringIO(u""" | |
187 PDDM-DEFINE noArgs( ) | |
188 body1 | |
189 body2 | |
190 | |
191 PDDM-DEFINE-END | |
192 | |
193 PDDM-DEFINE oneArg(a) | |
194 body3 a | |
195 | |
196 PDDM-DEFINE-END | |
197 | |
198 PDDM-DEFINE twoArgs(b,c) | |
199 body4 b c | |
200 body5 | |
201 PDDM-DEFINE-END | |
202 | |
203 """) | |
204 mc = pddm.MacroCollection(f) | |
205 test_list = [ | |
206 (u'noArgs()', | |
207 'body1\nbody2\n'), | |
208 (u'oneArg(wee)', | |
209 'body3 wee\n'), | |
210 (u'twoArgs(having some, fun)', | |
211 'body4 having some fun\nbody5'), | |
212 # One arg, pass empty. | |
213 (u'oneArg()', | |
214 'body3 \n'), | |
215 # Two args, gets empty in each slot. | |
216 (u'twoArgs(, empty)', | |
217 'body4 empty\nbody5'), | |
218 (u'twoArgs(empty, )', | |
219 'body4 empty \nbody5'), | |
220 (u'twoArgs(, )', | |
221 'body4 \nbody5'), | |
222 ] | |
223 for idx, (input_str, expected) in enumerate(test_list, 1): | |
224 result = mc.Expand(input_str) | |
225 self.assertEqual(result, expected, | |
226 'Entry %d --\n Result: %r\n Expected: %r' % | |
227 (idx, result, expected)) | |
228 | |
229 def testExpandArgOptions(self): | |
230 f = io.StringIO(u""" | |
231 PDDM-DEFINE bar(a) | |
232 a-a$S-a$l-a$L-a$u-a$U | |
233 PDDM-DEFINE-END | |
234 """) | |
235 mc = pddm.MacroCollection(f) | |
236 | |
237 self.assertEqual(mc.Expand('bar(xYz)'), 'xYz- -xYz-xyz-XYz-XYZ') | |
238 self.assertEqual(mc.Expand('bar(MnoP)'), 'MnoP- -mnoP-mnop-MnoP-MNOP') | |
239 # Test empty | |
240 self.assertEqual(mc.Expand('bar()'), '-----') | |
241 | |
242 def testExpandSimpleMacroErrors(self): | |
243 f = io.StringIO(u""" | |
244 PDDM-DEFINE foo(a, b) | |
245 <a-z> | |
246 PDDM-DEFINE baz(a) | |
247 a - a$z | |
248 """) | |
249 mc = pddm.MacroCollection(f) | |
250 test_list = [ | |
251 # 1. Unknown macro | |
252 (u'bar()', | |
253 'No macro named "bar".'), | |
254 (u'bar(a)', | |
255 'No macro named "bar".'), | |
256 # 3. Arg mismatch | |
257 (u'foo()', | |
258 'Expected 2 args, got: "foo()".'), | |
259 (u'foo(a b)', | |
260 'Expected 2 args, got: "foo(a b)".'), | |
261 (u'foo(a,b,c)', | |
262 'Expected 2 args, got: "foo(a,b,c)".'), | |
263 # 6. Unknown option in expansion | |
264 (u'baz(mumble)', | |
265 'Unknown arg option "a$z" while expanding "baz(mumble)".'), | |
266 ] | |
267 for idx, (input_str, expected_err) in enumerate(test_list, 1): | |
268 try: | |
269 result = mc.Expand(input_str) | |
270 self.fail('Should throw exception, entry %d' % idx) | |
271 except pddm.PDDMError as e: | |
272 self.assertEqual(e.message, expected_err, | |
273 'Entry %d failed: %r' % (idx, e)) | |
274 | |
275 def testExpandReferences(self): | |
276 f = io.StringIO(u""" | |
277 PDDM-DEFINE StartIt() | |
278 foo(abc, def) | |
279 foo(ghi, jkl) | |
280 PDDM-DEFINE foo(a, b) | |
281 bar(a, int) | |
282 bar(b, NSString *) | |
283 PDDM-DEFINE bar(n, t) | |
284 - (t)n; | |
285 - (void)set##n$u##:(t)value; | |
286 | |
287 """) | |
288 mc = pddm.MacroCollection(f) | |
289 expected = """- (int)abc; | |
290 - (void)setAbc:(int)value; | |
291 | |
292 - (NSString *)def; | |
293 - (void)setDef:(NSString *)value; | |
294 | |
295 - (int)ghi; | |
296 - (void)setGhi:(int)value; | |
297 | |
298 - (NSString *)jkl; | |
299 - (void)setJkl:(NSString *)value; | |
300 """ | |
301 self.assertEqual(mc.Expand('StartIt()'), expected) | |
302 | |
303 def testCatchRecursion(self): | |
304 f = io.StringIO(u""" | |
305 PDDM-DEFINE foo(a, b) | |
306 bar(1, a) | |
307 bar(2, b) | |
308 PDDM-DEFINE bar(x, y) | |
309 foo(x, y) | |
310 """) | |
311 mc = pddm.MacroCollection(f) | |
312 try: | |
313 result = mc.Expand('foo(A,B)') | |
314 self.fail('Should throw exception, entry %d' % idx) | |
315 except pddm.PDDMError as e: | |
316 self.assertEqual(e.message, | |
317 'Found macro recusion, invoking "foo(1, A)":\n...while ex
panding "bar(1, A)".\n...while expanding "foo(A,B)".') | |
318 | |
319 | |
320 class TestParsingSource(unittest.TestCase): | |
321 | |
322 def testBasicParse(self): | |
323 test_list = [ | |
324 # 1. no directives | |
325 (u'a\nb\nc', | |
326 (3,) ), | |
327 # 2. One define | |
328 (u'a\n//%PDDM-DEFINE foo()\n//%body\nc', | |
329 (1, 2, 1) ), | |
330 # 3. Two defines | |
331 (u'a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE bar()\n//%body2\nc', | |
332 (1, 4, 1) ), | |
333 # 4. Two defines with ends | |
334 (u'a\n//%PDDM-DEFINE foo()\n//%body\n//%PDDM-DEFINE-END\n' | |
335 u'//%PDDM-DEFINE bar()\n//%body2\n//%PDDM-DEFINE-END\nc', | |
336 (1, 6, 1) ), | |
337 # 5. One expand, one define (that runs to end of file) | |
338 (u'a\n//%PDDM-EXPAND foo()\nbody\n//%PDDM-EXPAND-END\n' | |
339 u'//%PDDM-DEFINE bar()\n//%body2\n', | |
340 (1, 1, 2) ), | |
341 # 6. One define ended with an expand. | |
342 (u'a\nb\n//%PDDM-DEFINE bar()\n//%body2\n' | |
343 u'//%PDDM-EXPAND bar()\nbody2\n//%PDDM-EXPAND-END\n', | |
344 (2, 2, 1) ), | |
345 # 7. Two expands (one end), one define. | |
346 (u'a\n//%PDDM-EXPAND foo(1)\nbody\n//%PDDM-EXPAND foo(2)\nbody2\n//%PDDM-E
XPAND-END\n' | |
347 u'//%PDDM-DEFINE foo()\n//%body2\n', | |
348 (1, 2, 2) ), | |
349 ] | |
350 for idx, (input_str, line_counts) in enumerate(test_list, 1): | |
351 f = io.StringIO(input_str) | |
352 sf = pddm.SourceFile(f) | |
353 sf._ParseFile() | |
354 self.assertEqual(len(sf._sections), len(line_counts), | |
355 'Entry %d -- %d != %d' % | |
356 (idx, len(sf._sections), len(line_counts))) | |
357 for idx2, (sec, expected) in enumerate(zip(sf._sections, line_counts), 1): | |
358 self.assertEqual(sec.num_lines_captured, expected, | |
359 'Entry %d, section %d -- %d != %d' % | |
360 (idx, idx2, sec.num_lines_captured, expected)) | |
361 | |
362 def testErrors(self): | |
363 test_list = [ | |
364 # 1. Directive within expansion | |
365 (u'//%PDDM-EXPAND a()\n//%PDDM-BOGUS', | |
366 'Ran into directive ("//%PDDM-BOGUS", line 2) while in "//%PDDM-EXPAND a(
)".'), | |
367 (u'//%PDDM-EXPAND a()\n//%PDDM-DEFINE a()\n//%body\n', | |
368 'Ran into directive ("//%PDDM-DEFINE", line 2) while in "//%PDDM-EXPAND a
()".'), | |
369 # 3. Expansion ran off end of file | |
370 (u'//%PDDM-EXPAND a()\na\nb\n', | |
371 'Hit the end of the file while in "//%PDDM-EXPAND a()".'), | |
372 # 4. Directive within define | |
373 (u'//%PDDM-DEFINE a()\n//%body\n//%PDDM-BOGUS', | |
374 'Ran into directive ("//%PDDM-BOGUS", line 3) while in "//%PDDM-DEFINE a(
)".'), | |
375 (u'//%PDDM-DEFINE a()\n//%body\n//%PDDM-EXPAND-END a()', | |
376 'Ran into directive ("//%PDDM-EXPAND-END", line 3) while in "//%PDDM-DEFI
NE a()".'), | |
377 # 6. Directives that shouldn't start sections | |
378 (u'a\n//%PDDM-DEFINE-END a()\n//a\n', | |
379 'Unexpected line 2: "//%PDDM-DEFINE-END a()".'), | |
380 (u'a\n//%PDDM-EXPAND-END a()\n//a\n', | |
381 'Unexpected line 2: "//%PDDM-EXPAND-END a()".'), | |
382 (u'//%PDDM-BOGUS\n//a\n', | |
383 'Unexpected line 1: "//%PDDM-BOGUS".'), | |
384 ] | |
385 for idx, (input_str, expected_err) in enumerate(test_list, 1): | |
386 f = io.StringIO(input_str) | |
387 try: | |
388 pddm.SourceFile(f)._ParseFile() | |
389 self.fail('Should throw exception, entry %d' % idx) | |
390 except pddm.PDDMError as e: | |
391 self.assertEqual(e.message, expected_err, | |
392 'Entry %d failed: %r' % (idx, e)) | |
393 | |
394 class TestProcessingSource(unittest.TestCase): | |
395 | |
396 def testBasics(self): | |
397 input_str = u""" | |
398 //%PDDM-IMPORT-DEFINES ImportFile | |
399 foo | |
400 //%PDDM-EXPAND mumble(abc) | |
401 //%PDDM-EXPAND-END | |
402 bar | |
403 //%PDDM-EXPAND mumble(def) | |
404 //%PDDM-EXPAND mumble(ghi) | |
405 //%PDDM-EXPAND-END | |
406 baz | |
407 //%PDDM-DEFINE mumble(a_) | |
408 //%a_: getName(a_) | |
409 """ | |
410 input_str2 = u""" | |
411 //%PDDM-DEFINE getName(x_) | |
412 //%do##x_$u##(int x_); | |
413 | |
414 """ | |
415 expected = u""" | |
416 //%PDDM-IMPORT-DEFINES ImportFile | |
417 foo | |
418 //%PDDM-EXPAND mumble(abc) | |
419 // This block of code is generated, do not edit it directly. | |
420 | |
421 abc: doAbc(int abc); | |
422 //%PDDM-EXPAND-END mumble(abc) | |
423 bar | |
424 //%PDDM-EXPAND mumble(def) | |
425 // This block of code is generated, do not edit it directly. | |
426 | |
427 def: doDef(int def); | |
428 //%PDDM-EXPAND mumble(ghi) | |
429 // This block of code is generated, do not edit it directly. | |
430 | |
431 ghi: doGhi(int ghi); | |
432 //%PDDM-EXPAND-END (2 expansions) | |
433 baz | |
434 //%PDDM-DEFINE mumble(a_) | |
435 //%a_: getName(a_) | |
436 """ | |
437 expected_stripped = u""" | |
438 //%PDDM-IMPORT-DEFINES ImportFile | |
439 foo | |
440 //%PDDM-EXPAND mumble(abc) | |
441 //%PDDM-EXPAND-END mumble(abc) | |
442 bar | |
443 //%PDDM-EXPAND mumble(def) | |
444 //%PDDM-EXPAND mumble(ghi) | |
445 //%PDDM-EXPAND-END (2 expansions) | |
446 baz | |
447 //%PDDM-DEFINE mumble(a_) | |
448 //%a_: getName(a_) | |
449 """ | |
450 def _Resolver(name): | |
451 self.assertEqual(name, 'ImportFile') | |
452 return io.StringIO(input_str2) | |
453 f = io.StringIO(input_str) | |
454 sf = pddm.SourceFile(f, _Resolver) | |
455 sf.ProcessContent() | |
456 self.assertEqual(sf.processed_content, expected) | |
457 # Feed it through and nothing should change. | |
458 f2 = io.StringIO(sf.processed_content) | |
459 sf2 = pddm.SourceFile(f2, _Resolver) | |
460 sf2.ProcessContent() | |
461 self.assertEqual(sf2.processed_content, expected) | |
462 self.assertEqual(sf2.processed_content, sf.processed_content) | |
463 # Test stripping (with the original input and expanded version). | |
464 f2 = io.StringIO(input_str) | |
465 sf2 = pddm.SourceFile(f2) | |
466 sf2.ProcessContent(strip_expansion=True) | |
467 self.assertEqual(sf2.processed_content, expected_stripped) | |
468 f2 = io.StringIO(sf.processed_content) | |
469 sf2 = pddm.SourceFile(f2, _Resolver) | |
470 sf2.ProcessContent(strip_expansion=True) | |
471 self.assertEqual(sf2.processed_content, expected_stripped) | |
472 | |
473 def testProcessFileWithMacroParseError(self): | |
474 input_str = u""" | |
475 foo | |
476 //%PDDM-DEFINE mumble(a_) | |
477 //%body | |
478 //%PDDM-DEFINE mumble(x_) | |
479 //%body2 | |
480 | |
481 """ | |
482 f = io.StringIO(input_str) | |
483 sf = pddm.SourceFile(f) | |
484 try: | |
485 sf.ProcessContent() | |
486 self.fail('Should throw exception, entry %d' % idx) | |
487 except pddm.PDDMError as e: | |
488 self.assertEqual(e.message, | |
489 'Attempt to redefine macro: "PDDM-DEFINE mumble(x_)"\n' | |
490 '...while parsing section that started:\n' | |
491 ' Line 3: //%PDDM-DEFINE mumble(a_)') | |
492 | |
493 def testProcessFileWithExpandError(self): | |
494 input_str = u""" | |
495 foo | |
496 //%PDDM-DEFINE mumble(a_) | |
497 //%body | |
498 //%PDDM-EXPAND foobar(x_) | |
499 //%PDDM-EXPAND-END | |
500 | |
501 """ | |
502 f = io.StringIO(input_str) | |
503 sf = pddm.SourceFile(f) | |
504 try: | |
505 sf.ProcessContent() | |
506 self.fail('Should throw exception, entry %d' % idx) | |
507 except pddm.PDDMError as e: | |
508 self.assertEqual(e.message, | |
509 'No macro named "foobar".\n' | |
510 '...while expanding "foobar(x_)" from the section that' | |
511 ' started:\n Line 5: //%PDDM-EXPAND foobar(x_)') | |
512 | |
513 | |
514 if __name__ == '__main__': | |
515 unittest.main() | |
OLD | NEW |