diff --git a/Semi_ATE/STDF/utils.py b/Semi_ATE/STDF/utils.py index fcee5da..843ad25 100644 --- a/Semi_ATE/STDF/utils.py +++ b/Semi_ATE/STDF/utils.py @@ -441,19 +441,20 @@ def HEAD_NUM_and_SITE_NUM_from_record(record): def TEST_NUM_from_record(record, endian): ''' given a PTR, MPR or FTR record, extract the test Number. - Note: TEST_NUM is for these records always located on offset 4..7 + Note: TEST_NUM is a U*4 (unsigned 4-byte) field located at offset 4..7 + (inclusive), so the slice is record[4:8]. REC_TYP REC_SUB TEST_NUM (V4) - PTR 15 10 4:7 - MPR 15 15 4:7 - FTR 15 20 4:7 + PTR 15 10 4:8 + MPR 15 15 4:8 + FTR 15 20 4:8 Also note that endian is important here! ''' REC_TYP, REC_SUB = TS_from_record(record) TEST_NUM = -1 if REC_TYP == 15 and REC_SUB in [10, 15, 20]: - TEST_NUM = struct.unpack("%sI" % endian, record[4:7]) + TEST_NUM = int(struct.unpack("%sI" % endian, record[4:8])[0]) return TEST_NUM compression_extensions = ['.gz', '.7z', '.zip', '.xz', '.bz2'] diff --git a/tests/test_utils.py b/tests/test_utils.py index 82d1095..70c6b7f 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -6,13 +6,18 @@ @author: seimit """ import os +import struct import tempfile import gzip import bz2 import lzma import zipfile import time + +import pytest + from Semi_ATE import STDF +from Semi_ATE.STDF.utils import TEST_NUM_from_record def test_records_from_file(): @@ -203,4 +208,39 @@ def test_dict_to_rec(): assert rec.get_value('HEAD_NUM') == head_num assert rec.get_value('SITE_GRP') == site_grp assert rec.get_value('START_T') == start_t - assert rec.get_value('WAFER_ID') == waf_id \ No newline at end of file + assert rec.get_value('WAFER_ID') == waf_id + +def _make_ptr_header(endian: str, test_num: int) -> bytes: + """Build the first 8 bytes of a PTR record: REC_LEN, REC_TYP=15, REC_SUB=10, TEST_NUM.""" + return struct.pack(f"{endian}HBBI", 4, 15, 10, test_num) + + +@pytest.mark.parametrize("endian", ["<", ">"]) +def test_TEST_NUM_from_record_roundtrip(endian): + """A PTR with TEST_NUM > 2**24 round-trips correctly when all 4 bytes are read. + + Regression test for #76: previously the slice was record[4:7] (3 bytes), + which would raise struct.error and made the function unusable for any + PTR/MPR/FTR record. The fix is record[4:8] (4 bytes, matching U*4). + """ + expected = 0x12345678 # > 2**24, exposes any MSB-byte truncation + record = _make_ptr_header(endian, expected) + assert TEST_NUM_from_record(record, endian) == expected + + +def test_TEST_NUM_from_record_returns_int_for_non_test_record(): + """Non-PTR/MPR/FTR records get a sentinel int (-1), not a tuple — the + returned value must always be int for downstream comparisons to work.""" + # FAR record: REC_TYP=0, REC_SUB=10 + record = struct.pack("