Share
Embed
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
Transcript
69 slides
-
def double(x): return x * 2 def test_doubling(): expected =
5 assert double(2) == expected -
def test_doubling(): expected = 5 > assert double(2) == expected
E assert 4 == 5 E + where 4 = double(2) sample_test.py:6: AssertionError -
def test_doubling(): expected = 5 > assert double(2) == expected
E assert 4 == 5 E + where 4 = double(2) sample_test.py:6: AssertionError -
Benjamin Peterson Behind the scenes of py.test's new assertion rewriting
bit.ly/pytest-ast -
Goal one Modify the assert statement to call a function
with both sides of the comparison -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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) -
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) -
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) -
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) -
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) -
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) -
import_node = ImportFrom( module='test_utils', names=[alias('assert_equals', '#eq')], lineno=0, col_offset=0, ) Call(
func=Name(id='assert_equals', ctx=Load()), args=[...], keywords=[] ) -
import_node = ImportFrom( module='test_utils', names=[alias('assert_equals', '#eq')], lineno=0, col_offset=0, ) Call(
func=Name(id='#eq', ctx=Load()), args=[...], keywords=[] ) -
Goal two Write an import hook that uses our
transformer to modify imported code -
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() -
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() -
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() -
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() -
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() -
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() ) -
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() ) -
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() ) -
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() ) -
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() ) -
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() ) -
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__) -
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__) -
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__) -
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__) -
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__) -
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__) -
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__) -
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__) -
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 -
Summary This is probably a giant foot gun Corner cases
left for the reader Python is awesome