From 1251aab9f8f9f38aa5bdefd4fed1f58236166cf8 Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 11:24:58 +0200 Subject: test(codes): compare Data Matrix and PDF417 with references and decode them, also from screenshots Co-Authored-By: Claude Opus 5.5 --- test/codes/CodeCacheTest.mc | 10 ++-- test/codes/DataMatrixTest.mc | 110 +++++++++++++++++++++++++++++++++++++++++++ test/codes/Pdf417Test.mc | 70 +++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 test/codes/DataMatrixTest.mc create mode 100644 test/codes/Pdf417Test.mc (limited to 'test/codes') diff --git a/test/codes/CodeCacheTest.mc b/test/codes/CodeCacheTest.mc index e09d902..812acc9 100644 --- a/test/codes/CodeCacheTest.mc +++ b/test/codes/CodeCacheTest.mc @@ -39,7 +39,7 @@ function codeCacheOfLockedEntriesNeedsTheSession(logger as Test.Logger) as Boole e.type = Codes.QR; e.locked = true; var id = vault.add(e); - vault.saveCodeCache(id, [3, 1, 4, 0xE0]b); + vault.saveCodeCache(id, [3, 1, 4, 1, 0xE0]b); vault.lock(); var code = 0; try { @@ -49,6 +49,10 @@ function codeCacheOfLockedEntriesNeedsTheSession(logger as Test.Logger) as Boole } StorageFixtures.unlock(vault, StorageFixtures.PIN); var back = vault.loadCodeCache(id); - return code == VaultError.LOCKED && back != null && Bytes.equal(back, [3, 1, 4, 0xE0]b) - && Codes.Matrix.fromBytes([3, 1, 4, 0xE0]b) != null && Codes.Matrix.fromBytes([3, 1, 4]b) == null; + var pdf = new Codes.Matrix(3, 2, 2, [1, 0, 1, 0, 1, 1]b); + pdf.rowHeight = 3; + var round = Codes.Matrix.fromBytes(pdf.toBytes()); + return code == VaultError.LOCKED && back != null && Bytes.equal(back, [3, 1, 4, 1, 0xE0]b) + && Codes.Matrix.fromBytes([3, 1, 4, 1, 0xE0]b) != null && Codes.Matrix.fromBytes([3, 1, 4]b) == null + && round != null && round.rowHeight == 3 && Bytes.equal(round.modules, [1, 0, 1, 0, 1, 1]b); } diff --git a/test/codes/DataMatrixTest.mc b/test/codes/DataMatrixTest.mc new file mode 100644 index 0000000..ff3cbbe --- /dev/null +++ b/test/codes/DataMatrixTest.mc @@ -0,0 +1,110 @@ +// Author: cirakg & claude ai +// Summary: Tests for the Data Matrix encoder: ppf.datamatrix references (ASCII), Base256 and upper-shift decoding dumps, limits. + +import Toybox.Lang; +import Toybox.Test; + +(:test) +module MatrixFixtures { + + function packedHex(m as Codes.Matrix) as String { + var bits = m.modules; + var out = new [(bits.size() + 7) / 8]b; + for (var i = 0; i < bits.size(); i++) { + if (bits[i] == 1) { + out[i / 8] = out[i / 8] | (0x80 >> (i % 8)); + } + } + return Bytes.toHex(out); + } + + // A line tools/check_codes.py decodes with zxing-cpp. + function dump(logger as Test.Logger, symbology as Number, text as String, m as Codes.Matrix) as Void { + logger.debug("MATRIXDUMP " + symbology + " " + m.width + " " + m.height + " " + m.rowHeight + " " + m.quiet + " " + + Bytes.toHex(Bytes.fromUtf8(text)) + " " + packedHex(m)); + } +} + +// README 12.4 check 1: identical to ppf.datamatrix in ASCII mode, from +// 10 x 10 to 144 x 144 (multi-region and multi-block sizes). +(:test) +function dataMatrixMatchesReference(logger as Test.Logger) as Boolean { + var rows = CodeVectors.DATAMATRIX; + for (var i = 0; i < rows.size(); i++) { + var row = rows[i] as Array; + var payload = row[0] as String; + var result = DataMatrix.encode(payload); + if (result instanceof Codes.CodeError) { + logger.error("row " + i + ": " + result.message); + return false; + } + var expectedSize = row[1] as Number; + var expected = row[2] as String; + var got = MatrixFixtures.packedHex(result); + if (result.width != expectedSize || !got.equals(expected)) { + logger.error("row " + i + " '" + Text.clip(payload, 20) + "': size " + result.width + ", reference " + expectedSize); + return false; + } + } + logger.debug("CODES datamatrix reference rows=" + rows.size()); + return true; +} + +// README 12.4 check 2: Base256 (UTF-8), Upper Shift and plain payloads, +// decoded by tools/check_codes.py. +(:test) +function dataMatrixDumpForDecoding(logger as Test.Logger) as Boolean { + var payloads = ["Zürich", "Ünïcödé 😀 fake card 0042", "é", "1234567890", "Member: fake-0042, valid 2027-12-31", + "ÄÖÜäöüß" + "ÄÖÜäöüß" + "ÄÖÜäöüß", "abc"]; + var long = ""; + for (var i = 0; i < 30; i++) { + long += "é0123456789"; + } + payloads.add(long); + var xs = ""; + for (var i = 0; i < 50; i++) { + xs += "xxxxxxxxxx"; + } + payloads.add(xs); // 88 x 88: 4 x 4 regions, 4 interleaved blocks + var digits = ""; + for (var i = 0; i < 110; i++) { + digits += "0123456789"; + } + payloads.add(digits); // 88 x 88 in digit pairs, comparable with zint + for (var i = 0; i < payloads.size(); i++) { + var text = payloads[i] as String; + var result = DataMatrix.encode(text); + if (result instanceof Codes.CodeError) { + logger.error("'" + text + "': " + result.message); + return false; + } + MatrixFixtures.dump(logger, Codes.DATAMATRIX, text, result); + } + return true; +} + +(:test) +function dataMatrixLimits(logger as Test.Logger) as Boolean { + var big = ""; + for (var i = 0; i < 160; i++) { + big += "abcdefghij"; + } + // (Separate checks: one long && chain of instanceof tests made the + // compiler run out of memory.) + if (!(DataMatrix.encode(big) instanceof Codes.CodeError)) { + logger.error("too long text was accepted"); + return false; + } + if (!(DataMatrix.encode("") instanceof Codes.CodeError)) { + logger.error("empty text was accepted"); + return false; + } + // Base256 is chosen when cheaper than Upper Shift for every byte. + var b256 = DataMatrix.codewords(Bytes.fromUtf8("ÄÖÜ")); + if (b256[0] != DataMatrix.BASE256_LATCH) { + logger.error("Base256 not chosen"); + return false; + } + var ascii = DataMatrix.codewords(Bytes.fromUtf8("A1234")); + return ascii.size() == 3 && ascii[0] == 66 && ascii[1] == 142; +} diff --git a/test/codes/Pdf417Test.mc b/test/codes/Pdf417Test.mc new file mode 100644 index 0000000..8939e03 --- /dev/null +++ b/test/codes/Pdf417Test.mc @@ -0,0 +1,70 @@ +// Author: cirakg & claude ai +// Summary: Tests for the PDF417 encoder: pdf417gen reference rows, decoding dumps of screen-fitted symbols, limits. + +import Toybox.Lang; +import Toybox.Test; + +// README 12.4 check 1: the same rows as pdf417gen for fixed columns and +// levels (compaction, padding, EC, row indicators and patterns). +(:test) +function pdf417MatchesReference(logger as Test.Logger) as Boolean { + var rows = CodeVectors.PDF417; + for (var i = 0; i < rows.size(); i++) { + var row = rows[i] as Array; + var payload = row[0] as String; + var result = Pdf417.encode(payload, row[1] as Number, row[2] as Number); + if (result instanceof Codes.CodeError) { + logger.error("row " + i + ": " + result.message); + return false; + } + var expectedRows = row[3] as Number; + var expectedWidth = row[4] as Number; + if (result.height != expectedRows || result.rowHeight != Pdf417.ROW_HEIGHT || result.width != expectedWidth) { + logger.error("row " + i + " '" + Text.clip(payload, 20) + "': " + result.width + " x " + result.height + + ", reference " + expectedWidth + " x " + expectedRows); + return false; + } + var got = MatrixFixtures.packedHex(result); + if (!got.equals(row[5] as String)) { + logger.error("row " + i + " '" + Text.clip(payload, 20) + "' differs"); + return false; + } + } + logger.debug("CODES pdf417 reference rows=" + rows.size()); + return true; +} + +// README 12.4 check 2: symbols laid out for real screens (columns chosen to +// fit), decoded by tools/check_codes.py. +(:test) +function pdf417DumpForDecoding(logger as Test.Logger) as Boolean { + var payloads = ["Boarding pass FAKE 0042 seat 12A", "Zürich ✓ Ünïcödé fake", "12345678901234567890123456789", + "https://example.com/member/fake?id=0042&level=gold", "a"]; + var screens = [[218, 218, true], [390, 390, true], [320, 360, false]]; + for (var i = 0; i < payloads.size(); i++) { + var s = screens[i % screens.size()] as Array; + var job = new Pdf417.Pdf417Job(payloads[i] as String, 1, s[0] as Number, s[1] as Number, s[2] as Boolean); + Jobs.runToCompletion(job, 1); + var m = job.matrix(); + if (job.error != null || m == null) { + logger.error("'" + payloads[i] + "': " + job.error); + return false; + } + MatrixFixtures.dump(logger, Codes.PDF417, payloads[i] as String, m); + } + return true; +} + +(:test) +function pdf417Limits(logger as Test.Logger) as Boolean { + if (!(Pdf417.encode("", 3, 2) instanceof Codes.CodeError)) { + logger.error("empty text accepted"); + return false; + } + // Two codewords in 30 columns is a single row: PDF417 needs 3. + if (!(Pdf417.encode("ab", 30, 0) instanceof Codes.CodeError)) { + logger.error("a one-row symbol was accepted"); + return false; + } + return Pdf417.autoLevel(40) == 2 && Pdf417.autoLevel(41) == 3 && Pdf417.autoLevel(320) == 4 && Pdf417.autoLevel(321) == 5; +} -- cgit v1.2.3