Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use pytest's plain asserts #228

Open
Jelleas opened this issue Jul 14, 2020 · 2 comments
Open

Use pytest's plain asserts #228

Jelleas opened this issue Jul 14, 2020 · 2 comments

Comments

@Jelleas
Copy link
Contributor

Jelleas commented Jul 14, 2020

pytest has built-in support for rather detailed assert messages. Allowing the testwriter to just use plain asserts:

assert 1 == 2

instead of

if 1 == 2:
     raise check50.Failure("1 does not equal 2")

To do this pytest rewrites part of the code and as it turns out that functionality is reasonably separate from pytests' core. With inspiration from their own test suite:

echo "assert 1 == 2" > bar.py
import ast
from _pytest.assertion.rewrite import rewrite_asserts

def rewrite(src: str) -> ast.Module:
    tree = ast.parse(src)
    rewrite_asserts(tree, src.encode())
    return tree

src = open("bar.py").read()
mod = rewrite(src)
code = compile(mod, "<test>", "exec")
namespace = {}
exec(code, namespace)

Just a thought, but perhaps it's worth exploring?

@cmlsharp
Copy link
Contributor

cmlsharp commented Jul 14, 2020

How do they generate the error messages?

We can always add "AssertionError" to the list of exceptions we catch in the check decorator and do a manual conversion fo check50.Failure. This wouldn't require rewriting the ast. The question is what the error message should be. I mean, you can include error messages in asserts like assert 1 == 2, "one does not equal two", but what if they don't include a human readable error message? Obviously this is technically possible now since someone could write raise check50.Failure(""), but this seems easier to do with the assert syntax.

I guess I'm not necessarily opposed to this, but I'm also not sure it adds much.

@Jelleas
Copy link
Contributor Author

Jelleas commented Jul 14, 2020

The default assert is rather useless. It provides nothing to produce a check50.Failure from:

try:
     assert 1 == 2
except AssertionError as e:
     print(e.args) # prints ()

pytest however puts the actual assertion into e.args, but also allows you to intercept this via _pytest.assertion.util._reprcompare (the implementation behind https://docs.pytest.org/en/latest/assert.html#defining-your-own-explanation-for-failed-assertions)

Allowing you to do the following:

import ast
from _pytest.assertion.rewrite import rewrite_asserts
from _pytest.assertion import util

def custom_assert_handler(left, right, op):
    print(f"left: {left}, right: {right}, op:{op}")

util._reprcompare = custom_assert_handler

def rewrite(src: str) -> ast.Module:
    tree = ast.parse(src)
    rewrite_asserts(tree, src.encode())
    return tree

src = open("bar.py").read()
mod = rewrite(src)
code = compile(mod, "<test>", "exec")
namespace = {}
exec(code, namespace)

Through this we could quite reasonably intercept different operators and types to provide student-friendly output, without putting all the burden on the checkwriter. Some low hanging fruit perhaps:

assert 1 == 2 => check50.Mismatch(1, 2)
assert 1 in [1,2,3] => check50.Missing(1, [1,2,3])

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants