- 11 test functions covering all DRV-02 and DRV-05 behaviors - _detect_encoding tests for UTF-16 LE, UTF-16 BE, UTF-8 BOM, ANSI/cp1252 - parse_inf tests: literal DriverDesc, %TOKEN% resolution, UTF-16 fixture - Multi-model deduplication, architecture detection (x64/arm64/x86/mixed) - .cat file detection, unused files detection, empty models section - INF fixtures: sample.inf, sample_utf16.inf (UTF-16 LE BOM), sample_multi_model.inf - imptune/services/__init__.py package marker
282 lines
7.8 KiB
Python
282 lines
7.8 KiB
Python
"""
|
||
Unit tests for imptune.services.inf_parser.
|
||
|
||
Covers DRV-02 (INF parsing, token resolution, encoding detection,
|
||
multi-model) and DRV-05 (unused file detection).
|
||
"""
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
from imptune.services.inf_parser import ParsedInf, _detect_encoding, parse_inf
|
||
|
||
FIXTURES = Path(__file__).parent / "fixtures"
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# _detect_encoding tests
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def test_detect_encoding_utf16le():
|
||
"""UTF-16 LE BOM (\xff\xfe) is detected as 'utf-16'."""
|
||
raw = b"\xff\xfe" + "[Version]\r\n".encode("utf-16-le")
|
||
assert _detect_encoding(raw) == "utf-16"
|
||
|
||
|
||
def test_detect_encoding_utf16be():
|
||
"""UTF-16 BE BOM (\xfe\xff) is also detected as 'utf-16'."""
|
||
raw = b"\xfe\xff" + "[Version]\r\n".encode("utf-16-be")
|
||
assert _detect_encoding(raw) == "utf-16"
|
||
|
||
|
||
def test_detect_encoding_utf8bom():
|
||
"""UTF-8 BOM (\xef\xbb\xbf) is detected as 'utf-8-sig'."""
|
||
raw = b"\xef\xbb\xbf" + b"[Version]\r\n"
|
||
assert _detect_encoding(raw) == "utf-8-sig"
|
||
|
||
|
||
def test_detect_encoding_ansi():
|
||
"""Byte sequence with no BOM falls back to cp1252 (ANSI)."""
|
||
raw = b"[Version]\r\nSignature=\"$Windows NT$\"\r\n"
|
||
assert _detect_encoding(raw) == "cp1252"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – simple INF with literal driver description
|
||
# ---------------------------------------------------------------------------
|
||
|
||
SIMPLE_INF = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
Class=Printer
|
||
|
||
[Manufacturer]
|
||
Acme=AcmeModels,NTamd64
|
||
|
||
[AcmeModels.NTamd64]
|
||
Acme SuperPrint 9000=Install,{AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA}
|
||
|
||
[Strings]
|
||
"""
|
||
|
||
|
||
def test_simple_driver_desc():
|
||
"""parse_inf extracts literal DriverDesc from a Models section."""
|
||
result = parse_inf(SIMPLE_INF, "acme.inf", ["acme.inf", "acme.dll"])
|
||
assert "Acme SuperPrint 9000" in result.driver_names
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – %TOKEN% resolution from [Strings]
|
||
# ---------------------------------------------------------------------------
|
||
|
||
TOKEN_INF = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
|
||
[Manufacturer]
|
||
%MFG%=Models,NTamd64
|
||
|
||
[Models.NTamd64]
|
||
%HP_DRIVER%=Install,{BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB}
|
||
|
||
[Strings]
|
||
MFG="HP"
|
||
HP_DRIVER="HP LaserJet"
|
||
"""
|
||
|
||
|
||
def test_token_resolution():
|
||
"""parse_inf resolves %TOKEN% references via the [Strings] section."""
|
||
result = parse_inf(TOKEN_INF, "hp.inf", ["hp.inf"])
|
||
assert "HP LaserJet" in result.driver_names
|
||
# Raw token should NOT appear in the output
|
||
assert "%HP_DRIVER%" not in result.driver_names
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – UTF-16 LE fixture
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def test_utf16_encoding():
|
||
"""Reading a UTF-16 LE BOM fixture and passing decoded text to parse_inf produces correct driver_names."""
|
||
raw = (FIXTURES / "sample_utf16.inf").read_bytes()
|
||
encoding = _detect_encoding(raw)
|
||
assert encoding == "utf-16"
|
||
text = raw.decode(encoding, errors="replace")
|
||
result = parse_inf(text, "sample_utf16.inf", ["sample_utf16.inf"])
|
||
assert "Test LaserJet Pro" in result.driver_names
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – multi-model INF (NTamd64 + undecorated)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def test_multi_model_inf():
|
||
"""INF with both NTamd64 and undecorated sections returns deduplicated driver_names."""
|
||
text = (FIXTURES / "sample_multi_model.inf").read_text(encoding="cp1252")
|
||
result = parse_inf(text, "multi.inf", ["multi.inf"])
|
||
# Both driver names must appear exactly once
|
||
assert "Multi Printer 1000" in result.driver_names
|
||
assert "Multi Printer 2000" in result.driver_names
|
||
assert result.driver_names.count("Multi Printer 1000") == 1
|
||
assert result.driver_names.count("Multi Printer 2000") == 1
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – architecture detection
|
||
# ---------------------------------------------------------------------------
|
||
|
||
AMD64_INF = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
|
||
[Manufacturer]
|
||
Mfg=Mfg.Models,NTamd64
|
||
|
||
[Mfg.Models.NTamd64]
|
||
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}
|
||
|
||
[Strings]
|
||
"""
|
||
|
||
ARM64_INF = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
|
||
[Manufacturer]
|
||
Mfg=Mfg.Models,NTarm64
|
||
|
||
[Mfg.Models.NTarm64]
|
||
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}
|
||
|
||
[Strings]
|
||
"""
|
||
|
||
UNDECORATED_INF = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
|
||
[Manufacturer]
|
||
Mfg=Mfg.Models
|
||
|
||
[Mfg.Models]
|
||
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}
|
||
|
||
[Strings]
|
||
"""
|
||
|
||
MIXED_INF = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
|
||
[Manufacturer]
|
||
Mfg=Mfg.Models,Mfg.Models.NTamd64
|
||
|
||
[Mfg.Models]
|
||
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}
|
||
|
||
[Mfg.Models.NTamd64]
|
||
Some Driver=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}
|
||
|
||
[Strings]
|
||
"""
|
||
|
||
|
||
def test_architecture_detection_amd64():
|
||
"""NTamd64 decoration -> architecture='x64'."""
|
||
result = parse_inf(AMD64_INF, "amd64.inf", [])
|
||
assert result.architecture == "x64"
|
||
|
||
|
||
def test_architecture_detection_arm64():
|
||
"""NTarm64 decoration -> architecture='arm64'."""
|
||
result = parse_inf(ARM64_INF, "arm64.inf", [])
|
||
assert result.architecture == "arm64"
|
||
|
||
|
||
def test_architecture_detection_undecorated():
|
||
"""Undecorated section only -> architecture='x86'."""
|
||
result = parse_inf(UNDECORATED_INF, "x86.inf", [])
|
||
assert result.architecture == "x86"
|
||
|
||
|
||
def test_architecture_detection_mixed():
|
||
"""Mixed architectures -> architecture=None (ambiguous)."""
|
||
result = parse_inf(MIXED_INF, "mixed.inf", [])
|
||
assert result.architecture is None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – .cat file detection
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def test_cat_file_detection_present():
|
||
"""zip_names containing a .cat file -> has_cat_file=True."""
|
||
result = parse_inf(SIMPLE_INF, "acme.inf", ["acme.inf", "driver.cat", "acme.dll"])
|
||
assert result.has_cat_file is True
|
||
|
||
|
||
def test_cat_file_detection_absent():
|
||
"""zip_names without a .cat file -> has_cat_file=False."""
|
||
result = parse_inf(SIMPLE_INF, "acme.inf", ["acme.inf", "acme.dll"])
|
||
assert result.has_cat_file is False
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – unused files detection
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def test_unused_files():
|
||
"""ZIP members not referenced in INF text appear in unused_files."""
|
||
inf_text = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
|
||
[Manufacturer]
|
||
Mfg=Mfg.Models
|
||
|
||
[Mfg.Models]
|
||
Some Driver=Install,{DDDDDDDD-DDDD-DDDD-DDDD-DDDDDDDDDDDD}
|
||
|
||
[SourceDisksFiles]
|
||
driver.inf=1
|
||
driver.dll=1
|
||
|
||
[Strings]
|
||
"""
|
||
zip_names = ["driver.inf", "driver.dll", "readme.txt"]
|
||
result = parse_inf(inf_text, "driver.inf", zip_names)
|
||
# readme.txt is not mentioned anywhere in inf_text
|
||
assert "readme.txt" in result.unused_files
|
||
# driver.inf and driver.dll ARE referenced in inf_text
|
||
assert "driver.inf" not in result.unused_files
|
||
assert "driver.dll" not in result.unused_files
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# parse_inf – empty models section
|
||
# ---------------------------------------------------------------------------
|
||
|
||
EMPTY_MODELS_INF = """
|
||
[Version]
|
||
Signature="$Windows NT$"
|
||
|
||
[Manufacturer]
|
||
Mfg=MfgModels
|
||
|
||
[MfgModels]
|
||
|
||
[Strings]
|
||
"""
|
||
|
||
|
||
def test_empty_models_section():
|
||
"""INF with empty Models section returns empty driver_names list."""
|
||
result = parse_inf(EMPTY_MODELS_INF, "empty.inf", [])
|
||
assert result.driver_names == []
|