Skip to content

Commit 2c1aaf0

Browse files
authored
added assertion rewrites
1 parent dd2e27d commit 2c1aaf0

3 files changed

Lines changed: 46 additions & 44 deletions

File tree

check50/__main__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,15 @@ def main():
369369
if not args.no_install_dependencies:
370370
install_dependencies(config["dependencies"])
371371

372-
checks_file = (internal.check_dir / config["checks"]).resolve()
373-
374-
# Rewrite all assert statements to check50_assert
372+
# Store the original checks file and leave as is
373+
original_checks_file = (internal.check_dir / config["checks"]).resolve()
374+
375+
# Create a temporary copy of the checks file
376+
with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as tmp:
377+
checks_file = Path(tmp.name)
378+
shutil.copyfile(original_checks_file, checks_file)
379+
380+
# Rewrite all assert statements in the copied checks file to check50_assert
375381
assertions.rewrite(str(checks_file))
376382

377383
# Have lib50 decide which files to include

check50/assertions/rewrite.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@ class _AssertionRewriter(ast.NodeTransformer):
4343
Helper class to to wrap the conditions being tested by assert with a
4444
function called `check50_assert`.
4545
"""
46-
def _visit_Assert(self, node):
46+
47+
def visit_Assert(self, node):
4748
"""
48-
An overwrite of the AST module's _visit_Assert to inject our code in
49+
An overwrite of the AST module's visit_Assert to inject our code in
4950
place of the default assertion logic.
5051
5152
:param node: An AST node.
@@ -56,8 +57,9 @@ def _visit_Assert(self, node):
5657
value=ast.Call(
5758
func=ast.Name(id="check50_assert", ctx=ast.Load()),
5859
args=[
59-
node.test,
60-
ast.Constant(value=ast.unparse(node.test))
60+
node.test,
61+
ast.Constant(value=ast.unparse(node.test)),
62+
node.msg if node.msg is not None else ast.Constant(value=None)
6163
],
6264
keywords=[]
6365
)

check50/assertions/runtime.py

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,45 @@
11
from check50 import Failure, Missing, Mismatch
22
import ast
33

4-
def check50_assert(cond: bool, src: str):
4+
def check50_assert(cond, src, msg_or_exc=None):
55
"""
66
Asserts a conditional statement. If the condition evaluates to True,
7-
nothing happens. Otherwise, the condition will raise a check50 exception.
8-
Used in rewriting check files. Evaluates subconditions in order and raises
9-
the first exception it sees. The specific exception raised depends on the
10-
type of conditional statement (see also `classify_ast`.)
7+
nothing happens. Otherwise, it will look for a message or exception that
8+
follows the condition (seperated by a comma). If the msg_or_exc is not
9+
a string, an exception, or not provided, then the additional argument is
10+
silently ignored, raising a check50.Failure.
11+
12+
Used for rewriting check files.
13+
14+
Example usage:
15+
```
16+
assert x in y, check50.Missing(x, y)
17+
```
18+
will be converted to
19+
```
20+
check50_assert(x in y, "x in y", check50.Missing(x, y))
21+
```
1122
1223
:param cond: The conditional statement.
1324
:type cond: bool
1425
:param src: The source code string of the conditional expression \
1526
(e.g., 'x in y'), extracted from the AST.
1627
:type src: str
17-
18-
:raises check50.Missing, check50.Mismatch, or check50.Failure: if the condition fails
28+
:param msg_or_exc: The message or exception following the conditional in \
29+
the assertion statement.
30+
:type msg_or_exc: str, BaseException, optional
31+
32+
:raises check50.Failure: if msg_or_exc is a string, if msg_or_exc is not
33+
included, or if both msg_or_exc is not a string and
34+
not an exception
35+
:raises msg_or_exc: if msg_or_exc is an exception
1936
"""
2037
if cond:
2138
return
22-
23-
expr = ast.parse(src, mode="eval").body
24-
exc = classify_ast(expr) # the exception that should be raised
25-
raise exc(f"Assertion failed: {src}")
26-
27-
def classify_ast(expr):
28-
"""
29-
Classifies an AST expression to return an exception based on the operator.
30-
31-
For instance, if the expression was read as "x not in [1,2,3]", the
32-
function would return a check50.Missing error.
33-
34-
:param expr: The AST expression.
35-
:type expr: ast.expr
36-
37-
:raises check50.Missing: if the comparison operator is one of: \
38-
(ast.In, ast.NotIn)
39-
:raises check50.Mismatch: if the comparison operator is one of: \
40-
(ast.Eq, ast.NotEq, ast.Gt, ast.Lt, ast.GtE, \
41-
ast.LtE)
42-
:raises check50.Failure: if not a comparison, or otherwise
43-
"""
44-
if isinstance(expr, ast.Compare):
45-
for op in expr.ops:
46-
if isinstance(op, (ast.In, ast.NotIn)):
47-
return Missing
48-
elif isinstance(op, (ast.Eq, ast.NotEq, ast.Gt, ast.Lt, ast.GtE, ast.LtE)):
49-
return Mismatch
50-
51-
return Failure
39+
40+
if isinstance(msg_or_exc, str):
41+
raise Failure(msg_or_exc)
42+
elif isinstance(msg_or_exc, BaseException):
43+
raise msg_or_exc
44+
else:
45+
raise Failure(f"Assertion failure: {src}")

0 commit comments

Comments
 (0)