Skip to content

Commit 8b6ee99

Browse files
test(unit): add ImportExport unit tests
1 parent d6f8fe5 commit 8b6ee99

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
@testsnippet deps_export_atp begin
2+
using EzXML
3+
end
4+
5+
# TODO: test if serialization works properly if uncertain types are used (Measurements)
6+
7+
@testitem "ImportExport(export_data::atp): export LineCableSystem -> LCC data" setup = [defaults, cable_system_export, deps_export_atp] begin
8+
9+
10+
# 1. ARRANGE & ACT: Run the export in a temporary directory
11+
mktempdir(joinpath(@__DIR__)) do tmpdir
12+
output_file = joinpath(tmpdir, "atp_export_test.xml")
13+
result_path = export_data(:atp, cable_system, earth_props, file_name=output_file)
14+
expected_file = joinpath(dirname(output_file), "$(cable_system.system_id)_$(basename(output_file))")
15+
16+
# 2. ASSERT: Basic file checks (exporter prefixes basename with system_id)
17+
@test result_path == expected_file
18+
@test isfile(expected_file)
19+
@test filesize(expected_file) > 500
20+
21+
# 3. ASSERT: General XML structure and LCC data
22+
@info " Performing high-level XML structure checks..."
23+
doc = readxml(expected_file)
24+
root_node = root(doc)
25+
26+
@test nodename(root_node) == "project"
27+
@test root_node["Application"] == "ATPDraw"
28+
29+
# Find the main LCC component content node
30+
comp_content_node = findfirst("/project/objects/comp/comp_content", root_node)
31+
@test !isnothing(comp_content_node)
32+
33+
# Verify general parameters like Length, Freq, and Ground Resistivity
34+
@info " Verifying general LCC data (Length, Freq, Grnd resis)..."
35+
@test parse(Float64, findfirst("data[@Name='Length']", comp_content_node)["Value"]) cable_system.line_length
36+
@test parse(Float64, findfirst("data[@Name='Freq']", comp_content_node)["Value"]) problem_atp.frequencies[1]
37+
@test parse(Float64, findfirst("data[@Name='Grnd resis']", comp_content_node)["Value"]) problem_atp.earth_props.layers[end].base_rho_g
38+
39+
# 4. ASSERT: Detailed validation of ALL cables and conductors
40+
@info " Verifying all cables and their conductors..."
41+
lcc_node = findfirst("/project/objects/comp/LCC", root_node)
42+
cable_header = findfirst("cable_header", lcc_node)
43+
cable_nodes = findall("cable", cable_header)
44+
45+
@test length(cable_nodes) == num_phases
46+
47+
# Loop through each cable exported in the XML and compare it to the source
48+
for (i, cable_node) in enumerate(cable_nodes)
49+
@info " -> Checking Cable #$i..."
50+
source_cable = cable_system.cables[i]
51+
52+
# Verify position of EACH cable
53+
@test parse(Float64, cable_node["PosX"]) source_cable.horz
54+
@test parse(Float64, cable_node["PosY"]) source_cable.vert
55+
56+
# Verify the number of conductor components inside this cable
57+
num_components = length(source_cable.design_data.components)
58+
@test parse(Int, cable_node["NumCond"]) == num_components
59+
60+
conductor_nodes = findall("conductor", cable_node)
61+
@test length(conductor_nodes) == num_components
62+
63+
# Loop through each conductor component within the cable
64+
for (j, conductor_node) in enumerate(conductor_nodes)
65+
source_component = source_cable.design_data.components[j]
66+
cond_group = source_component.conductor_group
67+
cond_props = source_component.conductor_props
68+
ins_group = source_component.insulator_group
69+
ins_props = source_component.insulator_props
70+
71+
expected_radius_in = cond_group.radius_in
72+
expected_radius_ext = cond_group.radius_ext
73+
expected_rho = cond_props.rho
74+
expected_muC = cond_props.mu_r
75+
expected_epsI = ins_props.eps_r
76+
expected_muI = ins_props.mu_r
77+
expected_Cext = ins_group.shunt_capacitance
78+
expected_Gext = ins_group.shunt_conductance
79+
80+
# Assert that every attribute matches the expected value
81+
@test parse(Float64, conductor_node["Rin"]) expected_radius_in
82+
@test parse(Float64, conductor_node["Rout"]) expected_radius_ext
83+
@test parse(Float64, conductor_node["rho"]) expected_rho
84+
@test parse(Float64, conductor_node["muC"]) expected_muC
85+
@test parse(Float64, conductor_node["muI"]) expected_muI
86+
@test parse(Float64, conductor_node["epsI"]) expected_epsI
87+
@test parse(Float64, conductor_node["Cext"]) expected_Cext
88+
@test parse(Float64, conductor_node["Gext"]) expected_Gext
89+
end
90+
end
91+
@info " All detailed checks passed!"
92+
end
93+
end
94+
95+
96+
97+
98+
@testitem "ImportExport(export_data::atp): export LineParameters -> ZY matrices" setup = [defaults, cable_system_export, deps_export_atp] begin
99+
100+
101+
102+
# 1. RUN THE TEST IN A TEMPORARY DIRECTORY
103+
mktempdir(joinpath(@__DIR__)) do tmpdir
104+
output_file = joinpath(tmpdir, "atp_export_test.xml")
105+
@info " Exporting ATP XML file to: $output_file"
106+
Z_matrix = randn(ComplexF64, num_phases, num_phases, length(freqs))
107+
Y_matrix = randn(ComplexF64, num_phases, num_phases, length(freqs))
108+
line_params = LineParameters(Z_matrix, Y_matrix)
109+
110+
# Call the function we want to test (use the LineParameters overload and pass freqs)
111+
result_path = export_data(:atp, line_params, freqs; file_name=output_file, cable_system=cable_system)
112+
expected_file = joinpath(dirname(output_file), "$(cable_system.system_id)_$(basename(output_file))")
113+
114+
# 2. BASIC FILE CHECKS
115+
@test result_path == expected_file
116+
@test isfile(expected_file)
117+
@test filesize(expected_file) > 100
118+
119+
xml_content = read(expected_file, String)
120+
@test occursin("<ZY", xml_content)
121+
@test occursin("</ZY>", xml_content)
122+
123+
# 3. XML STRUCTURE AND DATA VALIDATION
124+
@info " Performing XML structure checks via XPath..."
125+
xml_doc = readxml(expected_file)
126+
root_node = root(xml_doc)
127+
128+
@test nodename(root_node) == "ZY"
129+
@test parse(Int, root_node["NumPhases"]) == num_phases
130+
131+
# Search the whole document for Z blocks (safer) and assert presence before indexing
132+
z_blocks = findall("//Z", xml_doc)
133+
@test !isempty(z_blocks)
134+
@test length(z_blocks) == length(freqs)
135+
136+
# 4. DETAILED DATA VERIFICATION (for the first frequency)
137+
@info " Verifying numerical data for first frequency..."
138+
first_z_block = z_blocks[1]
139+
@test parse(Float64, first_z_block["Freq"]) freqs[1]
140+
141+
z_matrix_rows = split(strip(nodecontent(first_z_block)), '\n')
142+
@test length(z_matrix_rows) == num_phases
143+
144+
first_row_elements = split(z_matrix_rows[1], ',')
145+
@test length(first_row_elements) == num_phases
146+
number_pattern = r"(-?[\d\.]+E[+-]\d+)"
147+
complex_pattern = Regex("$(number_pattern.pattern)([+-][\\d\\.]+E[+-]\\d+)i")
148+
149+
match_result = match(complex_pattern, first_row_elements[1])
150+
151+
if !isnothing(match_result)
152+
# The captures are now guaranteed to be valid Float64 strings
153+
real_part = parse(Float64, match_result.captures[1])
154+
imag_part = parse(Float64, match_result.captures[2])
155+
parsed_z11 = complex(real_part, imag_part)
156+
157+
expected_z11 = Z_matrix[1, 1, 1]
158+
@test parsed_z11 expected_z11 rtol = 1e-12
159+
end
160+
end
161+
end

0 commit comments

Comments
 (0)