Skip to content

Commit

Permalink
DRY up codec tests
Browse files Browse the repository at this point in the history
The test cases for codecs are almost identically repeated 5 times
=> created a base class for these test cases which is inherited
for each codec.

Had to add full-alphabet test against datamatrix-svg for ascii (as
this was currently missing). Also changed full-alphabet tests of
TEXT and X12: So far, we tested a reduced alphabet against
datamatrix-svg because datamatrix-svg has issues with some characters.
Switched this to testing full alphabet but with ground truth
added "manually".

Modified flake8 exceptions in setup.cfg (import of ppf.datamatrix
appears unused but actually *is* used via codecs).

work on issue #14
  • Loading branch information
adrianschlatter committed Sep 15, 2023
1 parent b9ad4d1 commit bb6a3ce
Show file tree
Hide file tree
Showing 13 changed files with 200 additions and 294 deletions.
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ per-file-ignores =
# imported but unused, import *, undefined name:
__init__.py: F401, F403, F821
# imported but unused: Needed due to side-effects of import:
test_ascii.py: F401
test_edifact.py: F401
test_X12.py: F401
test_C40.py: F401
test_text.py: F401
Expand Down
12 changes: 6 additions & 6 deletions src/ppf/datamatrix/codec_C40.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
add_inverse_lookup(codepage)


def encode_to_C40(msg):
def encode(msg):
"""Encode to datamatrix.C40."""
return encode_text_mode(msg, codepage, b'\xE6', True)


def decode_from_C40(enc):
def decode(enc):
"""Decode datamatrix.C40-encoded message."""
try:
msg, length = decode_text_mode(enc, codepage, b'\xE6', True)
Expand All @@ -47,14 +47,14 @@ def decode_from_C40(enc):
return msg, length


def search_codec_C40(encoding_name):
def search_codec(encoding_name):
"""Search function needed for registration in python codecs."""
if encoding_name != 'datamatrix.c40':
return None

return codecs.CodecInfo(encode_to_C40,
decode_from_C40,
return codecs.CodecInfo(encode,
decode,
name='datamatrix.C40')


codecs.register(search_codec_C40)
codecs.register(search_codec)
12 changes: 6 additions & 6 deletions src/ppf/datamatrix/codec_X12.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
add_inverse_lookup(codepage)


def encode_to_X12(msg):
def encode(msg):
"""Encode to datamatrix.X12."""
try:
enc, length = encode_text_mode(msg, codepage, b'\xEE', False)
Expand All @@ -34,7 +34,7 @@ def encode_to_X12(msg):
return enc, length


def decode_from_X12(enc):
def decode(enc):
"""Decode datamatrix.X12-encoded message."""
try:
msg, length = decode_text_mode(enc, codepage, b'\xEE', False)
Expand All @@ -44,14 +44,14 @@ def decode_from_X12(enc):
return msg, length


def search_codec_X12(encoding_name):
def search_codec(encoding_name):
"""Search function needed for registration in python codecs."""
if encoding_name != 'datamatrix.x12':
return None

return codecs.CodecInfo(encode_to_X12,
decode_from_X12,
return codecs.CodecInfo(encode,
decode,
name='datamatrix.X12')


codecs.register(search_codec_X12)
codecs.register(search_codec)
12 changes: 6 additions & 6 deletions src/ppf/datamatrix/codec_ascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
DIGITS = '0123456789'


def encode_to_ascii(msg):
def encode(msg):
"""Encode to datamatrix.ascii."""
enc = []
i = 0
Expand All @@ -41,7 +41,7 @@ def encode_to_ascii(msg):
return bytes(enc), len(enc)


def decode_from_ascii(code):
def decode(code):
"""Decode datamatrix.ascii-encoded message."""
msg = ''
for c in code:
Expand All @@ -53,14 +53,14 @@ def decode_from_ascii(code):
return msg, len(msg)


def search_codec_ascii(encoding_name):
def search_codec(encoding_name):
"""Search function needed for registration in python codecs."""
if encoding_name != 'datamatrix.ascii':
return None

return codecs.CodecInfo(encode_to_ascii,
decode_from_ascii,
return codecs.CodecInfo(encode,
decode,
name='datamatrix.ascii')


codecs.register(search_codec_ascii)
codecs.register(search_codec)
10 changes: 5 additions & 5 deletions src/ppf/datamatrix/codec_edifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def pack(ascii):
return bytes(packed)


def encode_to_edifact(msg):
def encode(msg):
"""Encode message as datamatrix.edifact."""
# We want to encode the characters in msg + the "return to ASCII" code (1F)
# Edifact packs 4 input bytes into 3 output bytes.
Expand All @@ -57,7 +57,7 @@ def encode_to_edifact(msg):
return enc, len(enc)


def decode_from_edifact(enc):
def decode(enc):
"""Decode edifact-encoded message."""
edifact = list(enc)
if edifact[0] != 0xF0:
Expand Down Expand Up @@ -92,13 +92,13 @@ def decode_from_edifact(enc):
return msg, len(msg)


def search_codec_edifact(encoding_name):
def search_codec(encoding_name):
"""Search function needed for registration in python codecs."""
if encoding_name != 'datamatrix.edifact':
return None

return codecs.CodecInfo(encode_to_edifact, decode_from_edifact,
return codecs.CodecInfo(encode, decode,
name='datamatrix.edifact')


codecs.register(search_codec_edifact)
codecs.register(search_codec)
12 changes: 6 additions & 6 deletions src/ppf/datamatrix/codec_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@
add_inverse_lookup(codepage)


def encode_to_text(msg):
def encode(msg):
"""Encode to datamatrix.text."""
return encode_text_mode(msg, codepage, b'\xEF', True)


def decode_from_text(enc):
def decode(enc):
"""Decode datamatrix.text-encoded message."""
try:
msg, length = decode_text_mode(enc, codepage, b'\xEF', True)
Expand All @@ -47,14 +47,14 @@ def decode_from_text(enc):
return msg, length


def search_codec_text(encoding_name):
def search_codec(encoding_name):
"""Search function needed for registration in python codecs."""
if encoding_name != 'datamatrix.text':
return None

return codecs.CodecInfo(encode_to_text,
decode_from_text,
return codecs.CodecInfo(encode,
decode,
name='datamatrix.text')


codecs.register(search_codec_text)
codecs.register(search_codec)
94 changes: 94 additions & 0 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
.. author: Adrian Schlatter
"""

import ppf.datamatrix as put
import sys

# Datamatrix has the following encodings:
# TEXT, C40, X12, EDIFACT, BASE256
# TEXT and C40 both encode the entire ASCII character table (in a
Expand All @@ -18,3 +21,94 @@
EDIFACT = bytes(range(32, 95)).decode('ascii')
X12 = '\r*> 01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'
BASE256 = bytes(range(256))


class Codec_Test(object):
"""
Template class to test codec
Create an actual test class by inheriting from this class and from
unittest.TestCase. Make sure to customize class variables.
"""

CODEC = 'name codec here'
ALPHABET = 'list valid characters here'
ALPHABET_ENC = b'what datamatrix-svg encodes ALPHABET to'
KNOWN_PAIR = {'msg': 'A', 'enc': b'\x63'}
CODEC_MODULE_NAME = 'codec_name'

def test_consistency(self):
"""Verify that coding + decoding return the original message."""
for i in range(len(self.ALPHABET)):
if 2 * i > len(self.ALPHABET):
msg = (self.ALPHABET[i:] +
self.ALPHABET[:3 * i - len(self.ALPHABET)])
else:
msg = self.ALPHABET[i:2 * i]

code = msg.encode(self.CODEC)
decoded = code.decode(self.CODEC)
self.assertEqual(decoded, msg)

def test_raises(self):
"""Verify that error is raised for invalid code."""
code = bytes([0])
with self.assertRaises(ValueError):
code.decode(self.CODEC)

def test_encode_to_square_datamatrix(self):
"""Verify that encoding to square datamatrix works."""
for i in range(len(self.ALPHABET)):
if 2 * i > len(self.ALPHABET):
msg = (self.ALPHABET[i:] +
self.ALPHABET[:3 * i - len(self.ALPHABET)])
else:
msg = self.ALPHABET[i:2 * i]

# assert that this does not raise:
datamatrix = put.DataMatrix(msg)

self.assertTrue(len(datamatrix.matrix) > 0)

def test_encode_to_rect_datamatrix(self):
"""Verify that encoding to rectangular datamatrix works."""
for i in range(len(self.ALPHABET)):
if 2 * i > len(self.ALPHABET):
msg = (self.ALPHABET[i:] +
self.ALPHABET[:3 * i - len(self.ALPHABET)])
else:
msg = self.ALPHABET[i:2 * i]

# assert that this does not raise:
datamatrix = put.DataMatrix(msg, rect=True)

m = datamatrix.matrix
self.assertTrue(len(m) > 0)

def test_encode_known(self):
"""Test single-char edifact encoding"""
enc = self.KNOWN_PAIR['msg'].encode(self.CODEC)
self.assertEqual(enc, self.KNOWN_PAIR['enc'])

def test_encode_alphabet(self):
"""Encode entire alphabet and compare to datamatrix-svg."""
enc = self.ALPHABET.encode(self.CODEC)
self.assertEqual(enc, self.ALPHABET_ENC)

def test_decode_invalid(self):
"""Try to decode invalid code."""

code = 9 * b'\x00'
with self.assertRaises(ValueError):
code.decode(self.CODEC)

def test_search_wrong_codec(self):
"""
Test that search_codec callback returns None for non-matching
codec.
"""

full_mod_name = 'ppf.datamatrix.' + self.CODEC_MODULE_NAME
__import__(full_mod_name)
codec_mod = sys.modules[full_mod_name]
self.assertTrue(codec_mod.search_codec('invalid') is None)
68 changes: 16 additions & 52 deletions tests/test_C40.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,27 @@
.. author: Adrian Schlatter
"""

import ppf.datamatrix
import unittest
from .common import ASCII
import ppf.datamatrix as pu
from .common import ASCII, Codec_Test


class Test_datamatrix_C40(unittest.TestCase):
class Test_datamatrix_C40(Codec_Test, unittest.TestCase):
"""Test codecs datamatrix.C40."""

def test_consistency(self):
"""Verify that coding + decoding return the original message."""
for i in range(128):
if 2 * i > len(ASCII):
msg = ASCII[i:] + ASCII[:3 * i - len(ASCII)]
else:
msg = ASCII[i:2 * i]

code = msg.encode('datamatrix.C40')
decoded = code.decode('datamatrix.C40')
self.assertEqual(decoded, msg)

def test_encode_known_short(self):
"""
Encode short string and verify correctness.
'short' means: Too short to pack a single word.
"""
code = 'A'.encode('datamatrix.C40')
self.assertEqual(code, b'B')
CODEC = 'datamatrix.C40'
ALPHABET = ASCII
ALPHABET_ENC = (
b'\xe6\x00\x01\x06C\x00y\x19\x06\x00\xf1+\xc9\x01i>\x8c\x01'
b'\xe1QO\x02Yd\x12\x02\xd1v\xd5\x03I\x89\x98\x03\xc1\x9c[\x04'
b'9\xaf\x1e\x04\xb1\xc2:\x00*\x06\x92\x12\xed\x07\n%\xb0\x07'
b'\x828s\x07\xfaK6\x08u 83sF\xae\x08\x9ad:\t\x12v\xfd\t\x97`'
b'Rs\x8d\x86\xc8\x9a\x03\xad>\xc0y\xd3\xb4\xe6\xef\xf3\xff\t'
b'\xda\x96B\nS\x00R\x0c\xd3\x13\x15\rK%\xd8\r\xc38\x9b\x0e;K^'
b'\x0e\xb3^!\x0f+p\xe4\x0f\xa3\x83\xa7\x10\x1b\x96j\x10\x93'
b'\xa9-\x11\x0b\xbb\xf0\xfe')
KNOWN_PAIR = {'msg': 'A', 'enc': b'B'}
CODEC_MODULE_NAME = 'codec_C40'

def test_encode_known_long(self):
"""
Expand All @@ -45,35 +38,6 @@ def test_encode_known_long(self):
code = (9 * 'A' + '!').encode('datamatrix.C40')
self.assertEqual(code, b'\xe6Y\xbfY\xbfY\xbf\xfe"')

def test_encode_ASCII(self):
"""Encode ASCII and compare to datamatrix-svg."""
code = ASCII.encode('datamatrix.C40')
truth = (b'\xe6\x00\x01\x06C\x00y\x19\x06\x00\xf1+\xc9\x01i>\x8c\x01'
b'\xe1QO\x02Yd\x12\x02\xd1v\xd5\x03I\x89\x98\x03\xc1\x9c[\x04'
b'9\xaf\x1e\x04\xb1\xc2:\x00*\x06\x92\x12\xed\x07\n%\xb0\x07'
b'\x828s\x07\xfaK6\x08u 83sF\xae\x08\x9ad:\t\x12v\xfd\t\x97`'
b'Rs\x8d\x86\xc8\x9a\x03\xad>\xc0y\xd3\xb4\xe6\xef\xf3\xff\t'
b'\xda\x96B\nS\x00R\x0c\xd3\x13\x15\rK%\xd8\r\xc38\x9b\x0e;K^'
b'\x0e\xb3^!\x0f+p\xe4\x0f\xa3\x83\xa7\x10\x1b\x96j\x10\x93'
b'\xa9-\x11\x0b\xbb\xf0\xfe')
# Note: truth's last code is \xfe which means 'return to ascii'.
# We do not consider it as an error if code is equal to truth
# except for missing an 0xFE at the end:
self.assertTrue(code == truth or code == truth[:-1])

def test_decode_invalid_C40(self):
"""Try to decode invalid code."""

code = 9 * b'\x00'
with self.assertRaises(ValueError):
code.decode('datamatrix.C40')

def test_search_nonTEXT(self):
"""Test that search_codec callback returns None for non-C40."""

from ppf.datamatrix import codec_C40
self.assertTrue(codec_C40.search_codec_C40('invalid') is None)


if __name__ == '__main__':
# This enables running the unit tests by running this script which is
Expand Down
Loading

0 comments on commit bb6a3ce

Please sign in to comment.