Skip to content

Commit 95e5fec

Browse files
authored
Optimize stateful test healthcheck (#102)
* Fix expression precedence * Update formatting * Consume all descendants on deletion * Use posargs as a substitute * Remove unused import * Suppress health check * Remove given from invariant * Optimize test rules * Add copyright notice * Update copyright notice * Revert suppress
1 parent 29de62b commit 95e5fec

File tree

2 files changed

+39
-21
lines changed

2 files changed

+39
-21
lines changed

noxfile.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,13 @@ def test(session, sqlalchemy):
119119
with_coverage = False
120120
else:
121121
with_coverage = True
122-
pytest_cmd = [
123-
"pytest", "--pyargs", "sqlalchemy_mptt",
124-
"--cov", "sqlalchemy_mptt", "--cov-report", "term-missing:skip-covered"
125-
] + ["--cov-report", "xml"] if with_coverage else [] + session.posargs
122+
pytest_cmd = ["pytest"] + (
123+
session.posargs or [
124+
"--pyargs", "sqlalchemy_mptt",
125+
"--cov", "sqlalchemy_mptt", "--cov-report", "term-missing:skip-covered",
126+
"-W", "error:::sqlalchemy_mptt"
127+
]
128+
) + (["--cov-report", "xml"] if with_coverage else [])
126129
session.run(*pytest_cmd)
127130

128131

sqlalchemy_mptt/tests/test_stateful.py

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1+
# -*- coding: utf-8 -*-
2+
# vim:fenc=utf-8
3+
#
4+
# Copyright (c) 2025 Fayaz Yusuf Khan <[email protected]>
5+
#
6+
# Distributed under terms of the MIT license.
17
"""Test cases written using Hypothesis stateful testing framework."""
2-
from hypothesis import assume, given, settings, strategies as st
8+
from hypothesis import HealthCheck, settings, strategies as st
39
from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, invariant, rule
410
from sqlalchemy import Column, Integer, Boolean, create_engine
511
from sqlalchemy.ext.declarative import declarative_base
6-
from sqlalchemy.orm import sessionmaker
12+
from sqlalchemy.orm import joinedload, sessionmaker
713

814
from sqlalchemy_mptt import BaseNestedSets, mptt_sessionmaker
915

@@ -37,43 +43,52 @@ def __init__(self):
3743
def add_root_node(self, visible):
3844
node = Tree(visible=visible)
3945
self.session.add(node)
40-
self.session.flush()
46+
self.session.commit()
4147
assert node.left < node.right
4248
return node
4349

4450
@rule(node=consumes(node))
4551
def delete_node(self, node):
46-
assume(node in self.session)
52+
# Consume all descendants of the node
53+
for name, value in list(self.names_to_values.items()):
54+
if value not in self.session or node.is_ancestor_of(value):
55+
for var_reference in self.bundles["node"][:]:
56+
if var_reference.name == name:
57+
self.bundles["node"].remove(var_reference)
58+
# Remove the object as well for garbage collection
59+
del self.names_to_values[name]
4760
self.session.delete(node)
48-
self.session.flush()
61+
self.session.commit()
4962

5063
@rule(target=node, node=node, visible=st.none() | st.booleans())
5164
def add_child(self, node, visible):
52-
assume(node in self.session)
5365
child = Tree(parent=node, visible=visible)
5466
self.session.add(child)
55-
self.session.flush()
67+
self.session.commit()
5668
assert node.left < child.left < child.right < node.right
5769
return child
5870

5971
@invariant()
6072
def check_get_tree_integrity(self):
6173
"""Check that get_tree response is valid after each operation."""
62-
response = Tree.get_tree(self.session)
74+
response = Tree.get_tree(
75+
self.session,
76+
query=lambda x: x.execution_options(populate_existing=True).options(joinedload(Tree.children)))
6377
assert isinstance(response, list)
6478
for node in response:
65-
self.session.refresh(node['node'])
6679
validate_get_tree_node(node)
6780

6881
@invariant()
69-
@given(st.none() | st.booleans())
70-
def check_get_tree_with_custom_query(self, visible):
82+
def check_get_tree_with_custom_query(self):
7183
"""Check that get_tree response is valid with custom queries."""
72-
response = Tree.get_tree(self.session, query=lambda x: x.filter_by(visible=visible))
73-
assert isinstance(response, list)
74-
for node in response:
75-
self.session.refresh(node['node'])
76-
validate_get_tree_node_for_custom_query(node)
84+
for visible in [None, True, False]:
85+
response = Tree.get_tree(
86+
self.session,
87+
query=lambda x: x.filter_by(visible=visible)
88+
.execution_options(populate_existing=True).options(joinedload(Tree.children)))
89+
assert isinstance(response, list)
90+
for node in response:
91+
validate_get_tree_node_for_custom_query(node)
7792

7893

7994
def validate_get_tree_node(node_response, level=1):
@@ -101,5 +116,5 @@ def validate_get_tree_node_for_custom_query(node_response):
101116
# Export the stateful test case
102117
TestTreeStates = TreeStateMachine.TestCase
103118
TestTreeStates.settings = settings(
104-
max_examples=75, stateful_step_count=25
119+
max_examples=75, stateful_step_count=25, suppress_health_check=[HealthCheck.too_slow]
105120
)

0 commit comments

Comments
 (0)