diff options
Diffstat (limited to 'test/barcodetest.py')
-rwxr-xr-x | test/barcodetest.py | 413 |
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()) |