potluck.tests.test_mast

Tests of the mast module.

test_mast.py

  1"""
  2Tests of the mast module.
  3
  4test_mast.py
  5"""
  6
  7import ast
  8
  9from .. import mast, mast_utils
 10
 11TESTS = [
 12    (True, 1, '2', '2'),
 13    (False, 0, '2', '3'),
 14    (True, 1, 'x', '_a_'),
 15    (True, 1, '(x, y)', '(_a_, _b_)'),
 16    (False, 0, '(x, y)', '(_a_, _a_)'),
 17    (True, 1, '(x, x)', '(_a_, _a_)'),
 18    (True, 4, 'max(7,3)', '_x_'),
 19    (True, 1, 'max(7,3)', 'max(7,3)'),
 20    (False, 0, 'max(7,2)', 'max(7,3)'),
 21    (True, 1, 'max(7,3,5)', 'max(___args___)'),
 22    (False, 1, 'min(max(7,3),5)', 'max(___args___)'),
 23    (True, 2, 'min(max(7,3),5)', '_f_(___args___)'),
 24    (True, 1, 'min(max(7,3),5)', 'min(max(___maxargs___),___minargs___)'),
 25    (True, 1, 'max()', 'max(___args___)'),
 26    (True, 1, 'max(4)', 'max(4,___args___)'),
 27    (True, 1, 'max(4,5,6)', 'max(4,___args___)'),
 28    (True, 1, '"hello %s" % x', '_a_str_ % _b_'),
 29    (False, 0, 'y % x', '_a_str_ % _b_'),
 30    (False, 0, '7 % x', '_a_str_ % _b_'),
 31    (True, 1, '3', '_a_int_'),
 32    (True, 1, '3.4', '_a_float_'),
 33    (False, 0, '3', '_a_float_'),
 34    (False, 0, '3.4', '_a_int_'),
 35    (True, 1, 'True', '_a_bool_'),
 36    (True, 1, 'False', '_a_bool_'),
 37    (True, 1, 'None', 'None'),
 38    # node vard can bind statements or exprs based on context.
 39    (True, 7, 'print("hello"+str(3))', '_x_'), # 6 => 7 in Python 3
 40    (True, 1, 'print("hello"+str(3))', 'print(_x_)'),
 41    (True, 1, 'print(1)', 'print(_x_, ___args___)'),
 42    (False, 0, 'print(1)', 'print(_x_, _y_, ___args___)'),
 43    (False, 0, 'print(1, 2)', 'print(_x_)'),
 44    (True, 1, 'print(1, 2)', 'print(_x_, ___args___)'),
 45    (True, 1, 'print(1, 2)', 'print(_x_, _y_, ___args___)'),
 46    (True, 1, 'print(1, 2, 3)', 'print(_x_, ___args___)'),
 47    (True, 1, 'print(1, 2, 3)', 'print(_x_, _y_, ___args___)'),
 48    (True, 1,
 49     '''
 50def f(x):
 51    return 17
 52     ''',
 53     '''
 54def f(_a_):
 55    return _b_
 56     '''),
 57    (True, 1,
 58     '''
 59def f(x):
 60    return x
 61     ''',
 62     '''
 63def f(_a_):
 64    return _b_
 65     '''),
 66    (True, 1,
 67     '''
 68def f(x):
 69    return x
 70     ''',
 71     '''
 72def f(_a_):
 73    return _a_
 74     '''),
 75    (False, 0,
 76     '''
 77def f(x):
 78    return 17
 79     ''',
 80     '''
 81def f(_a_):
 82    return _a_
 83     '''),
 84    (True, 1,
 85     '''
 86def f(x):
 87    return 17
 88     ''',
 89     '''
 90def f(_x_):
 91    return _y_
 92     '''),
 93    (True, 1,
 94     '''
 95def f(x,y):
 96    print('hi')
 97    return x
 98     ''',
 99     '''
100def f(_x_,_y_):
101    print('hi')
102    return _x_
103     '''),
104    (True, 1,
105     '''
106def f(x,y):
107    print('hi')
108    return x
109     ''',
110     '''
111def _f_(_x_,_y_):
112    print('hi')
113    return _x_
114     '''),
115    (False, 0,
116     '''
117def f(x,y):
118    print('hi')
119    return y
120     ''',
121     '''
122def f(_a_,_b_):
123    print('hi')
124    return _a_
125     '''),
126    (False, 0, 'x', 'y'),
127    (True, 1,
128     '''
129def f(x,y):
130    print('hi')
131    return y
132     ''',
133     '''
134def _f_(_x_,_y_):
135    ___
136    return _y_
137     '''),
138    (True, 1,
139     '''
140def f(x,y):
141    print('hi')
142    print('world')
143    print('bye')
144    return y
145     ''',
146     '''
147def _f_(_x_,_y_):
148    ___stmts___
149    return _y_
150     '''),
151    (False, 0,
152     '''
153def f(x,y):
154    print('hi')
155    print('world')
156    x = 4
157    print('really')
158    y = 7
159    print('bye')
160    return y
161     ''',
162     '''
163def _f_(_x_,_y_):
164    ___stmts___
165    print(_z_)
166    _y_ = _a_int_
167    return _y_
168     '''),
169    (True, 1,
170     '''
171def f(x,y):
172    print('hi')
173    print('world')
174    x = 4
175    print('really')
176    y = 7
177    print('bye')
178    return y
179     ''',
180     '''
181def _f_(_x_,_y_):
182    ___stmts___
183    print(_z_)
184    _y_ = _a_int_
185    ___more___
186    return _y_
187     '''),
188    (True, 1,
189     '''
190def f(x,y):
191    print('hi')
192    print('world')
193    x = 4
194    print('really')
195    y = 7
196    print('bye')
197    return y
198     ''',
199     '''
200def _f_(_x_,_y_):
201    ___stmts___
202    print(_a_)
203    _b_ = _c_
204    ___more___
205    return _d_
206     '''),
207    (False, 1,
208     '''
209def f(x,y):
210    print('hi')
211    print('world')
212    x = 4
213    print('really')
214    y = 7
215    print('bye')
216    return y
217     ''',
218     '''
219___stmts___
220print(_a_)
221_b_ = _c_
222___more___
223return _d_
224     '''),
225    (True, 1,
226     '''
227def eyes():
228    eye1 = Layer()
229    eye2 = Layer()
230    face = Layer()
231    face.add(eye)
232    face.add(eye2)
233    return face
234     ''',
235     '''
236def eyes():
237    ___
238    _face_ = Layer()
239    ___
240    return _face_
241     '''),
242    (True, 1, '1 == 2', '2 == 1'),
243    (True, 1, '1 <= 2', '2 >= 1'),
244    (False, 0, '1 <= 2', '2 <= 1'),
245    (False, 0, 'f() <= 2', '2 >= f()'),
246    # Hmnm, is this the semantics we want for `and`?
247    (True, 1, 'a and b and c', 'b and a and c'),
248    (True, 1, '(a == b) == (b == c)', '(a == b) == (c == b)'),
249    (True, 1, '(a and b) and c', 'a and (b and c)'),
250    (True, 1, 'a and b', 'a and b'),
251    (True, 1, 'g == "a" or g == "b" or g == "c"',
252     '_g_ == _a_ or _g_ == _b_ or _c_ == _g_'),
253    (True, 1, '''
254x = 1
255y = 2
256''', '''
257x = 1
258y = 2
259'''),
260    (True, 1, '''
261x = 1
262y = 2
263''', '''
264___
265y = 2
266'''),
267    (True, 1, '''
268x = 1
269if (a or b or c):
270    return True
271else:
272    return False
273     ''',
274     '''
275___
276if _:
277    return _a_bool_
278else:
279    return _b_bool_
280___
281     '''),
282    (True, 1, '''
283if (a or b or c):
284    return True
285else:
286    return False
287     ''',
288     '''
289if _:
290    return _a_bool_
291else:
292    return _b_bool_
293     '''),
294    (True, 1, '''
295x = 1
296if (a or b or c):
297    return True
298else:
299    return False
300     ''',
301     '''
302___
303if _:
304    return _a_bool_
305else:
306    return _b_bool_
307     '''),
308    (False, 1, '''
309def f():
310    if (a or b or c):
311        return True
312    else:
313        return False
314     ''',
315     '''
316___
317if _:
318    return _a_bool_
319else:
320    return _b_bool_
321___
322     '''),
323    (False, 1, '''
324def isValidGesture(gesture):
325    if (gesture == 'rock' or gesture == 'paper' or gesture == 'scissors'):
326        return True
327    else:
328        return False
329        ''', '''
330if _:
331    return _a_bool_
332else:
333    return _b_bool_
334     '''),
335    (False, 1, '''
336def isValidGesture(gesture):
337    print('blah')
338    if (gesture == 'rock' or gesture == 'paper' or gesture == 'scissors'):
339        return True
340    return False
341        ''', '''
342___
343if _:
344    return _a_bool_
345return _b_bool_
346     '''),
347
348    (False, 1, '''
349def isValidGesture(gesture):
350    print('blah')
351    if (gesture == 'rock' or gesture == 'paper' or gesture == 'scissors'):
352        return True
353    return False
354    ''', '''
355if _:
356    return _a_bool_
357return _b_bool_
358    '''),
359    (False, 1, '''
360def isValidGesture(gesture):
361    if (gesture == 'rock' or gesture == 'paper' or gesture == 'scissors'):
362        return True
363    return False
364     ''', '''
365if _:
366    return _a_bool_
367return _b_bool_
368     '''),
369    (False, 1, '''
370def isValidGesture(gesture):
371    if (gesture == 'rock' or gesture == 'paper' or gesture == 'scissors'):
372        x = True
373    x = False
374    return x
375     ''', '''
376if _:
377    _x_ = _a_bool_
378_x_ = _b_bool_
379     '''),
380    (True, 1, '''
381def isValidGesture(gesture):
382    if (gesture == 'rock' or gesture == 'paper' or gesture == 'scissors'):
383        return True
384    return False
385     ''', '''
386def _(_):
387    if _:
388        return _a_bool_
389    return _b_bool_
390     '''),
391    (True, 1, 'x, y = f()', '___vars___ = f()'),
392    (True, 1, 'x, y = f()', '_vars_ = f()'),
393    (True, 1, 'f(a=1, b=2)', 'f(b=2, a=1)'),
394    (True, 1, '''
395def f(x,y):
396    """with a docstring"""
397    if level <= 0:
398        pass
399    else:
400        fd(3)
401        lt(90)
402    ''', '''
403def f(_, _):
404    ___
405    if _:
406        ___t___
407    else:
408        ___e___
409    ___s___
410     '''),
411    (True, 1, '''
412class A(B):
413    def f(self, x):
414        pass
415     ''', 'class _(_): ___'),
416    # (True, True,
417    #  'for x, y in f():\n    ___',
418    #  'for ___vars___ in f():\n    ___'),
419    (False, 0, 'drawLs(size/2, level - 1)', 'drawLs(_size_/2.0, _)'),
420    (False, 0, '2', '2.0'),
421    (False, 0, '2', '_d_float_'),
422    (True, 1, '''
423def keepFirstLetter(phrase):
424    """Returns a new string that contains only the first occurrence of a
425    letter from the original phrase.
426    The first letter occurrence can be upper or lower case.
427    Non-alpha characters (such as punctuations and space) are left unchanged.
428    """
429    #this list holds lower-cased versions of all of the letters already used
430    usedCharacters = []
431
432    finalPhrase = ""
433    for n in phrase:
434        if n.isalpha():
435            #we need to create a temporary lower-cased version of the letter,
436            #so that we can check and see if we've seen an upper or lower-cased
437            #version of this letter before
438            tempN = n.lower()
439            if tempN not in usedCharacters:
440                usedCharacters.append(tempN)
441                #but we need to add the original n, so that we can preserve
442                #if it was upper cased or not
443                finalPhrase = finalPhrase + n
444
445        #this adds all non-letter characters into the final phrase list
446        else:
447            finalPhrase = finalPhrase + n
448
449    return finalPhrase
450    ''', '''
451def _(___):
452    ___
453    _acc_ = ""
454    ___
455    for _ in _:
456        ___
457    return _acc_
458    ___
459    '''),
460    (False, # Pattern should not match program
461     1, # Pattern should be found within program
462     # Program
463     '''
464def keepFirstLetter(phrase):
465    #this list holds lower-cased versions of all of the letters already used
466    usedCharacters = []
467
468    finalPhrase = ""
469    for n in phrase:
470        if n.isalpha():
471            #we need to create a temporary lower-cased version of the letter,
472            #so that we can check and see if we've seen an upper or lower-cased
473            #version of this letter before
474            tempN = n.lower()
475            if tempN not in usedCharacters:
476                usedCharacters.append(tempN)
477                #but we need to add the original n, so that we can preserve
478                #if it was upper cased or not
479                finalPhrase = finalPhrase + n
480
481        #this adds all non-letter characters into the final phrase list
482        else:
483            finalPhrase = finalPhrase + n
484
485    return finalPhrase
486     ''',
487     # Pattern
488     '''
489___prefix___
490_acc_ = ""
491___middle___
492for _ in _:
493    ___
494return _acc_
495___suffix___
496     '''),
497    (True, 1, '''
498a = 1
499b = 2
500c = 3
501     ''', '''___; _x_ = _n_'''),
502    (False, # Pattern should not match program
503     1, # Pattern should be found within program
504     # Program
505     '''
506def f(a):
507    x = ""
508    return x
509     ''',
510     # Pattern
511     '''
512_acc_ = ""
513return _acc_
514     '''),
515
516    # Treatment of elses:
517    (True, 1, 'if x: print(1)', 'if _: _'),
518    (False, 0, 'if x: print(1)\nelse: pass', 'if _: _'),
519    (True, 1, 'if x: print(1)\nelse: pass', 'if _: _\nelse: ___'),
520    (False, 0, 'if x: print(1)', '''
521if _: _
522else:
523    _
524    ___
525     '''),
526    (True, 1, 'if x: print(1)\nelse: pass', '''
527if _: _
528else:
529    _
530    ___
531     '''),
532
533    # If ExpandExplicitElsePattern is used:
534    # (False, 0, 'if x: print(1)', 'if _: _\nelse: ___'),
535
536    # If ExpandExplicitElsePattern is NOT used:
537    (True, 1, 'if x: print(1)', 'if _: _\nelse: ___'),
538
539    # Keyword arguments
540    (True, 1, 'f(a=1)', 'f(a=1)'),
541    (True, 1, 'f(a=1)', 'f(_kw_=1)'),
542    (True, 1, 'f(a=1)', 'f(_kw_=_arg_)'),
543    (True, 1, 'f(a=1)', 'f(_=1)'),
544    (True, 1, 'f(a=1)', 'f(_=_arg_)'),
545    (True, 1, 'f(a=1, b=2)', 'f(_x_=_, _y_=_)'),
546    (True, 1, 'f(a=1, b=2)', 'f(_x_=2, _y_=_)'),
547    (False, 0, 'f(a=1, b=2)', 'f(_x_=_)'),
548    (False, 0, 'f(a=1, b=2)', 'f(_x_=_, _y_=_, _z_=_)'),
549    (False, 0, 'f(a=1, b=2)', 'f(_x_=2, _y_=2)'),
550    (True, 1, 'f(a=1, b=2)', 'f(_x_=_, b=_)'),
551    (True, 1, 'f(a=1, b=2)', 'f(b=_, _x_=_)'),
552    (True, 1, 'f(a=1+1, b=1+1)', 'f(_c_=_x_+_x_, _d_=_y_+_y_)'),
553    (True, 1, 'f(a=1+1, b=2+2)', 'f(_c_=_x_+_x_, _d_=_y_+_y_)'),
554    (True, 1, 'f(a=1+2, b=2+1)', 'f(_c_=_x_+_y_, _d_=_y_+_x_)'),
555    (True, 1, 'f(a=1+1, b=1+1)', 'f(_c_=_x_+_x_, _d_=_x_+_x_)'),
556    (False, 0, 'f(a=1+1, b=2+2)', 'f(_c_=_x_+_x_, _d_=_x_+_x_)'),
557    (True, 1, 'f(a=1, b=2)', 'f(___=_)'),
558    (True, 1, 'f(a=1, b=2)', 'f(___kwargs___=_)'),
559    (True, 1, 'f(a=1, b=2)', 'f(___kwargs___=_, b=_)'),
560    (True, 1, 'f(a=1, b=2)', 'f(b=_, ___kwargs___=_)'),
561    (True, 1, 'f(a=1, b=2)', 'f(___kwargs___=_, a=_, b=_)'),
562    (True, 1, 'f(a=1, b=2)', 'f(a=_, b=_, ___kwargs___=_)'),
563    (True, 1, 'f(a=1, b=2)', 'f(___kwargs___=_, b=_, a=_)'),
564    (True, 1, 'f(a=1, b=2)', 'f(b=_, a=_, ___kwargs___=_)'),
565    (True, 1, 'f(a=1, b=2)', 'f(a=_, ___kwargs___=_, b=_)'),
566    (True, 1, 'f(a=1, b=2)', 'f(b=_, ___kwargs___=_, a=_)'),
567    (True, 1, 'b = 7; f(a=1, b=2)', '_x_ = _; f(_x_=_, _y_=_)'),
568    (False, 0, 'b = 7; f(a=1, b=2)', '_x_ = _; f(_x_=1, _y_=_)'),
569
570    # and/or set wildcards
571    (True, 1, 'x or y or z', '_x_ or ___rest___'),
572    (True, 1, 'x or y or z', '_x_ or _y_ or ___rest___'),
573    (True, 1, 'x or y or z', '_x_ or _y_ or _z_ or ___rest___'),
574
575    # kwarg matching
576    (True, 1, 'def f(x=3):\n  return x', 'def _f_(x=3):\n  return x'),
577    (True, 1, 'def f(x=3):\n  return x', 'def _f_(_=3):\n  return _'),
578    (True, 1, 'def f(x=3):\n  return x', 'def _f_(_=3):\n  ___'),
579    (True, 1, 'def f(x=3):\n  return x', 'def _f_(_x_=3):\n  return _x_'),
580    (True, 1,
581     'def f(y=7):\n  return y', 'def _f_(_x_=_y_):\n  return _x_'),
582    (False, 0, 'def f(x=3):\n  return x', 'def _f_(_y_=7):\n  return _x_'),
583
584    # Succeeds!
585    (True, 1, 'def f(x=12):\n  return x', 'def _f_(_=_):\n  ___'),
586
587    # Should match because ___ has no default
588    (True, 1, 'def f(x=17):\n  return x', 'def _f_(___, _=17):\n  ___'),
589
590    # Multiple kwargs
591    (True, 1, 'def f(x=3, y=4):\n  return x', 'def _f_(_=3, _=4):\n  ___'),
592    (True, 1, 'def f(x=5, y=6):\n  return x', 'def _f_(_=_, _=_):\n  ___'),
593    # ___ doesn't match kwargs
594    (False, 0, 'def f(x=7, y=8):\n  return x', 'def _f_(___):\n  ___'),
595
596    # Exact matching of kwarg expressions
597    (True, 1, 'def f(x=y+3):\n  return x', 'def _f_(_=y+3):\n  ___'),
598
599    # Matching of kw-only args
600    (True, 1,
601     'def f(*a, x=5):\n  return x',
602     'def _f_(*_, _x_=5):\n  return _x_'),
603    # ___ does not match *_
604    (False, 0,
605     'def f(*a, x=6):\n  return x',
606     'def _f_(___, _x_=6):\n  return _x_'),
607
608    # Multiple kw-only args
609    (True, 1,
610     'def f(*a, x=5, y=6):\n  return x, y',
611     'def _f_(*_, _x_=5, _y_=6):\n  return _x_, _y_'),
612    (False, 0,
613     'def f(*a, x=7, y=8):\n  return x, y',
614     'def _f_(___, _x_=7, _y_=8):\n  return _x_, _y_'),
615
616    # Function with docstring (must use ast.get_docstring!)
617    (False, 0, 'def f():\n  """docstring"""', 'def _f_(___):\n  _a_str_'),
618
619    # Function with docstring (using ast.get_docstring)
620    (True, 1,
621     'def f(x):\n  """doc1"""\n  return x',
622     'def _f_(___):\n  ___',
623     lambda node, env: ast.get_docstring(node) is not None),
624
625    # Function without docstring (using ast.get_docstring)
626    (False, 0,
627     'def f(x):\n  """doc2"""\n  return x',
628     'def _f_(___):\n  ___',
629     lambda node, env:(
630         ast.get_docstring(node) is None
631      or ast.get_docstring(node).strip() == ''
632     )),
633    (True, 1,
634     'def f(x):\n  """"""\n  return x',
635     'def _f_(___):\n  ___',
636     lambda node, env:(
637         ast.get_docstring(node) is None
638      or ast.get_docstring(node).strip() == ''
639     )),
640    (True, 1,
641     'def nodoc(x):\n  return x',
642     'def _f_(___):\n  ___',
643     lambda node, env:(
644         ast.get_docstring(node) is None
645      or ast.get_docstring(node).strip() == ''
646     )),
647
648    # TODO: Recursive matching of kwarg expressions
649    (True, 1, 'def f(x=y+3):\n  return x', 'def _f_(_=_+3):\n  ___'),
650
651    # Function with multiple normal arguments
652    (True, 1, 'def f(x, y, z):\n  return x', 'def _f_(___):\n  ___'),
653
654    # Matching redundant elif conditions
655    (
656      True,
657      1,
658      'if x == 3:\n  return x\nelif not x == 3 and x > 5:\n  return x-2',
659      'if _cond_:\n  ___\nelif not _cond_ and ___:\n  ___'
660    ),
661    ( # order matters
662      False,
663      0,
664      'if x == 3:\n  return x\nelif x > 5 and not x == 3:\n  return x-2',
665      'if _cond_:\n  ___\nelif not _cond_ and ___:\n  ___'
666    ),
667    ( # not == is not the same as !=
668      False,
669      0,
670      'if x == 3:\n  return x\nelif x > 5 and x != 3:\n  return x-2',
671      'if _cond_:\n  ___\nelif not _cond_ and ___:\n  ___'
672    ),
673    ( # not == is not the same as !=
674      True,
675      1,
676      'if x == 3:\n  return x\nelif x > 5 and not x == 3:\n  return x-2',
677      'if _cond_:\n  ___\nelif ___ and not _cond_:\n  ___'
678    ),
679    ( # not == is not the same as !=
680      True,
681      1,
682      'if x == 3:\n  return x\nelif x > 5 and x != 3:\n  return x-2',
683      'if _n_ == _v_:\n  ___\nelif ___ and _n_ != _v_:\n  ___'
684    ),
685    ( # extra conditions do matter!
686      False,
687      0,
688      'if x == 3:\n  return x\nelif not x == 3 and x > 5:\n  return x-2\n'
689    + 'elif not x == 3 and x <= 5 and x < 0:\n  return 0',
690      'if _cond_:\n  ___\nelif not _cond_ and ___:\n  ___'
691    ),
692    ( # match extra conditions:
693      True,
694      1,
695      'if x == 3:\n  return x\nelif not x == 3 and x > 5:\n  return x-2\n'
696    + 'elif not x == 3 and x <= 5 and x < 0:\n  return 0',
697      'if _cond_:\n  ___\nelif not _cond_ and ___:\n  ___\nelif _:\n  ___'
698    ),
699    ( # number of conditions must match exactly:
700      False,
701      0,
702      'if x == 3:\n  return x\nelif not x == 3 and x > 5:\n  return x-2\n'
703    + 'elif not x == 3 and x <= 5 and x < 0:\n  return 0\n'
704    + 'elif not x == 3 and x <= 5 and x >= 0 and x == 1:\n  return 1.5',
705      'if _cond_:\n  ___\nelif not _cond_ and ___:\n  ___\nelif _:\n  ___'
706    ),
707    ( # matching with elif:
708      True,
709      2,
710      'if x < 0:\n  x += 1\nelif x < 10:\n  x += 0.5\nelse:\n  x += 0.25',
711      'if _:\n  ___\nelse:\n  ___'
712    ),
713    ( # left/right branches are both okay
714      True,
715      1,
716      'x == 3',
717      '3 == x',
718    ),
719    ( # order does matter for some operators
720     False,
721     0,
722     '1 + 2 + 3',
723     '3 + 2 + 1'
724    ),
725    ( # but not for others
726     True,
727     1,
728     '1 and 2 and 3',
729     '3 and 2 and 1'
730    ),
731
732    # Import matching
733    (True, 1, 'import math', 'import _'),
734    (False, 0, 'import math as m', 'import _'),
735    (True, 1, 'import math as m', 'import _ as _'),
736    (True, 1, 'import math as m', 'import _ as _'),
737    (True, 1, 'import math as m', 'import math as _'),
738    (True, 1, 'import math as m', 'import _ as m'),
739    (True, 1, 'import math as m', 'import math as m'),
740    (True, 1, 'import math, io', 'import ___'),
741    (True, 1, 'import math, io', 'import math, ___'),
742    (True, 1, 'import math, io', 'import io, ___'),
743    (False, 0, 'from math import cos, sin', 'import math'),
744    (False, 0, 'from math import cos, sin', 'import _'),
745    (False, 0, 'from math import cos, sin', 'import ___'),
746    (True, 1, 'from math import cos, sin', 'from _ import ___'),
747    (True, 1, 'from math import cos, sin', 'from _ import _x_, sin'),
748    (True, 1, 'from math import cos, sin', 'from _ import cos, _x_'),
749    (True, 1, 'from math import cos, sin', 'from _ import _x_, cos'),
750    (True, 1, 'from math import cos, sin', 'from _ import sin, _x_'),
751    (True, 1, 'from math import cos, sin', 'from _ import _x_, ___'),
752    # Note: two bindings, but only one node match
753    (True, 1, 'from math import cos, sin', 'from math import ___'),
754    (True, 1, 'from math import cos, sin', 'from math import cos, sin'),
755    (True, 1, 'from math import cos, sin', 'from math import sin, cos'),
756
757    # Try/except matching
758    (
759        True, 1,
760        'try:\n  pass\nexcept ValueError as e:\n  pass',
761        'try:\n  ___\nexcept _ as e:\n  ___',
762    ), # Note: it's horrible that 'e' has to match exactly here...
763    (
764        True, 1,
765        'try:\n  pass\nexcept ValueError:\n  pass\nexcept TypeError:\n pass',
766        'try:\n  ___\nexcept _:\n  ___\nexcept _:\n  ___',
767    ),
768    # Note: doesn't feel great that we need to match order/number of
769    # excepts, as well as presence/absence of as clause for each.
770    (
771        True, 1,
772        'try:\n  pass\nexcept ValueError:\n  pass\nfinally:\n pass',
773        'try:\n  ___\nexcept _:\n  ___\nfinally:\n  ___',
774    ),
775    (
776        True, 1,
777        'try:\n  pass\nfinally:\n pass',
778        'try:\n  ___\nfinally:\n  ___',
779    ),
780
781    # With matching
782    (
783        True, 1,
784        'with open("a", "r") as fin:\n  fin.read()',
785        'with _ as _:\n  ___',
786    ),
787    (
788        True, 1,
789        'with handler:\n  code',
790        'with _:\n  ___',
791    ),
792    (
793        True, 1,
794        'with one, two as x:\n  code',
795        'with _, _ as _:\n  ___',
796    ),
797    # Note: Kinda sucks that we've got to match ordering & number of
798    # withitems, etc. TODO: allow sequence vars in withitems?
799]
800
801# These tests fail, but in the future, maybe they could pass?
802FAILING = [
803    # TODO: Fails probably because of docstring issues?
804    (True, 1,
805     '''
806def f(x,y):
807    """I have a docstring.
808    It is a couple lines long."""
809    eye = Layer()
810    if eye:
811        face = Layer()
812    else:
813        face = Layer()
814    eye2 = Layer()
815    face.add(eye)
816    face.add(eye2)
817    return face
818     ''',
819     '''
820def f(___args___):
821    eye = Layer()
822    ___
823     '''),
824
825    # TODO: Fails because we compare all defaults as one block
826    # Zero extra keyword arguments:
827    (True, 1, 'def f(x=17):\n  return x', 'def _f_(_=17, ___=_):\n  ___'),
828
829    # TODO: Fails because of default matching bug
830    (True, 1, 'def f(x=9, y=10):\n  return x', 'def _f_(___=_):\n  ___'),
831
832    # TODO: This fails because the 'name' field of an ast.ExceptHandler
833    # is just a string, and node_is_bindable returns False for that. We
834    # ideally need some way of saying that a string node is bindable as a
835    # Name(id=value, ctx=None) IF the node is a 'name' filed of an
836    # ast.ExceptHandler, but that's not easy to figure out...
837    (
838        True, 1,
839        'try:\n  pass\nexcept ValueError as e:\n  pass',
840        'try:\n  ___\nexcept _ as _:\n  ___',
841    )
842]
843
844
845# TODO: Get this to work!!!
846def _test_import_findall_envs():
847    """
848    Tests # of environment matches w/ findall & a mix of scalar/set vars.
849    Note that this DOESN'T work for 3+ imports, and that's a bug...
850    """
851    node = mast.parse("from math import cos, sin, tan")
852    pat = mast.parse_pattern("from _ import _x_, ___")
853    results = list(mast.findall(node, pat))
854    assert len(results) == 1
855    mn, me = results[0]
856    assert isinstance(mn, ast.ImportFrom)
857    assert mn.module == "math"
858    assert mn.level == 0
859    print("names:", [e['x'].id for e in me])
860    assert len(me) == 3
861    assert all(len(env) == 1 for env in me)
862    assert all('x' in env for env in me)
863    xs = [env['x'] for env in me]
864    assert len([x for x in xs if x.id == "cos"]) == 1
865    assert len([x for x in xs if x.id == "sin"]) == 1
866    assert len([x for x in xs if x.id == "tan"]) == 1
867
868
869def test_two_aliases():
870    """
871    Tests matching a list of two aliases against different permutations
872    of a scalar + set var.
873    """
874    node = [
875        ast.alias(name="cos", asname=None),
876        ast.alias(name="sin", asname=None)
877    ]
878    pat1 = [
879        ast.alias(name="_x_", asname=None),
880        ast.alias(name="___", asname=None)
881    ]
882    pat2 = [
883        ast.alias(name="___", asname=None),
884        ast.alias(name="_x_", asname=None)
885    ]
886
887    m1 = list(mast.imatches(node, pat1, mast_utils.Some({}), True))
888    m2 = list(mast.imatches(node, pat2, mast_utils.Some({}), True))
889
890    assert len(m1) == 1
891    assert len(m2) == 1
892    e1 = m1[0]
893    e2 = m2[0]
894    assert len(e1) == 1
895    assert len(e2) == 1
896    assert 'x' in e1
897    assert 'x' in e2
898    x1 = e1['x']
899    x2 = e2['x']
900    assert x1.id == 'cos'
901    assert x2.id == 'sin'
902
903
904def test_mast():
905    """
906    Runs all of the TESTS.
907    """
908    for test_spec in TESTS:
909        run_test(*test_spec)
910
911
912def run_test(
913    expect_match,
914    expect_count,
915    src,
916    pattern,
917    matchpred=mast.predtrue
918):
919    """
920    Runs a test of the match, find, and count functions using a given
921    source string to match against and pattern to match. A match
922    predicate may also be provided to test that functionality, but is
923    optional.
924
925    You must specify whether a match is expected for the full pattern,
926    and the number of matches expected in count mode.
927    """
928    # Create & reduce the node to match against
929    node = mast.parse(src)
930    node = (
931        node.body[0].value
932        if type(node.body[0]) == ast.Expr and len(node.body) == 1
933        else (
934            node.body[0]
935            if len(node.body) == 1
936            else node.body
937        )
938    )
939
940    # Create our pattern node
941    pat_node = mast.parse_pattern(pattern)
942
943    # Messages to include when we assert for context
944    baggage = ''
945    baggage += f'Program: {src} => {mast.dump(node)}\n'
946    if isinstance(node, ast.FunctionDef):
947        baggage += f'Docstring: {ast.get_docstring(node)}\n'
948
949    baggage += f'Pattern: {pattern} => {mast.dump(pat_node)}\n'
950
951    if matchpred != mast.predtrue:
952        baggage += f'Matchpred: {matchpred.__name__}\n'
953
954    # gen = False
955    assert (
956        bool(mast.match(node, pat_node, gen=False, matchpred=matchpred))
957     == expect_match
958    ), baggage
959
960    # gen = True
961    assert (
962        bool(
963            mast_utils.takeone(
964                mast.match(node, pat_node, gen=True, matchpred=matchpred)
965            )
966        )
967     == expect_match
968    ), baggage
969
970    opt = mast.find(node, pat_node, matchpred=matchpred)
971    assert bool(opt) == (0 < expect_count), baggage
972
973    c = mast.count(node, pat_node, matchpred=matchpred)
974    assert c == expect_count, baggage
975
976    nmatches = 0
977    for (node, envs) in mast.findall(node, pat_node, matchpred=matchpred):
978        # find should return first findall result.
979        if nmatches == 0:
980            fnode, fenvs = opt.get()
981            # Note: because of arg -> name conversion, == doesn't work on
982            # the envs, since the name nodes produced will be the same,
983            # but they won't be == to each other!
984
985            assert fnode == node, baggage
986            assert [
987                {k: mast.dump(env[k]) for k in env}
988                for env in envs
989            ] == [
990                {k: mast.dump(env[k]) for k in env}
991                for env in fenvs
992            ], baggage
993        nmatches += 1
994        pass
995
996    # count should count the same number of matches that findall finds.
997    assert nmatches == c, baggage
def test_two_aliases():
870def test_two_aliases():
871    """
872    Tests matching a list of two aliases against different permutations
873    of a scalar + set var.
874    """
875    node = [
876        ast.alias(name="cos", asname=None),
877        ast.alias(name="sin", asname=None)
878    ]
879    pat1 = [
880        ast.alias(name="_x_", asname=None),
881        ast.alias(name="___", asname=None)
882    ]
883    pat2 = [
884        ast.alias(name="___", asname=None),
885        ast.alias(name="_x_", asname=None)
886    ]
887
888    m1 = list(mast.imatches(node, pat1, mast_utils.Some({}), True))
889    m2 = list(mast.imatches(node, pat2, mast_utils.Some({}), True))
890
891    assert len(m1) == 1
892    assert len(m2) == 1
893    e1 = m1[0]
894    e2 = m2[0]
895    assert len(e1) == 1
896    assert len(e2) == 1
897    assert 'x' in e1
898    assert 'x' in e2
899    x1 = e1['x']
900    x2 = e2['x']
901    assert x1.id == 'cos'
902    assert x2.id == 'sin'

Tests matching a list of two aliases against different permutations of a scalar + set var.

def test_mast():
905def test_mast():
906    """
907    Runs all of the TESTS.
908    """
909    for test_spec in TESTS:
910        run_test(*test_spec)

Runs all of the TESTS.

def run_test( expect_match, expect_count, src, pattern, matchpred=<function predtrue>):
913def run_test(
914    expect_match,
915    expect_count,
916    src,
917    pattern,
918    matchpred=mast.predtrue
919):
920    """
921    Runs a test of the match, find, and count functions using a given
922    source string to match against and pattern to match. A match
923    predicate may also be provided to test that functionality, but is
924    optional.
925
926    You must specify whether a match is expected for the full pattern,
927    and the number of matches expected in count mode.
928    """
929    # Create & reduce the node to match against
930    node = mast.parse(src)
931    node = (
932        node.body[0].value
933        if type(node.body[0]) == ast.Expr and len(node.body) == 1
934        else (
935            node.body[0]
936            if len(node.body) == 1
937            else node.body
938        )
939    )
940
941    # Create our pattern node
942    pat_node = mast.parse_pattern(pattern)
943
944    # Messages to include when we assert for context
945    baggage = ''
946    baggage += f'Program: {src} => {mast.dump(node)}\n'
947    if isinstance(node, ast.FunctionDef):
948        baggage += f'Docstring: {ast.get_docstring(node)}\n'
949
950    baggage += f'Pattern: {pattern} => {mast.dump(pat_node)}\n'
951
952    if matchpred != mast.predtrue:
953        baggage += f'Matchpred: {matchpred.__name__}\n'
954
955    # gen = False
956    assert (
957        bool(mast.match(node, pat_node, gen=False, matchpred=matchpred))
958     == expect_match
959    ), baggage
960
961    # gen = True
962    assert (
963        bool(
964            mast_utils.takeone(
965                mast.match(node, pat_node, gen=True, matchpred=matchpred)
966            )
967        )
968     == expect_match
969    ), baggage
970
971    opt = mast.find(node, pat_node, matchpred=matchpred)
972    assert bool(opt) == (0 < expect_count), baggage
973
974    c = mast.count(node, pat_node, matchpred=matchpred)
975    assert c == expect_count, baggage
976
977    nmatches = 0
978    for (node, envs) in mast.findall(node, pat_node, matchpred=matchpred):
979        # find should return first findall result.
980        if nmatches == 0:
981            fnode, fenvs = opt.get()
982            # Note: because of arg -> name conversion, == doesn't work on
983            # the envs, since the name nodes produced will be the same,
984            # but they won't be == to each other!
985
986            assert fnode == node, baggage
987            assert [
988                {k: mast.dump(env[k]) for k in env}
989                for env in envs
990            ] == [
991                {k: mast.dump(env[k]) for k in env}
992                for env in fenvs
993            ], baggage
994        nmatches += 1
995        pass
996
997    # count should count the same number of matches that findall finds.
998    assert nmatches == c, baggage

Runs a test of the match, find, and count functions using a given source string to match against and pattern to match. A match predicate may also be provided to test that functionality, but is optional.

You must specify whether a match is expected for the full pattern, and the number of matches expected in count mode.