Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Dispelling py.test magic

Dispelling py.test magic

How to replicate py.test magic in lest than 100 lines of code.
Demo code available here: https://github.com/oinopion/dispel

Tomek Paczkowski

September 19, 2015
Tweet

More Decks by Tomek Paczkowski

Other Decks in Programming

Transcript

  1. def test_doubling(): expected = 5 > assert double(2) == expected

    E assert 4 == 5 E + where 4 = double(2) sample_test.py:6: AssertionError
  2. def test_doubling(): expected = 5 > assert double(2) == expected

    E assert 4 == 5 E + where 4 = double(2) sample_test.py:6: AssertionError
  3. Goal one Modify the assert statement to
 call a function

    with both sides 
 of the comparison
  4. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  5. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  6. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  7. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  8. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  9. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  10. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  11. class AssertRewrite(NodeTransformer):
 def visit_Assert(self, node):
 call = Call(
 func=Name(
 id='assert_equals',

    ctx=Load()
 ),
 args=[
 node.test.left,
 node.test.comparators[0]
 ],
 keywords=[]
 )
 new_node = Expr(value=call)
 copy_location(new_node, node)
 fix_missing_locations(new_node)
 return new_node
  12. def transform(module):
 import_node = ImportFrom(
 module='test_utils',
 names=[alias('assert_equals', None)],
 lineno=0, col_offset=0,


    )
 module.body[0:0] = [import_node]
 transformer = AssertRewrite()
 return transformer.visit(module)
  13. def transform(module):
 import_node = ImportFrom(
 module='test_utils',
 names=[alias('assert_equals', None)],
 lineno=0, col_offset=0,


    )
 module.body[0:0] = [import_node]
 transformer = AssertRewrite()
 return transformer.visit(module)
  14. def transform(module):
 import_node = ImportFrom(
 module='test_utils',
 names=[alias('assert_equals', None)],
 lineno=0, col_offset=0,


    )
 module.body[0:0] = [import_node]
 transformer = AssertRewrite()
 return transformer.visit(module)
  15. def transform(module):
 import_node = ImportFrom(
 module='test_utils',
 names=[alias('assert_equals', None)],
 lineno=0, col_offset=0,


    )
 module.body[0:0] = [import_node]
 transformer = AssertRewrite()
 return transformer.visit(module)
  16. def transform(module):
 import_node = ImportFrom(
 module='test_utils',
 names=[alias('assert_equals', None)],
 lineno=0, col_offset=0,


    )
 module.body[0:0] = [import_node]
 transformer = AssertRewrite()
 return transformer.visit(module)
  17. def transform(module):
 import_node = ImportFrom(
 module='test_utils',
 names=[alias('assert_equals', None)],
 lineno=0, col_offset=0,


    )
 module.body[0:0] = [import_node]
 transformer = AssertRewrite()
 return transformer.visit(module)
  18. Goal two Write an import hook 
 that uses our

    transformer 
 to modify imported code
  19. def import_hook(path): if os.path.abspath('') == path: return Finder() else: raise

    ImportError sys.path_hooks.insert(0, import_hook) sys.path_importer_cache.clear()
  20. def import_hook(path): if os.path.abspath('') == path: return Finder() else: raise

    ImportError sys.path_hooks.insert(0, import_hook) sys.path_importer_cache.clear()
  21. def import_hook(path): if os.path.abspath('') == path: return Finder() else: raise

    ImportError sys.path_hooks.insert(0, import_hook) sys.path_importer_cache.clear()
  22. def import_hook(path): if os.path.abspath('') == path: return Finder() else: raise

    ImportError sys.path_hooks.insert(0, import_hook) sys.path_importer_cache.clear()
  23. def import_hook(path): if os.path.abspath('') == path: return Finder() else: raise

    ImportError sys.path_hooks.insert(0, import_hook) sys.path_importer_cache.clear()
  24. from importlib.util import spec_from_file_location
 
 class Finder:
 def find_spec(self, module,

    target=None):
 file_name = module + '.py'
 if not os.path.exists(file_name):
 return None
 return spec_from_file_location(
 name=module, 
 location=file_name, 
 loader=Loader()
 )
  25. from importlib.util import spec_from_file_location
 
 class Finder:
 def find_spec(self, module,

    target=None):
 file_name = module + '.py'
 if not os.path.exists(file_name):
 return None
 return spec_from_file_location(
 name=module, 
 location=file_name, 
 loader=Loader()
 )
  26. from importlib.util import spec_from_file_location
 
 class Finder:
 def find_spec(self, module,

    target=None):
 file_name = module + '.py'
 if not os.path.exists(file_name):
 return None
 return spec_from_file_location(
 name=module, 
 location=file_name, 
 loader=Loader()
 )
  27. from importlib.util import spec_from_file_location
 
 class Finder:
 def find_spec(self, module,

    target=None):
 file_name = module + '.py'
 if not os.path.exists(file_name):
 return None
 return spec_from_file_location(
 name=module, 
 location=file_name, 
 loader=Loader()
 )
  28. from importlib.util import spec_from_file_location
 
 class Finder:
 def find_spec(self, module,

    target=None):
 file_name = module + '.py'
 if not os.path.exists(file_name):
 return None
 return spec_from_file_location(
 name=module, 
 location=file_name, 
 loader=Loader()
 )
  29. from importlib.util import spec_from_file_location
 
 class Finder:
 def find_spec(self, module,

    target=None):
 file_name = module + '.py'
 if not os.path.exists(file_name):
 return None
 return spec_from_file_location(
 name=module, 
 location=file_name, 
 loader=Loader()
 )
  30. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as fp:

    source = fp.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  31. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as fp:

    source = fp.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  32. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as f:

    source = f.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  33. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as fp:

    source = fp.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  34. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as fp:

    source = fp.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  35. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as fp:

    source = fp.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  36. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as fp:

    source = fp.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  37. class Loader: def exec_module(self, module): with open(module.__file__, 'rb') as fp:

    source = fp.read() tree = ast.parse(source, module.__file__) tree = transform(tree) code = compile(tree, module.__file__, 'exec') exec(code, module.__dict__)
  38. class Loader:
 def exec_module(self, module):
 with open(module.__file__, 'rb') as fp:


    source = fp.read()
 
 tree = ast.parse(source, module.__file__)
 tree = transform(tree)
 code = compile(tree, module.__file__, 'exec')
 module.__dict__['#eq'] = assert_equals
 exec(code, module.__dict__) Bonus
  39. Summary This is probably a giant foot gun Corner cases

    left for the reader Python is awesome