Coverage for tests/test_npath/test_python.py: 100%

1"""Tests for NPath complexity (Python)."""

2

3 from analyzers.python import analyze_source

4

5

6 def _npath(code: str) -> int:

7 """Return NPath of the first function in code."""

8 result = analyze_source(code)

9 assert result.functions, f"No functions found in:\n{code}"

10 return result.functions[0].npath

11

12

13 def _npaths(code: str) -> dict[str, int]:

14 """Return {name: npath} for all functions in code."""

15 result = analyze_source(code)

16 return {f.name: f.npath for f in result.functions}

17

18

19# --- Simple ---

20

21 def test_simple_function():

22 assert _npath("def f(): pass") == 1

23

24

25 def test_empty_source():

26 result = analyze_source("")

27 assert len(result.functions) == 0

28

29

30# --- If statements ---

31

32 def test_if_only():

33 code = """

34 def f(x):

35 if x:

36 pass

37"""

38 assert _npath(code) == 2 # NP(if body)=1 + 1 (no else)

39

40

41 def test_if_else():

42 code = """

43 def f(x):

44 if x:

45 pass

46 else:

47 pass

48"""

49 assert _npath(code) == 2 # NP(if body)=1 + NP(else body)=1

50

51

52 def test_if_elif_else():

53 code = """

54 def f(x):

55 if x > 0:

56 pass

57 elif x < 0:

58 pass

59 else:

60 pass

61"""

62 assert _npath(code) == 3 # 1 + 1 + 1

63

64

65 def test_if_elif_no_else():

66 code = """

67 def f(x):

68 if x > 0:

69 pass

70 elif x < 0:

71 pass

72"""

73 assert _npath(code) == 3 # 1 + 1 + 1 (implicit no-else path)

74

75

76# --- Bool ops in conditions ---

77

78 def test_bool_ops_in_if():

79 code = """

80 def f(a, b):

81 if a and b:

82 pass

83"""

84 assert _npath(code) == 3 # 1 + 1 (no else) + 1 (one bool op)

85

86

87 def test_two_bool_ops():

88 code = """

89 def f(a, b, c):

90 if a and b or c:

91 pass

92"""

93 assert _npath(code) == 4 # 1 + 1 (no else) + 2 (two bool ops)

94

95

96# --- Loops ---

97

98 def test_for_loop():

99 code = """

100 def f(items):

101 for x in items:

102 pass

103"""

104 assert _npath(code) == 2 # NP(body)=1 + 1

105

106

107 def test_while_loop():

108 code = """

109 def f(x):

110 while x:

111 pass

112"""

113 assert _npath(code) == 2 # NP(body)=1 + 1

114

115

116 def test_while_with_bool_cond():

117 code = """

118 def f(a, b):

119 while a and b:

120 pass

121"""

122 assert _npath(code) == 3 # NP(body)=1 + 1 + 1 (bool op)

123

124

125 def test_for_else():

126 code = """

127 def f(items):

128 for x in items:

129 pass

130 else:

131 pass

132"""

133 assert _npath(code) == 2 # NP(body)=1 + NP(else)=1

134

135

136# --- Try/except ---

137

138 def test_try_except():

139 code = """

140 def f():

141 try:

142 pass

143 except:

144 pass

145"""

146 assert _npath(code) == 2 # NP(try body)=1 + NP(except body)=1

147

148

149# --- Ternary ---

150

151 def test_ternary():

152 code = """

153 def f(c, x, y):

154 return x if c else y

155"""

156 assert _npath(code) == 2 # NP(x)=1 + NP(y)=1

157

158

159# --- Sequential ifs (multiplicative) ---

160

161 def test_sequential_ifs():

162 code = """

163 def f(a, b):

164 if a:

165 pass

166 if b:

167 pass

168"""

169 assert _npath(code) == 4 # 2 * 2

170

171

172# --- Nested if ---

173

174 def test_nested_if():

175 code = """

176 def f(a, b):

177 if a:

178 if b:

179 pass

180"""

181 # Outer if: NP(inner if) + 1 (no else)

182 # Inner if: 1 + 1 (no else) = 2

183 # Outer: 2 + 1 = 3

184 assert _npath(code) == 3

185

186

187# --- Match ---

188

189 def test_match_three_cases():

190 code = """

191 def f(x):

192 match x:

193 case 1:

194 pass

195 case 2:

196 pass

197 case 3:

198 pass

199"""

200 assert _npath(code) == 4 # 1+1+1 + 1 (no wildcard)

201

202

203 def test_match_with_wildcard():

204 code = """

205 def f(x):

206 match x:

207 case 1:

208 pass

209 case _:

210 pass

211"""

212 assert _npath(code) == 2 # 1+1, has wildcard

213

214

215# --- Nested ternary ---

216

217 def test_nested_ternary():

218 code = """

219 def f(c1, c2, a, b, d):

220 return a if c1 else (b if c2 else d)

221"""

222 # Outer: NP(a) + NP(inner) = 1 + (1+1) = 3

223 assert _npath(code) == 3

224

225

226# --- Nested function ---

227

228 def test_nested_function():

229 code = """

230 def outer(x):

231 if x:

232 pass

233 def inner(y):

234 if y:

235 pass

236"""

237 npaths = _npaths(code)

238 assert npaths["outer"] == 2 # inner counts as 1

239 assert npaths["inner"] == 2

240

241

242# --- With statement ---

243

244 def test_with_statement():

245 code = """

246 def f():

247 with open("x") as fp:

248 if True:

249 pass

250"""

251 assert _npath(code) == 2 # with body contains if -> npath 2

252

253

254# --- Lambda with ternary ---

255

256 def test_lambda_ternary():

257 code = """

258 def f():

259 g = lambda x: x if x > 0 else -x

260"""

261 # The expression_statement contains a ternary inside the lambda

262 # lambda body is recursed: ternary -> 2

263 assert _npath(code) == 2