Skip to content

Commit ba2ac7f

Browse files
committed
fix(parser): reset component state between channels and add regression coverage
- reset `compenv1_` / `compenv2_` after channel finalization to prevent stale component metadata leaking into subsequent channels - fix false XY interpretation that caused `x and y data have different number of values` on valid IMC2 files - add runtime-generated IMC2 regression test to verify channel state isolation
1 parent 413dd41 commit ba2ac7f

3 files changed

Lines changed: 89 additions & 1 deletion

File tree

lib/imc_channel.hpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,18 @@ namespace imc
529529

530530
if ( xnum_values != ynum_values )
531531
{
532-
throw std::runtime_error("x and y data have different number of values");
532+
throw std::runtime_error(
533+
std::string("x and y data have different number of values")
534+
+ std::string(" (channel uuid='") + uuid_
535+
+ std::string("', name='") + name_
536+
+ std::string("', x_values=") + std::to_string(xnum_values)
537+
+ std::string(", y_values=") + std::to_string(ynum_values)
538+
+ std::string(", x_buffer_size=") + std::to_string(xCSbuffer_size)
539+
+ std::string(", y_buffer_size=") + std::to_string(yCSbuffer_size)
540+
+ std::string(", x_signbits=") + std::to_string(xsignbits_)
541+
+ std::string(", y_signbits=") + std::to_string(ysignbits_)
542+
+ std::string(")")
543+
);
533544
}
534545
xprec_ = 9;
535546
}

lib/imc_raw.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,9 @@ namespace imc
309309
chnenv.CTuuid_.clear();
310310
chnenv.CSuuid_.clear();
311311

312+
chnenv.compenv1_.reset();
313+
chnenv.compenv2_.reset();
314+
312315
compenv_ptr = nullptr;
313316
}
314317
}

tests/test_python.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,80 @@ def test_imc3_signature_reports_unsupported_format(self, tmp_path):
120120
with pytest.raises(RuntimeError, match="unsupported IMC3 format"):
121121
ImcTermite(str(imc3_file).encode())
122122

123+
124+
class TestChannelStateRegression:
125+
"""Regression tests for channel component state handling."""
126+
127+
@staticmethod
128+
def _block(key: str, payload, version: int = 1) -> bytes:
129+
if isinstance(payload, str):
130+
payload_bytes = payload.encode("ascii")
131+
else:
132+
payload_bytes = payload
133+
return (
134+
b"|"
135+
+ key.encode("ascii")
136+
+ b","
137+
+ str(version).encode("ascii")
138+
+ b","
139+
+ str(len(payload_bytes)).encode("ascii")
140+
+ b","
141+
+ payload_bytes
142+
+ b";"
143+
)
144+
145+
def test_component_state_is_reset_between_channels(self, tmp_path):
146+
reg_file = tmp_path / "channel_state_regression.dat"
147+
148+
raw = bytearray()
149+
raw += self._block("CF", "2,1", version=2)
150+
raw += self._block("CK", "1,1")
151+
152+
raw += self._block("CG", "1,3,2")
153+
raw += self._block("CD", "5E-2,1,1,s,0,0,0")
154+
raw += self._block("NT", "1,1,2020,0,0,0.0")
155+
raw += self._block("CC", "1,1")
156+
raw += self._block("CP", "1,2,4,16,0,0,1,0")
157+
raw += self._block("Cb", "1,0,1,1,0,4,0,4,1,0.0,0.0")
158+
raw += self._block("CC", "2,1")
159+
raw += self._block("CP", "2,2,4,16,0,0,1,0")
160+
raw += self._block("Cb", "1,0,2,1,4,4,0,4,1,0.0,0.0")
161+
raw += self._block("CN", "0,0,0,6,CHAN_A,1,A")
162+
raw += self._block("CS", b"1," + bytes([1, 0, 2, 0, 11, 0, 12, 0]))
163+
164+
raw += self._block("CG", "1,1,1")
165+
raw += self._block("CD", "1E-1,1,1,s,0,0,0")
166+
raw += self._block("NT", "1,1,2020,0,0,0.0")
167+
raw += self._block("CC", "1,1")
168+
raw += self._block("CP", "1,2,4,16,0,0,1,0")
169+
raw += self._block("Cb", "1,0,1,1,0,6,0,6,1,0.0,0.0")
170+
raw += self._block("CN", "0,0,0,6,CHAN_B,1,B")
171+
raw += self._block("CS", b"1," + bytes([21, 0, 22, 0, 23, 0]))
172+
173+
reg_file.write_bytes(raw)
174+
175+
imc = ImcTermite(str(reg_file).encode())
176+
channels = {
177+
channel["group"]["name"]: channel
178+
for channel in imc.get_channels(include_data=False)
179+
}
180+
181+
def count_rows(uuid: str) -> int:
182+
total = 0
183+
for chunk in imc.iter_channel_numpy(
184+
uuid.encode("utf-8"),
185+
include_x=True,
186+
chunk_rows=1024,
187+
mode="scaled",
188+
):
189+
assert len(chunk["x"]) == len(chunk["y"])
190+
total += len(chunk["y"])
191+
return total
192+
193+
assert len(channels) == 2
194+
assert count_rows(channels["CHAN_A"]["uuid"]) == 2
195+
assert count_rows(channels["CHAN_B"]["uuid"]) == 3
196+
123197
class TestChunkedNumpy:
124198
"""Test chunked NumPy API"""
125199

0 commit comments

Comments
 (0)