aboutsummaryrefslogtreecommitdiff
path: root/tools/tests/test_barcode_tools.py
blob: 0ee3cc497f33d53fa4fb8255a94cd1f81c212267 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# Author:  cirakg & claude ai
# Summary: Tests for the barcode reference tools: zxing-cpp rows, rendering, decoding, check digits, generator, checker.
from __future__ import annotations

import random
from pathlib import Path

import barcode_ref as ref
import check_codes
import gen_code_vectors
import gen_qr_tables

ROOT = Path(__file__).resolve().parents[2]


def test_reference_rows_have_the_standard_lengths() -> None:
    assert ref.reference_modules(ref.EAN13, "590123412345") == (
        "10100010110100111011001100100110111101001110101010110011011011001000010101110010011101000100101"
    )
    assert len(ref.reference_modules(ref.EAN8, "1234567")) == 67
    assert len(ref.reference_modules(ref.UPCE, "0425261")) == 51


def test_rendered_codes_decode() -> None:
    modules = ref.reference_modules(ref.CODE39, "ABC")
    for scale in (1, 2, 3):
        assert ref.decode(ref.render_linear(modules, scale), ref.CODE39) == ["ABC"]


def test_check_digits() -> None:
    assert ref.gs1_check("590123412345") == 7
    assert ref.gs1_check("1234567") == 0
    assert ref.code39_check("CODE 39") == "R"  # (12+24+13+14+38+3+9) % 43 = 27


def test_expected_reading_normalises_upc() -> None:
    assert ref.expected_reading(ref.UPCA, "012345678905") == "0012345678905"
    assert ref.upce_expand("01234565") == "0012345000065"


def test_generator_only_makes_canonical_upce() -> None:
    rng = random.Random(5)
    for _ in range(200):
        d = gen_code_vectors.valid_upce(rng)
        ref.reference_modules(ref.UPCE, "0" + d)  # zxing-cpp rejects non-canonical ones


def test_committed_vectors_match_the_generator() -> None:
    rows = gen_code_vectors.cases(random.Random(1212), 25)
    committed = (ROOT / "test" / "data" / "CodeVectors.mc").read_text(encoding="utf-8")
    assert committed == gen_code_vectors.render(rows, 1212)


def test_check_codes_reads_dump_lines(tmp_path: Path) -> None:
    good = ref.reference_modules(ref.EAN8, "1234567")
    log = tmp_path / "dev.log"
    log.write_text(
        f"CODEDUMP {ref.EAN8} {b'12345670'.hex()} {good}\nCODEDUMP {ref.EAN8} {b'12345670'.hex()} {good[:-5]}00000\n",
        encoding="utf-8",
    )
    checked, failures = check_codes.check_log(log)
    assert checked == 2 and len(failures) == 3  # the damaged code fails at all three sizes
    assert check_codes.main([str(log)]) == 1
    empty = tmp_path / "empty.log"
    empty.write_text("nothing\n", encoding="utf-8")
    assert check_codes.main([str(empty)]) == 2


def test_committed_qr_tables_match_segno() -> None:
    committed = (ROOT / "source" / "codes" / "QrTables.mc").read_text(encoding="utf-8")
    assert committed == gen_qr_tables.render(gen_qr_tables.table())
    rows = gen_qr_tables.table()
    assert rows[0] == [7, 1, 19, 0, 0]  # version 1-L: 7 EC codewords, one block of 19
    assert len(gen_qr_tables.alignment()) == 40 and gen_qr_tables.alignment()[0] == []


def test_segno_padding_quirk_detection() -> None:
    # Byte mode always ends on a codeword boundary after the terminator, so
    # segno would add a zero byte unless the symbol is exactly full.
    assert gen_code_vectors.segno_pads_extra_byte("hello", 0, 1)
    assert not gen_code_vectors.segno_pads_extra_byte("01234567", 1, 1)


def test_rendered_qr_matrix_decodes() -> None:
    _, _, hexbits = gen_code_vectors.qr_reference("HELLO WORLD", 1, 1, 2)
    image = ref.render_matrix(bytes.fromhex(hexbits), 21, 2, quiet=4)
    assert ref.decode(image, ref.QR) == ["HELLO WORLD"]