summaryrefslogtreecommitdiffstats
path: root/test/barcodetest.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/barcodetest.py')
-rwxr-xr-xtest/barcodetest.py413
1 files changed, 413 insertions, 0 deletions
diff --git a/test/barcodetest.py b/test/barcodetest.py
new file mode 100755
index 0000000..295832e
--- /dev/null
+++ b/test/barcodetest.py
@@ -0,0 +1,413 @@
+#!/usr/bin/env python
+# pylint: disable=C0103,C0114,C0115,C0116,C0209,R0912,R0915,W0511,W0603,W0613,W0707,W0212
+
+from __future__ import print_function
+
+import re
+import sys
+import unittest as UT
+import xml.etree.ElementTree as ET
+
+from errno import EISDIR, EINVAL, EACCES
+from io import StringIO
+from os import path, getcwd
+from subprocess import Popen, PIPE
+from traceback import format_exception
+
+try:
+ from urllib.error import HTTPError
+ from urllib.parse import urljoin, urlunparse
+ from urllib.request import urlopen
+except ImportError:
+ from urllib2 import urlopen, HTTPError
+ from urlparse import urljoin, urlunparse
+
+debug = False
+
+# program to run - None means we still need to search for it
+zbarimg = None
+# arguments to said program
+zbarimg_args = ['-q', '--xml', '--nodbus']
+
+
+# namespace support
+try:
+ register_namespace = ET.register_namespace
+except AttributeError:
+ def register_namespace(prefix, uri):
+ ET._namespace_map[uri] = prefix
+
+# barcode results
+BC = 'http://zbar.sourceforge.net/2008/barcode'
+register_namespace('bc', BC)
+
+# testcase extensions
+TS = 'http://zbar.sourceforge.net/2009/test-spec'
+register_namespace('test', TS)
+
+
+# printing support
+def fixtag(node):
+ return str(node.tag).split('}', 1)[-1]
+
+
+def toxml(node):
+ s = StringIO()
+ ET.ElementTree(node).write(s)
+ return s.getvalue()
+
+
+def hexdump(data):
+ print(data)
+ c = 0
+ for i, c in enumerate(data):
+ if i & 0x7 == 0:
+ print('[%04x]' % i, end=' ')
+ print(' %04x' % ord(c), end=' ')
+ if i & 0x7 == 0x7:
+ print()
+ if len(c) & 0x7 != 0x7:
+ print('\n')
+
+
+# search for a file in the distribution
+def distdir_search(search_subdir, base, suffixes=('',)):
+ # start with current dir,
+ # then try script invocation path
+ rundir = path.dirname(sys.argv[0])
+ search = ['', rundir]
+
+ # finally, attempt to follow VPATH if present
+ try:
+ with open('Makefile', 'r') as makefile:
+ for line in makefile:
+ if re.match(r'^VPATH\s*=', line):
+ vpath = line.split('=', 1)[1].strip()
+ if vpath and vpath != rundir:
+ search.append(vpath)
+ break
+ except IOError:
+ pass
+
+ # poke around for subdir
+ subdirs = tuple((search_subdir, path.join('..', search_subdir), '..', ''))
+
+ for prefix in search:
+ for subdir in subdirs:
+ for suffix in suffixes:
+ file = path.realpath(path.join(prefix, subdir, base + suffix))
+ if path.isfile(file):
+ return file
+ return None
+
+
+def find_zbarimg():
+ """search for zbarimg program to run.
+ """
+ global zbarimg
+ # look in dist dir first
+ zbarimg = distdir_search('zbarimg', 'zbarimg', ('', '.exe'))
+ if not zbarimg:
+ # fall back to PATH
+ zbarimg = 'zbarimg'
+ if debug:
+ print('using zbarimg from PATH')
+ elif debug:
+ print('using:', zbarimg)
+
+
+def run_zbarimg(images):
+ """invoke zbarimg for the specified files.
+
+ return results as an ET.Element
+ """
+ args = [zbarimg]
+ args.extend(zbarimg_args)
+ args.extend(images)
+ if debug:
+ print('running:', ' '.join(args))
+
+ # FIXME should be able to pipe (feed) parser straight from output
+ child = Popen(args, stdout=PIPE, stderr=PIPE)
+ (xml, err) = child.communicate()
+
+ rc = child.returncode
+ if debug:
+ print('zbarimg returned', rc)
+
+ # FIXME trim usage from error msg
+ assert rc in (0, 4), \
+ 'zbarimg returned error status (%d):\n\t%s\n' % (rc, str(err.decode()))
+
+ assert not err, err
+
+ result = ET.XML(xml)
+ assert result.tag == ET.QName(BC, 'barcodes')
+ return result
+
+
+class TestCase(UT.TestCase):
+ """single barcode source test.
+
+ must have source attribute set to an ET.Element representation of a
+ bc:source tag before test is run.
+ """
+
+ def __init__(self, methodName):
+ UT.TestCase.__init__(self, methodName)
+ self.source = None
+
+ def shortDescription(self):
+ return self.source.get('href')
+
+ def setUp(self):
+ if not zbarimg:
+ find_zbarimg()
+
+ def runTest(self):
+ expect = self.source
+ assert expect is not None
+ assert expect.tag == ET.QName(BC, 'source')
+ actual = run_zbarimg((expect.get('href'),))
+
+ self.assertEqual(len(actual), 1)
+
+ try:
+ compare_sources(expect, actual[0])
+ except AssertionError:
+ if expect.get(str(ET.QName(TS, 'exception'))) != 'TODO':
+ raise
+ # ignore
+
+
+class BuiltinTestCase(TestCase):
+ def __init__(self, methodName='runTest'):
+ TestCase.__init__(self, methodName)
+
+ href = distdir_search('examples', 'ean-13.png')
+ if not href:
+ href = 'https://git.linuxtv.org/zbar.git/plain/examples/ean-13.png'
+
+ self.source = src = ET.Element(ET.QName(BC, 'source'), href=href)
+ sym = ET.SubElement(src, ET.QName(BC, 'symbol'), type='EAN-13',
+ orientation='UP')
+ data = ET.SubElement(sym, ET.QName(BC, 'data'))
+ data.text = '9789876543217'
+
+
+def compare_maps(expect, actual, compare_func):
+ errors = []
+ notes = []
+ for key, iact in actual.items():
+ iexp = expect.pop(key, None)
+ if iexp is None:
+ errors.append('bonus unexpected result:\n' + toxml(iact))
+ continue
+
+ try:
+ compare_func(iexp, iact)
+ except BaseException:
+ errors.append(''.join(format_exception(*sys.exc_info())))
+
+ if iexp.get(str(ET.QName(TS, 'exception'))) == 'TODO':
+ notes.append('TODO unexpected result:\n' + toxml(iact))
+
+ for key, iexp in expect.items():
+
+ exc = iexp.get(str(ET.QName(TS, 'exception')))
+
+ if exc == 'TODO':
+ notes.append('TODO missing expected result:\n' + toxml(iexp))
+ elif exc is not None:
+ errors.append('missing expected result:\n' + toxml(iexp))
+
+ if len(notes) == 1:
+ print('(TODO)', end=' ', file=sys.stderr)
+ elif notes:
+ print('(%d TODO)' % len(notes), end=' ', file=sys.stderr)
+ assert not errors, '\n'.join(errors)
+
+
+def compare_sources(expect, actual):
+ assert actual.tag == ET.QName(BC, 'source')
+ assert actual.get('href').endswith(expect.get('href')), \
+ 'source href mismatch: %s != %s' % (actual, expect)
+
+ # FIXME process/trim test:* contents
+
+ def map_source(src):
+ if src == '' or src[0].tag != ET.QName(BC, 'index'):
+ # insert artificial hierarchy
+ syms = src[:]
+ del src[:]
+ idx = ET.SubElement(src, ET.QName(BC, 'index'), num='0')
+ idx[:] = syms
+ exc = src.get(str(ET.QName(TS, 'exception')))
+ if exc is not None:
+ idx.set(str(ET.QName(TS, 'exception')), exc)
+ return {'0': idx}
+ elif len(src):
+ assert src[0].tag != ET.QName(BC, 'symbol'), \
+ 'invalid source element: ' + \
+ 'expecting "index" or "symbol", got "%s"' % fixtag(src[0])
+
+ srcmap = {}
+ for idx in src:
+ srcmap[idx.get('num')] = idx
+ return srcmap
+
+ compare_maps(map_source(expect), map_source(actual), compare_indices)
+
+
+def compare_indices(expect, actual):
+ assert actual.tag == ET.QName(BC, 'index')
+ assert actual.get('num') == expect.get('num')
+
+ # FIXME process/trim test:* contents
+
+ def map_index(idx):
+ idxmap = {}
+ for sym in idx:
+ assert sym.tag == ET.QName(BC, 'symbol')
+ typ = sym.get('type')
+ assert typ is not None
+ data = sym.find(str(ET.QName(BC, 'data'))).text
+ idxmap[typ, data] = sym
+
+ return idxmap
+
+ try:
+ compare_maps(map_index(expect), map_index(actual), compare_symbols)
+ except AssertionError:
+ if expect.get(str(ET.QName(TS, 'exception'))) != 'TODO':
+ raise
+
+
+def compare_symbols(expect, actual):
+ orient = expect.get('orientation')
+ if orient:
+ assert actual.get('orientation') == orient
+
+# override unittest.TestLoader to populate tests from xml description
+
+
+class TestLoader:
+ suiteClass = UT.TestSuite
+
+ def __init__(self):
+ self.cwd = urlunparse(('file', '', getcwd() + '/', '', '', ''))
+ if debug:
+ print('cwd =', self.cwd)
+
+ def loadTestsFromModule(self, module):
+ return self.suiteClass([BuiltinTestCase()])
+
+ def loadTestsFromNames(self, names, module=None):
+ suites = [self.loadTestsFromName(name, module) for name in names]
+ return self.suiteClass(suites)
+
+ def loadTestsFromURL(self, url=None, file=None):
+ if debug:
+ print('loading url:', url)
+
+ target = None
+ if not file:
+ if not url:
+ return self.suiteClass([BuiltinTestCase()])
+
+ content = None
+ url = urljoin(self.cwd, url)
+ # FIXME grok fragment
+
+ try:
+ if debug:
+ print('trying:', url)
+ file = urlopen(url)
+ content = file.info().get('Content-Type')
+ except HTTPError:
+ # possible remote directory
+ pass
+ except IOError as e:
+ if e.errno not in (EISDIR, EINVAL, EACCES):
+ raise
+ # could be local directory
+
+ if (not file or
+ content == 'text/html' or
+ (isinstance(file, HTTPError) and file.code != 200)):
+ # could be directory, try opening index
+ try:
+ tmp = urljoin(url + '/', 'index.xml')
+ if debug:
+ print('trying index:', tmp)
+ file = urlopen(tmp)
+ content = file.info().get('Content-Type')
+ url = tmp
+ except IOError:
+ raise IOError('no test index found at: %s' % url)
+
+ if debug:
+ print('\tContent-Type:', content)
+
+ if content not in ('application/xml', 'text/xml'):
+ # assume url is image to test, try containing index
+ # FIXME should be able to keep searching up
+ try:
+ target = url.rsplit('/', 1)[1]
+ index = urljoin(url, 'index.xml')
+ if debug:
+ print('trying index:', index)
+ file = urlopen(index)
+ content = file.info().get('Content-Type')
+ if debug:
+ print('\tContent-Type:', content)
+ assert content in ('application/xml', 'text/xml')
+ url = index
+ except IOError:
+ raise IOError('no index found for: %s' % url)
+
+ index = ET.ElementTree(file=file).getroot()
+ assert index.tag == ET.QName(BC, 'barcodes')
+
+ suite = self.suiteClass()
+ for src in index:
+ # FIXME trim any meta info
+ href = src.get('href')
+ if target and target != href:
+ continue
+ if src.tag == ET.QName(BC, 'source'):
+ test = TestCase()
+ # convert file URLs to filesystem paths
+ href = urljoin(url, href)
+ href = re.sub(r'^file://', '', href)
+ src.set('href', href)
+ test.source = src
+ suite.addTest(test)
+ elif src.tag == ET.QName(TS, 'index'):
+ suite.addTest(self.loadTestsFromURL(urljoin(url, href)))
+ else:
+ raise AssertionError('malformed test index') # FIXME detail
+
+ assert suite.countTestCases(), 'empty test index: %s' % url
+ return suite
+
+ def loadTestsFromName(self, name=None, module=None):
+ return self.loadTestsFromURL(name)
+
+ def unsupported(self, *args, **kwargs):
+ raise TypeError("unsupported TestLoader API")
+
+ # FAKE discover method needed for python3 to work
+ def discover(self, start_dir, pattern, top_level_dir=None):
+ return self.loadTestsFromURL()
+
+ loadTestsFromTestCase = unsupported
+ getTestCaseNames = unsupported
+
+
+if __name__ == '__main__':
+ if '-d' in sys.argv:
+ debug = True
+ sys.argv.remove('-d')
+
+ UT.main(module=None, testLoader=TestLoader())