Skip to content

Commit e424cb6

Browse files
committed
test: add coverage tests for iteration, introspection, held_cmd, method syntax
99 new targeted tests covering previously uncovered code paths: - Scalar/vector iteration protocol (iteration.jl: 40% → 82%) - numer/denom/real_part/imag_part edge cases (introspection.jl: 51% → 65%) - HeldCmd LaTeX rendering: directional limits, transforms, equations (held_cmd.jl: 70% → 89%) - GiacExpr method-style property access (types.jl) Overall coverage: 75% → 79%
1 parent 5d6d4a7 commit e424cb6

2 files changed

Lines changed: 376 additions & 0 deletions

File tree

test/runtests.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ using LinearAlgebra
126126
# ============================================================================
127127
include("test_held_cmd.jl")
128128

129+
# ============================================================================
130+
# Additional Coverage Tests
131+
# Targets uncovered code paths in iteration, introspection, held_cmd, types
132+
# ============================================================================
133+
include("test_coverage.jl")
134+
129135
# ============================================================================
130136
# MathJSON Conversion Extension Tests (054-mathjson-conversion)
131137
# Verifies bidirectional conversion between GiacExpr and MathJSON types

test/test_coverage.jl

Lines changed: 370 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,370 @@
1+
# Additional tests to improve code coverage for uncovered but valid code paths
2+
3+
# ============================================================================
4+
# 1. Iteration edge cases (src/iteration.jl)
5+
# ============================================================================
6+
@testset "Iteration Coverage" begin
7+
@testset "Scalar iteration" begin
8+
# Iterating over a non-vector GiacExpr yields itself once
9+
x = giac_eval("42")
10+
# Use iterate directly since collect may use getindex which throws for scalars
11+
result = iterate(x)
12+
@test result !== nothing
13+
@test string(result[1]) == "42"
14+
# Second call should return nothing (single element)
15+
result2 = iterate(x, result[2])
16+
@test result2 === nothing
17+
end
18+
19+
@testset "Scalar length and size" begin
20+
x = giac_eval("42")
21+
@test length(x) == 1
22+
@test size(x) == (1,)
23+
@test firstindex(x) == 1
24+
@test lastindex(x) == 1
25+
@test collect(eachindex(x)) == [1]
26+
end
27+
28+
@testset "Vector iteration" begin
29+
v = giac_eval("[1,2,3]")
30+
collected = collect(v)
31+
@test length(collected) == 3
32+
@test string(collected[1]) == "1"
33+
@test string(collected[2]) == "2"
34+
@test string(collected[3]) == "3"
35+
end
36+
37+
@testset "Scalar in operator" begin
38+
# in() on a non-vector compares string representations
39+
x = giac_eval("42")
40+
@test giac_eval("42") in x
41+
@test !(giac_eval("43") in x)
42+
end
43+
44+
@testset "Vector keys/values/pairs" begin
45+
v = giac_eval("[10,20,30]")
46+
ks = collect(keys(v))
47+
@test ks == [1, 2, 3]
48+
vs = collect(values(v))
49+
@test length(vs) == 3
50+
@test string(vs[2]) == "20"
51+
ps = collect(pairs(v))
52+
@test ps[1][1] == 1
53+
@test string(ps[1][2]) == "10"
54+
end
55+
56+
@testset "Vector slicing" begin
57+
v = giac_eval("[1,2,3,4,5]")
58+
sliced = v[2:4]
59+
@test length(sliced) == 3
60+
@test string(sliced[1]) == "2"
61+
62+
all_elems = v[:]
63+
@test length(all_elems) == 5
64+
end
65+
66+
@testset "Indexing errors" begin
67+
x = giac_eval("42")
68+
@test_throws ErrorException x[1]
69+
70+
v = giac_eval("[1,2,3]")
71+
@test_throws BoundsError v[0]
72+
@test_throws BoundsError v[4]
73+
74+
# Range indexing on non-vector
75+
@test_throws ErrorException x[1:2]
76+
end
77+
end
78+
79+
# ============================================================================
80+
# 2. Introspection edge cases (src/introspection.jl)
81+
# ============================================================================
82+
@testset "Introspection Edge Cases" begin
83+
@testset "numer on integer" begin
84+
n = giac_eval("5")
85+
result = Giac.numer(n)
86+
@test string(result) == "5"
87+
end
88+
89+
@testset "denom on integer" begin
90+
n = giac_eval("5")
91+
result = Giac.denom(n)
92+
@test string(result) == "1"
93+
end
94+
95+
@testset "numer/denom on fraction" begin
96+
f = giac_eval("3/7")
97+
@test string(Giac.numer(f)) == "3"
98+
@test string(Giac.denom(f)) == "7"
99+
end
100+
101+
@testset "real_part on real number" begin
102+
r = giac_eval("3")
103+
@test string(Giac.real_part(r)) == "3"
104+
end
105+
106+
@testset "imag_part on real number" begin
107+
r = giac_eval("3")
108+
@test string(Giac.imag_part(r)) == "0"
109+
end
110+
111+
@testset "real_part/imag_part on complex" begin
112+
c = giac_eval("3+4*i")
113+
@test string(Giac.real_part(c)) == "3"
114+
@test string(Giac.imag_part(c)) == "4"
115+
end
116+
117+
@testset "Type predicates" begin
118+
@test Giac.is_integer(giac_eval("42"))
119+
@test !Giac.is_integer(giac_eval("3.14"))
120+
@test Giac.is_numeric(giac_eval("42"))
121+
@test Giac.is_numeric(giac_eval("3.14"))
122+
@test !Giac.is_numeric(giac_eval("x"))
123+
@test Giac.is_vector(giac_eval("[1,2,3]"))
124+
@test !Giac.is_vector(giac_eval("42"))
125+
@test Giac.is_symbolic(giac_eval("sin(x)"))
126+
@test !Giac.is_symbolic(giac_eval("42"))
127+
@test Giac.is_identifier(giac_eval("x"))
128+
@test !Giac.is_identifier(giac_eval("42"))
129+
@test Giac.is_fraction(giac_eval("3/4"))
130+
@test !Giac.is_fraction(giac_eval("42"))
131+
@test Giac.is_boolean(giac_eval("true"))
132+
@test Giac.is_boolean(giac_eval("false"))
133+
@test !Giac.is_boolean(giac_eval("1"))
134+
end
135+
136+
@testset "symb_funcname and symb_argument" begin
137+
s = giac_eval("sin(x)")
138+
@test Giac.symb_funcname(s) == "sin"
139+
arg = Giac.symb_argument(s)
140+
@test string(arg) == "x"
141+
end
142+
143+
@testset "Error on non-fraction numer/denom" begin
144+
x = giac_eval("x")
145+
@test_throws GiacError Giac.numer(x)
146+
@test_throws GiacError Giac.denom(x)
147+
end
148+
149+
@testset "Error on non-symbolic symb_funcname/symb_argument" begin
150+
n = giac_eval("42")
151+
@test_throws GiacError Giac.symb_funcname(n)
152+
@test_throws GiacError Giac.symb_argument(n)
153+
end
154+
end
155+
156+
# ============================================================================
157+
# 3. HeldCmd LaTeX edge cases (src/held_cmd.jl)
158+
# ============================================================================
159+
@testset "HeldCmd LaTeX Coverage" begin
160+
@giac_var x t s n k
161+
162+
@testset "Directional limits" begin
163+
# Left limit (dir = -1)
164+
lim_left = hold_cmd(:limit, giac_eval("1/x"), x, giac_eval("0"), giac_eval("-1"))
165+
latex_left = sprint(show, MIME("text/latex"), lim_left)
166+
@test occursin("\\lim", latex_left)
167+
@test occursin("^-", latex_left)
168+
169+
# Right limit (dir = 1)
170+
lim_right = hold_cmd(:limit, giac_eval("1/x"), x, giac_eval("0"), giac_eval("1"))
171+
latex_right = sprint(show, MIME("text/latex"), lim_right)
172+
@test occursin("\\lim", latex_right)
173+
@test occursin("^+", latex_right)
174+
end
175+
176+
@testset "Limit with fewer args" begin
177+
# 2 args
178+
lim2 = hold_cmd(:limit, giac_eval("1/x"), x)
179+
latex2 = sprint(show, MIME("text/latex"), lim2)
180+
@test occursin("\\lim", latex2)
181+
182+
# 1 arg
183+
lim1 = hold_cmd(:limit, giac_eval("1/x"))
184+
latex1 = sprint(show, MIME("text/latex"), lim1)
185+
@test occursin("\\lim", latex1)
186+
end
187+
188+
@testset "Sum and product" begin
189+
h_sum = hold_cmd(:sum, giac_eval("k^2"), k, giac_eval("1"), giac_eval("10"))
190+
latex_sum = sprint(show, MIME("text/latex"), h_sum)
191+
@test occursin("\\sum", latex_sum)
192+
193+
h_prod = hold_cmd(:product, giac_eval("k"), k, giac_eval("1"), giac_eval("5"))
194+
latex_prod = sprint(show, MIME("text/latex"), h_prod)
195+
@test occursin("\\prod", latex_prod)
196+
end
197+
198+
@testset "Sum/product with fewer args" begin
199+
# 2 args
200+
h2 = hold_cmd(:sum, giac_eval("k^2"), k)
201+
latex2 = sprint(show, MIME("text/latex"), h2)
202+
@test occursin("\\sum", latex2)
203+
204+
# 1 arg
205+
h1 = hold_cmd(:sum, giac_eval("k^2"))
206+
latex1 = sprint(show, MIME("text/latex"), h1)
207+
@test occursin("\\sum", latex1)
208+
end
209+
210+
@testset "HeldEquation with Number" begin
211+
eq = HeldEquation(giac_eval("x^2"), 0)
212+
latex_str = sprint(show, MIME("text/latex"), eq)
213+
@test occursin("=", latex_str)
214+
@test occursin("0", latex_str)
215+
end
216+
217+
@testset "HeldEquation with HeldCmd on left" begin
218+
held = hold_cmd(:integrate, giac_eval("x^2"), x)
219+
eq = HeldEquation(held, giac_eval("x^3/3"))
220+
latex_str = sprint(show, MIME("text/latex"), eq)
221+
@test occursin("\\int", latex_str)
222+
@test occursin("=", latex_str)
223+
end
224+
225+
@testset "HeldEquation with HeldCmd on both sides" begin
226+
lhs = hold_cmd(:diff, giac_eval("x^3/3"), x)
227+
rhs = hold_cmd(:integrate, giac_eval("x"), x)
228+
eq = HeldEquation(lhs, rhs)
229+
latex_str = sprint(show, MIME("text/latex"), eq)
230+
@test occursin("\\frac{d}{dx}", latex_str)
231+
@test occursin("=", latex_str)
232+
end
233+
234+
@testset "HeldEquation plain text" begin
235+
held = hold_cmd(:factor, giac_eval("x^2-1"))
236+
eq = HeldEquation(held, giac_eval("(x-1)*(x+1)"))
237+
txt = sprint(show, eq)
238+
@test occursin("=", txt)
239+
@test occursin("factor", txt)
240+
241+
txt_plain = sprint(show, MIME("text/plain"), eq)
242+
@test occursin("=", txt_plain)
243+
end
244+
245+
@testset "Tilde operator creates HeldEquation" begin
246+
held = hold_cmd(:factor, giac_eval("x^2-1"))
247+
eq1 = held ~ giac_eval("(x-1)*(x+1)")
248+
@test eq1 isa HeldEquation
249+
250+
eq2 = giac_eval("x^2") ~ held
251+
@test eq2 isa HeldEquation
252+
253+
held2 = hold_cmd(:diff, x, x)
254+
eq3 = held ~ held2
255+
@test eq3 isa HeldEquation
256+
257+
eq4 = held ~ 42
258+
@test eq4 isa HeldEquation
259+
260+
eq5 = 42 ~ held
261+
@test eq5 isa HeldEquation
262+
end
263+
264+
@testset "LaTeX: ilaplace alias" begin
265+
h = hold_cmd(:ilaplace, giac_eval("1/s"), s, t)
266+
latex = sprint(show, MIME("text/latex"), h)
267+
@test occursin("\\mathcal{L}^{-1}", latex)
268+
end
269+
270+
@testset "LaTeX: ztransform alias" begin
271+
# Use HeldCmd constructor directly to test LaTeX rendering without command validation
272+
h = HeldCmd(:ztransform, (giac_eval("n"), n, giac_eval("z")))
273+
latex = sprint(show, MIME("text/latex"), h)
274+
@test occursin("\\mathcal{Z}", latex)
275+
end
276+
277+
@testset "LaTeX: invztransform alias" begin
278+
h = HeldCmd(:invztransform, (giac_eval("1/z"), giac_eval("z"), n))
279+
latex = sprint(show, MIME("text/latex"), h)
280+
@test occursin("\\mathcal{Z}^{-1}", latex)
281+
end
282+
283+
@testset "LaTeX: transform with fewer args" begin
284+
# 1 arg
285+
h1 = hold_cmd(:laplace, giac_eval("sin(t)"))
286+
latex1 = sprint(show, MIME("text/latex"), h1)
287+
@test occursin("\\mathcal{L}", latex1)
288+
289+
# 0 args (edge case)
290+
h0 = hold_cmd(:laplace)
291+
latex0 = sprint(show, MIME("text/latex"), h0)
292+
@test occursin("\\mathcal{L}", latex0)
293+
294+
# inv with 1 arg
295+
h1i = hold_cmd(:invlaplace, giac_eval("1/s"))
296+
latex1i = sprint(show, MIME("text/latex"), h1i)
297+
@test occursin("\\mathcal{L}^{-1}", latex1i)
298+
299+
# inv with 0 args
300+
h0i = hold_cmd(:invlaplace)
301+
latex0i = sprint(show, MIME("text/latex"), h0i)
302+
@test occursin("\\mathcal{L}^{-1}", latex0i)
303+
end
304+
305+
@testset "LaTeX: integrate with 1 arg" begin
306+
h = hold_cmd(:integrate, giac_eval("x^2"))
307+
latex = sprint(show, MIME("text/latex"), h)
308+
@test occursin("\\int", latex)
309+
end
310+
311+
@testset "LaTeX: diff with 1 arg" begin
312+
h = hold_cmd(:diff, giac_eval("x^2"))
313+
latex = sprint(show, MIME("text/latex"), h)
314+
@test occursin("\\frac{d}{d?}", latex)
315+
end
316+
317+
@testset "HeldCmd text/plain show" begin
318+
h = hold_cmd(:factor, giac_eval("x^2-1"))
319+
txt = sprint(show, MIME("text/plain"), h)
320+
@test occursin("HeldCmd:", txt)
321+
@test occursin("factor", txt)
322+
end
323+
324+
@testset "_arg_to_latex edge cases" begin
325+
# Symbol arg
326+
h = hold_cmd(:diff, giac_eval("x^2"), :x)
327+
latex = sprint(show, MIME("text/latex"), h)
328+
@test occursin("dx", latex)
329+
330+
# String arg in generic command
331+
h_str = HeldCmd(:foo, ("hello",))
332+
latex_str = sprint(show, MIME("text/latex"), h_str)
333+
@test occursin("\\text{hello}", latex_str)
334+
335+
# Vector arg in generic command
336+
h_vec = HeldCmd(:bar, ([1, 2, 3],))
337+
latex_vec = sprint(show, MIME("text/latex"), h_vec)
338+
@test occursin("[1, 2, 3]", latex_vec)
339+
end
340+
341+
@testset "Generic fallback with no args" begin
342+
h = HeldCmd(:foo, ())
343+
latex = sprint(show, MIME("text/latex"), h)
344+
@test occursin("\\mathrm{foo}", latex)
345+
@test occursin("()", latex)
346+
end
347+
end
348+
349+
# ============================================================================
350+
# 4. GiacExpr method syntax (src/types.jl)
351+
# ============================================================================
352+
@testset "GiacExpr Method Syntax" begin
353+
@testset "Method-style command access" begin
354+
expr = giac_eval("x^2 - 1")
355+
result = expr.factor()
356+
@test string(result) == "(x-1)*(x+1)"
357+
end
358+
359+
@testset "Method-style with additional args" begin
360+
@giac_var x
361+
expr = giac_eval("x^2")
362+
result = expr.integrate(x)
363+
@test occursin("x^3", string(result))
364+
end
365+
366+
@testset "propertynames" begin
367+
expr = giac_eval("42")
368+
@test propertynames(expr) == (:ptr,)
369+
end
370+
end

0 commit comments

Comments
 (0)