Skip to content

Commit 7ba1772

Browse files
committed
Add functions to assert no loops/etc. resolve #7
1 parent 277ea0a commit 7ba1772

File tree

3 files changed

+85
-16
lines changed

3 files changed

+85
-16
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
## [Unreleased]
1010

11+
### Added
12+
13+
- `assert_no_if` function in audit.py
14+
- `assert_no_for` function in audit.py
15+
- `assert_no_while` function in audit.py
16+
- `assert_no_loops` function in audit.py
17+
1118
### Changed
1219

1320
- always set status to "failed" on error
1421

22+
### Removed
23+
24+
- `count_while_loops()` function in audit.py
25+
1526

1627
## [1.2.1] - 2025-09-07
1728

examples/6_provided_data/test_analyze.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from jmu_pytest_utils.audit import count_comments, count_regex_matches, count_while_loops
1+
from jmu_pytest_utils.audit import assert_no_while, count_comments, count_regex_matches
22
from jmu_pytest_utils.common import assert_pep8, assert_docs, chdir_test
33
from jmu_pytest_utils.decorators import weight
44
import os
@@ -21,8 +21,8 @@ def test_pep8_docs():
2121

2222
@weight(2)
2323
def test_approach():
24+
assert_no_while(FILENAME)
2425
assert count_comments(FILENAME) > 2
25-
assert count_while_loops(FILENAME) == 0
2626
assert count_regex_matches(FILENAME, r"\bwith\b") == 2
2727

2828

jmu_pytest_utils/audit.py

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
"""
66

77
__all__ = [
8+
"assert_no_if",
9+
"assert_no_for",
10+
"assert_no_while",
11+
"assert_no_loops",
812
"count_calls",
913
"count_comments",
1014
"count_nodes",
11-
"count_while_loops",
1215
"count_regex_matches",
1316
"get_source_code",
1417
]
@@ -72,6 +75,71 @@
7275
]
7376

7477

78+
def assert_no_if(filename, main=True):
79+
"""Check that no if statements/expressions are used.
80+
81+
Args:
82+
filename (str): The source file to parse.
83+
main (bool): Don't count if __name__ == "__main__".
84+
"""
85+
count = 0
86+
if main:
87+
source = get_source_code(filename)
88+
tree = ast.parse(source, filename)
89+
# Iterate top-level statements only
90+
for node in tree.body:
91+
if (
92+
isinstance(node, ast.If)
93+
and isinstance(node.test, ast.Compare)
94+
and isinstance(node.test.left, ast.Name)
95+
and node.test.left.id == "__name__"
96+
and len(node.test.ops) == 1
97+
and isinstance(node.test.ops[0], ast.Eq)
98+
and isinstance(node.test.comparators[0], ast.Constant)
99+
and node.test.comparators[0].value == "__main__"
100+
):
101+
count += 1
102+
node_count = count_nodes(filename)
103+
assert node_count["If"] == count, "If statements are not allowed"
104+
assert node_count["IfExp"] == 0, "If expressions are not allowed"
105+
106+
107+
def assert_no_for(filename, comps=True):
108+
"""Check that no for loops are used.
109+
110+
Args:
111+
filename (str): The source file to parse.
112+
comps (bool): Also check for comprehensions and generator expressions.
113+
"""
114+
node_count = count_nodes(filename)
115+
assert node_count["For"] == 0, "For loops are not allowed"
116+
if comps:
117+
assert node_count["ListComp"] == 0, "List comprehensions are not allowed"
118+
assert node_count["SetComp"] == 0, "Set comprehensions are not allowed"
119+
assert node_count["DictComp"] == 0, "Dict comprehensions are not allowed"
120+
assert node_count["GeneratorExp"] == 0, "Generator expressions are not allowed"
121+
122+
123+
def assert_no_while(filename):
124+
"""Check that no while loops are used.
125+
126+
Args:
127+
filename (str): The source file to parse.
128+
"""
129+
node_count = count_nodes(filename)
130+
assert node_count["While"] == 0, "While loops are not allowed"
131+
132+
133+
def assert_no_loops(filename):
134+
"""Calls assert_no_for() and assert_no_while().
135+
136+
Args:
137+
filename (str): The source file to parse.
138+
"""
139+
assert_no_for(filename)
140+
assert_no_while(filename)
141+
142+
75143
def count_calls(filename, func_id):
76144
"""Count how many times a function is called.
77145
@@ -99,6 +167,9 @@ def count_calls(filename, func_id):
99167
def count_comments(filename):
100168
"""Count the number of # comments in a program.
101169
170+
Args:
171+
filename (str): The source file to parse.
172+
102173
Returns:
103174
int: Number of end-of-line comments found.
104175
"""
@@ -133,19 +204,6 @@ def count_nodes(filename):
133204
return Counter(type(node).__name__ for node in ast.walk(tree))
134205

135206

136-
def count_while_loops(filename):
137-
"""Count the number of while loops in a program.
138-
139-
Args:
140-
filename (str): The source file to parse.
141-
142-
Returns:
143-
int: Number of while loops found.
144-
"""
145-
nodes = count_nodes(filename)
146-
return nodes['While']
147-
148-
149207
def count_regex_matches(filename, pattern, strip_comments=True):
150208
"""Count the number of regex pattern matches in code.
151209

0 commit comments

Comments
 (0)