Saturday, July 9, 2011

Behind the scenes of py.test's new assertion rewriting

py.test 2.1 was just released. py.test, which uses the Python assert statement to check test conditions, has long had support for displaying intermediate values in subexpressions of a failing assert statement. This feature is called assertion introspection. Historically, py.test performed assertion introspection by reinterpreting failed assertions in order to glean information about subexpressions. In assertion reinterpreting, py.test actually reruns the assertion noting intermediate values during interpretation. This works pretty well but is subject to several problems, most importantly that assert statements with side-effects can produce strange results because they are evaluated twice on failure. py.test 2.1's main new feature, which I wrote (with generous sponsorship from Merlinux GmbH), is a new assertion introspection technique called assertion rewriting. Assertion rewriting modifies the AST of test modules to produce subexpression information when assertions fail. This blog post will give a peek into how this is done and what the rewritten tests look like.

py.test tries to rewrite every module that it collects as a test module. Assertion rewriting uses a PEP 302 import hook to capture test modules for rewriting. I'm happy to report doing this was easier than I expected. Most of the code in the import hook I had to write was dealing with detecting test modules rather than supporting import's extremely complicated API. Rewriting has a non-zero cost during test collection, so py.test compiles rewritten modules to bytecode and caches them in the PEP 3147 PYC repository, __pycache__. One major thing I did have to account for was the possibility that multiple py.test processes would be writing PYC files. (This is a very real possibility when the xdist plugin is being used. Therefore, py.test uses only atomic operations on the rewritten PYC file. Windows, lacking atomic rename, was a pain here.

I'm now going to demonstrate what py.test's rewriting phase does to a test module. Let's dive in with a failing test for a (broken) function that is supposed to create empty files:

import os

def make_empty_file(name):
with open(name, "w") as fp:
fp.write("hello")

def test_make_empty_file():
name = "/tmp/empty_test"
make_empty_file(name)
with open(name, "r") as fp:
assert not fp.read()


This test nicely demonstrates the problem with py.test's old assertion method mentioned in the first paragraph. If we force the old assertion interpretation mode with --assert=reinterp, we see:


def test_make_empty_file():
name = "/tmp/empty_test"
make_empty_file(name)
with open(name, "r") as fp:
> assert not fp.read()
E AssertionError: (assertion failed, but when it was re-run for printing intermediate values, it did not fail. Suggestions: compute assert expression before the assert or use --no-assert)

test_empty_file.py:11: AssertionError


The problem is that assert statement has the side-effect of reading the file. When py.test reinterprets the assert statement, it uses the same file object, now at EOF, and read() returns an empty string. py.test's new rewriting mode fixes this by scanning the assert for introspection information before executing the test. Running py.test with assertion rewriting enabled gives the desired result:


def test_make_empty_file():
name = "/tmp/empty_test"
make_empty_file(name)
with open(name, "r") as fp:
> assert not fp.read()
E assert not 'hello'
E + where 'hello' = ()
E + where = .read

test_empty_file.py:11: AssertionError


So what magic has py.test worked to display such nice debugging information? This is what Python is actually executing:

def test_make_empty_file():
name = '/tmp/empty_test'
make_empty_file(name)
with open(name, 'r') as fp:
@py_assert1 = fp.read
@py_assert3 = @py_assert1()
@py_assert5 = (not @py_assert3)
if (not @py_assert5):
@py_format6 = ('assert not %(py4)s\n{%(py4)s = %(py2)s\n{%(py2)s = %(py0)s.read\n}()\n}' %
{'py0': (@pytest_ar._saferepr(fp) if ('fp' in @py_builtins.locals() is not @py_builtins.globals()) else 'fp'),
'py2': @pytest_ar._saferepr(@py_assert1),
'py4': @pytest_ar._saferepr(@py_assert3)})
raise AssertionError(@pytest_ar._format_explanation(@py_format6))
del @py_assert5, @py_assert1, @py_assert3


As you can see, it's not going to be winning any awards for beautiful Python! (Ideally, though, you'll never have to see or think about it.) Examining the rewritten code, we see a lot of internal variables starting with "@" have been created. The "@", invalid in Python identifiers, is to make sure internal names don't conflict with any user-defined names which might be in the scope. In the first four written lines under the with statement, the test of the assert statement has been expanded into its component subexpressions. This allows py.test to display the values of subexpressions should the assertion fail. If the assertion fails, the if statement in the fifth line of rewriting evaluates to True and a AssertionError will be raised. Under the if statement is the real mess. This is where the helpful error message is generated. The line starting with @py_format6 is simply does string formatting (with %) on a template generated from the structure of the assert statement. This template is filled in with the intermediate values of the expressions collected above. @py_builtins is the builtins module, used in case the test is shadowing builtins the rewriting code uses. The @pytest_ar variable is a special module of assertion formatting helpers. For example, @pytest_ar._saferepr is like builtin repr but gracefully handles long reprs and __repr__ methods that raise exceptions. A non-obvious trick in the format dict is the expression @pytest_ar._saferepr(fp) if ('fp' in @py_builtins.locals() is not @py_builtins.globals()) else 'fp'. This checks whether fp is a local variable or not and customizes the display accordingly. After the initial formatting, the helper function _format_explanation is called. This function produces the indentation and "+" you see in the error message. Finally, we note that if the assertion doesn't fail, py.test cleans up after itself by deleting temporary variables.

The example above is a fairly tame (and luckily also typical) assertion. Rewriting gets more "exciting" when boolean operations and comparisons enter because they require short circuit evaluation, which complicates both the expansion of expressions and formatting (think lots of nested ifs).

In conclusion, py.test's new assertion rewriting fixes some long standing issues with assertion introspection and continues py.test's long tradition of excellent debugging support. (There are now three(!) assertion introspection methods in py.test: two reinterpretation implementations as well as rewriting) I just hope I haven't scared you completely off py.test! :)

222 comments:

«Oldest   ‹Older   201 – 222 of 222
DRAGON said...

الأعمال الخشبية تحتاج إلى فني لديه خبرة في القياس والتركيب والتشطيب، خصوصا عند إصلاح الأبواب أو تركيب خزائن أو تعديل أثاث داخل المنزل. التنفيذ الجيد يجعل الشكل النهائي أفضل ويطيل عمر القطع الخشبية.

لذلك عند الحاجة إلى صيانة أو تركيب أعمال خشبية، يمكن الاعتماد على نجار عجمان لتنفيذ المطلوب بشكل عملي ومناسب لطبيعة المكان.

Multisoft Virtual Academy said...

This is an excellent walkthrough for developers exploring Grails with modern front-end technologies. The clear explanations and real-world approach make the concepts easy to follow. Even today, the architectural ideas shared here remain valuable for understanding full-stack application development. Thanks for the insightful post!
Oracle Fusion SCM Training
Salesforce Sales Cloud Certification Training
SAP IS Utilities Training
SAP BRIM Training
SAP Document and Reporting Compliance (DRC) Training
Six Sigma Green Belt Training
SailPoint Identity Security Cloud (ISC) Training

Davidsun said...


Organic Desiccated Coconut
Organic Coconut Chips

david said...

arc flash safety gear offers reliable protection for technicians performing routine electrical inspections.

micheal said...

AGP 40 Cal Arc Clothing continues to deliver dependable workplace protection for electricians and maintenance professionals working in challenging industrial environments every day

Felix Solutions said...

nice article, waiting for your another :)
Best medical coding institute in Hyderabad
Best medical coding institute in Chennai
Best medical coding institute in Pune

david said...

flame resistant coveralls provide dependable protective clothing for demanding workplace applications.

stacy gorge said...

All suits are made with DuPont™ Nomex® & DuPont™ Kevlar® for extra protection and comfort .Heavy PPE can slow you down your movement. Arclash® combines lightweight multi layered protection, helping you move easier, work longer and stay protected in demanding arc flash environments. Every Arclash product is developed with a clear objective to help safety professionals, employers and workers confidently manage the risks associated with electrical and thermal hazards while meeting the performance expectations of modern industry .Arclash 60 Cal Arc Flash Protection Suit is made for the strong and reliable protection. No compromise on the safety standards this Arclash suit arc flash ppe clothing provides the extraordinary arc flash protection with the maximum comfort and agility due to the light weight. The suit construction consist on DuPont™ Nomex® & DuPont™ Kevlar® which gives the you the extra edge of protection and comfort.Arclash has the complete range of arc flash suits and garments .Protection starts from HRC 2, HRC 3 & HRC 4

stacy gorge said...

Arclash 60 Cal Arc Flash Protection Suit is made for the strong and reliable protection. No compromise on the safety standards this Arclash suit arc flash ppe clothing provides the extraordinary arc flash protection with the maximum comfort and agility due to the light weight. The suit construction consist on DuPont™ Nomex® & DuPont™ Kevlar® which gives the you the extra edge of protection and comfort. Arclash has the complete range of arc flash suits and garments .Protection starts from HRC 2, HRC 3 & HRC 4 and All suits arc flash ppe clothing

are made with DuPont™ Nomex® & DuPont™ Kevlar® for extra protection and comfort .Heavy PPE can slow you down your movement. Arclash® combines lightweight multi layered protection, helping you move easier, work longer and stay protected in demanding arc flash environments. Every Arclash product is developed with a clear objective to help safety professionals, employers and workers confidently manage the risks associated with electrical and thermal hazards while meeting the performance expectations of modern industry .

stacy gorge said...

Arclash has the complete range of arc flash suits and garments .Protection starts from HRC 2, HRC 3 & HRC 4 and All suits are made with DuPont™ Nomex® & DuPont™ Kevlar® for extra protection and comfort .Heavy PPE can slow you down your movement. Arclash® combines lightweight multi layered protection, helping you move easier, work longer and stay protected in demanding arc flash environments. Every Arclash product is developed with a clear objective to help safety professionals, employers and workers confidently manage the risks associated with electrical and thermal hazards while meeting the performance expectations of modern industry .Arclash 60 Cal Arc Flash Protection Suit is made for the strong and reliable protection. No compromise on the safety standards this Arclash suit arc flash ppe clothing provides the extraordinary arc flash protection with the maximum comfort and agility due to the light weight. The suit construction consist on DuPont™ Nomex® & DuPont™ Kevlar® which gives the you the extra edge of protection and comfort.

stacy gorge said...

Arclash arc flash ppe clothing
has the complete range of arc flash suits and garments .Protection starts from HRC 2, HRC 3 & HRC 4 and All suits are made with DuPont™ Nomex® & DuPont™ Kevlar® for extra protection and comfort .Heavy PPE can slow you down your movement. Arclash® combines lightweight multi layered protection, helping you move easier, work longer and stay protected in demanding arc flash environments. Every Arclash product is developed with a clear objective to help safety professionals, employers and workers confidently manage the risks associated with electrical and thermal hazards while meeting the performance expectations of modern industry .Arclash 60 Cal Arc Flash Protection Suit is made for the strong and reliable protection. No compromise on the safety standards this Arclash suit arc flash ppe clothing provides the extraordinary arc flash protection with the maximum comfort and agility due to the light weight. The suit construction consist on DuPont™ Nomex® & DuPont™ Kevlar® which gives the you the extra edge of protection and comfort.

seo said...

الترا كلين - أفضل شركة تنظيف في الكويت - تنظيف منازل - غسيل كنب - تنظيف بعد التشطيب - خدمة 24/7 - أسعار منافسة | اتصل الآن 96550162626
شركة تنظيف

seo said...

سمو الاناقة لبيع المستلزمات الرجالية نقدم كل ما تحتاجة من اشمغة و غتر و ثياب و مشالح ملكية و ملابس احرام واثواب السفير تشكيلة من الماركات العالمية.
اشمغة

seo said...

يوفر متجر ايكة هوم طاولات طعام وضيافة وخدمة وكراسي ودولاب بوفيه وطاولات تلفزيون وجلسات خارجية ومداخل استقبال بتصاميم عصرية وجودة عالية
متجر أثاث أونلاين في السعودية

seo said...

تبحث عن شركة عزل ممبرين محترفة؟ عالم التطويرية للعوازل تقدم حلول عزل بيتومين وPVC وبولي يوريا بخامات معتمدة وضمان 10 سنوات. تواصل معنا الآن.
شركة عزل ممبرين

Finding the Right Urgent Care in Long Beach and Paramount said...

Great information about finding convenient healthcare when you need medical attention quickly. Having access to a reliable **walk-in urgent care clinic** can be helpful for treating common illnesses, minor injuries, infections, and other non-emergency medical concerns. For patients looking for **urgent care in Long Beach or Paramount, CA**, Reddy Urgent Care offers convenient medical services and walk-in care.

Learn more about their services and locations here: https://www.reddyuc.com/

A useful resource for anyone searching for **same-day urgent care, walk-in medical care, or urgent care near Long Beach and Paramount**.

seo said...

منصة متابعة الغياب المدرسية لتسجيل غياب الطلاب، حساب نسب الحضور، وإعداد التقارير بسهولة مع نسخة خاصة ومستقلة لكل مدرسة.
منصة متابعة الغياب المدرسية

agilesecurity said...

Businesses looking to improve efficiency can explore modern digital business solutions to streamline their operations and support growth. Nuvosync by Weblab Solutions is worth exploring when researching business software solutions, workflow management tools, and digital transformation solutions. Visit the website to learn more about the product and its potential applications.

Explore Nuvosync: https://www.weblabsolutions.in/products/nuvosync

seo said...

Al Razana Kitchen Equipment - trusted UAE supplier of premium commercial kitchen, refrigeration and catering equipment in Dubai and across the Gulf.
commercial kitchen equipment uae

seo said...

تيك ايفينت افضل منصة حجز تذاكر الفعاليات و الحفلات في السعودية. احجز تذاكر المباريات والحفلات والفعاليات الرياضية والترفيهية بسهولة وأمان واستمتع بأفضل التجارب
أفضل منصة حجز تذاكر مباريات دوري روشن بالسعودية

seo said...

شنط يد كانفس بالاسم أو الحروف، صناعة يدوية من Decent أفضل متجر حقائب سفر في السعودية. تجهيز 3 أيام، شحن مجاني فوق 405 ، وقسّطي مع تابي بسهولة.
شنط يد

seo said...

سكيمر مسبح عالي الجودة، مجموعة سكيمرات من سيتي بول مصممة لجمع الأوساخ السطحية قبل ترسبها في القاع. نوفر لك خيارات عملية تناسب المسابح المنزلية والتجارية من وكيل معتمد،
سكيمر مسبح

«Oldest ‹Older   201 – 222 of 222   Newer› Newest»