Files
ImpTune/tests/test_inf_parser.py
T
kawaandClaude Opus 4.6 27ddc77ee1 fix(inf-parser): tolerate bare-line sections in real vendor INFs
Real INFs (e.g. Ricoh oemsetup.inf) include [SourceDisksFiles] entries
with bare filename lines (no '='), which strict configparser rejects
with ParsingError, surfacing as a 500 on /drivers/upload.

Pre-process the INF text to rewrite bare lines into synthetic
__bare_N = <line> entries before parsing, and filter those synthetic
keys out of DriverDesc extraction so they cannot leak into driver_names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:11:45 +02:00

319 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 == []
# ---------------------------------------------------------------------------
# parse_inf tolerates bare-line sections (real-world Ricoh oemsetup.inf)
# ---------------------------------------------------------------------------
BARE_LINE_INF = """\
[Version]
Signature="$Windows NT$"
[Manufacturer]
%MFG%=Models,NTamd64
[Models.NTamd64]
%RICOH_DRIVER%=Install,{CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCCCCCC}
[SourceDisksFiles]
ricu18ui.dll,ricu18ui.dl_
ricu18ui.irj
ricu18ui.rdj
ricu18gl.dll,ricu18gl.dl_
RD01Kd64.dll,RD01Kd64.dl_,,0x00000020
[Strings]
MFG="Ricoh"
RICOH_DRIVER="Ricoh PCL6 Universal"
"""
def test_bare_line_sections_do_not_raise():
"""Real INFs (e.g. Ricoh oemsetup.inf) include [SourceDisksFiles] entries
with bare filename lines and no '=' — parse_inf must not raise and must
still extract DriverDesc from the Models section."""
result = parse_inf(BARE_LINE_INF, "oemsetup.inf", ["oemsetup.inf"])
assert "Ricoh PCL6 Universal" in result.driver_names
# Synthetic __bare_N keys must not leak into driver_names
assert not any(n.startswith("__bare_") for n in result.driver_names)