1+ """
2+ Tests for CaseValidator.flag() and ast_analyzer._build_local_param_map fix.
3+ """
4+
5+ import ast
6+ import unittest
7+
8+ from ..case_validator import CaseValidator
9+ from ..params .ast_analyzer import CaseValidatorAnalyzer
10+
11+
12+ def _param_map (source : str ) -> dict :
13+ """Parse a single method body and return its local-param map."""
14+ wrapped = "class _V:\n " + "\n " .join (" " + l for l in source .splitlines ())
15+ func = ast .parse (wrapped ).body [0 ].body [0 ]
16+ a = CaseValidatorAnalyzer .__new__ (CaseValidatorAnalyzer )
17+ return a ._build_local_param_map (func )
18+
19+
20+ class TestFlag (unittest .TestCase ):
21+
22+ def test_true_when_set_to_T (self ):
23+ self .assertTrue (CaseValidator ({"chemistry" : "T" }).flag ("chemistry" ))
24+
25+ def test_false_when_set_to_F (self ):
26+ self .assertFalse (CaseValidator ({"chemistry" : "F" }).flag ("chemistry" ))
27+
28+ def test_false_when_absent (self ):
29+ self .assertFalse (CaseValidator ({}).flag ("chemistry" ))
30+
31+ def test_only_uppercase_T_is_truthy (self ):
32+ """Lowercase, integers, and booleans must not count as set."""
33+ for val in ("t" , "True" , 1 , True , None ):
34+ self .assertFalse (CaseValidator ({"x" : val }).flag ("x" ), f"Expected False for { val !r} " )
35+
36+ def test_returns_bool (self ):
37+ self .assertIsInstance (CaseValidator ({"x" : "T" }).flag ("x" ), bool )
38+
39+
40+ class TestASTCoupling (unittest .TestCase ):
41+
42+ def test_flag_call_recorded_by_analyzer (self ):
43+ """self.flag('x') must register 'x' in the param map, same as self.get('x','F')=='T'."""
44+ m = _param_map ("def check(self):\n chemistry = self.flag('chemistry')" )
45+ self .assertEqual (m .get ("chemistry" ), "chemistry" )
46+
47+ def test_get_call_still_recorded (self ):
48+ """Existing self.get() pattern must not regress."""
49+ m = _param_map ("def check(self):\n d = self.get('chem_params%diffusion', 'F') == 'T'" )
50+ self .assertEqual (m .get ("d" ), "chem_params%diffusion" )
51+
52+ def test_old_matcher_would_have_missed_flag (self ):
53+ """Demonstrate the pre-fix bug: attr=='get' silently dropped flag() calls."""
54+ source = "def check(self):\n chemistry = self.flag('chemistry')"
55+ wrapped = "class _V:\n " + "\n " .join (" " + l for l in source .splitlines ())
56+ func = ast .parse (wrapped ).body [0 ].body [0 ]
57+
58+ broken = {}
59+ for node in ast .walk (func ):
60+ if isinstance (node , ast .Assign ):
61+ v = node .value .left if isinstance (node .value , ast .Compare ) else node .value
62+ if isinstance (v , ast .Call ) and isinstance (v .func , ast .Attribute ):
63+ if v .func .value .id == "self" and v .func .attr == "get" : # old check
64+ for t in node .targets :
65+ if isinstance (t , ast .Name ):
66+ broken [t .id ] = v .args [0 ].value
67+
68+ self .assertNotIn ("chemistry" , broken , "Old matcher should miss flag() — that was the bug" )
69+
70+ a = CaseValidatorAnalyzer .__new__ (CaseValidatorAnalyzer )
71+ fixed = a ._build_local_param_map (func )
72+ self .assertIn ("chemistry" , fixed , "Fixed matcher must capture flag() calls" )
73+
74+
75+ if __name__ == "__main__" :
76+ unittest .main ()
0 commit comments