diff --git a/setup.py b/setup.py index f31c97c..7ce8082 100644 --- a/setup.py +++ b/setup.py @@ -17,8 +17,8 @@ description = 'ASN.1 code generator for Python and C', scripts = ['scripts/tinyber_gen', 'scripts/dax'], package_data = { - 'tinyber': ['data/*.[ch]', 'tinyber/codec.py'], - 'tests': ['*.asn1'], + 'tinyber': ['data/*.[ch]', 'tinyber/codec.py',], + 'tests': ['*.asn1', 'coverage/t0_wrap.pyx'], }, ext_modules = exts, test_suite = "tests", diff --git a/test/run_tests.py b/test/run_tests.py deleted file mode 100644 index 730aff6..0000000 --- a/test/run_tests.py +++ /dev/null @@ -1,47 +0,0 @@ - -# this is a first step toward moving this test suite into the 'tests' -# directory where it can be run with unittest. - -# 1) generate t0.[ch] - -# this is based on ../tests/utils.py - -from asn1ate import parser -from asn1ate.sema import * -from tinyber.walker import Walker - -from tinyber.c_nodes import CBackend -from tinyber import c_nodes as nodes - -def generate(infilename, outfilename): - class FakeArgs(object): - no_standalone = False - - import os - with open(infilename) as f: - asn1def = f.read() - - parse_tree = parser.parse_asn1(asn1def) - modules = build_semantic_model(parse_tree) - assert (len(modules) == 1) - - module_name = outfilename - path = "." - args = FakeArgs() - - # pull in the python-specific node implementations - walker = Walker(modules[0], nodes) - walker.walk() - - backend = CBackend(args, walker, module_name, path) - backend.generate_code() - - -generate ('t0.asn', 't0') - -# 2) build the cython extension in place. -from distutils.core import run_setup -run_setup ('setup.py', ['build_ext', '--inplace']) - -# 3) run the test -execfile ("t0_c_test.py") diff --git a/test/setup.py b/test/setup.py deleted file mode 100644 index 54cf34e..0000000 --- a/test/setup.py +++ /dev/null @@ -1,14 +0,0 @@ -from setuptools import setup, find_packages -from distutils.extension import Extension -from Cython.Build import cythonize - -ext = [ - Extension ("t0_test", ['t0_test.pyx', 't0.c', 'tinyber.c']) - ] - -setup ( - name = 'tinyber_test', - version = '0.1', - packages = find_packages(), - ext_modules = cythonize (ext) - ) diff --git a/test/t0.py b/test/t0.py deleted file mode 100644 index a121738..0000000 --- a/test/t0.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- Mode: Python; indent-tabs-mode: nil -*- - -from t0_ber import * - -def HD(s): - return s.decode ('hex') - -data = HD('300602016302016e') -p = Pair() -p.decode (data) -print p.encode().encode('hex') - -tests = [ - HD('6110300e020203e90101ff30003003020101'), - HD('611230100204400000000101ff30003003020101'), - HD('6110300e020203e901010030003003020101'), - HD('61133011020203e901010030003006020101020102'), - HD('61133011020203e901010030030101ff3003020101'), - HD('61163014020203e901010030060101ff0101003003020101'), - HD('603c303a040361626302013202022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0100'), - HD('603c303a040361626302013302022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0100'), - HD('603c303a040361626302013402022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101ff0a0100'), - HD('603c303a040361626302013202022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0101'), - HD('603c303a040361626302013302022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0101'), - HD('603c303a040361626302013402022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101ff0a0101'), - HD('603c303a040361626302013202022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0102'), - HD('603c303a040361626302013302022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0102'), - HD('603c303a040361626302013402022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101ff0a0102'), -] - -for data in tests: - m = ThingMsg() - m.decode (data) - print m.value diff --git a/test/t0_c_test.py b/test/t0_c_test.py deleted file mode 100644 index 406d538..0000000 --- a/test/t0_c_test.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- Mode: Python -*- - -# run the auto-generated tests on the C codec. - -from coro.asn1.ber import * -from t0_test import try_decode -from t0_gen_test import gen_thingmsg - -class ExpectedGood (Exception): - pass -class ExpectedBad (Exception): - pass -class BadEncoding (Exception): - pass - -# XXX use the unittest framework - -def go(): - n = 0 - for tval, good in gen_thingmsg(): - print tval.encode ('hex'), good - r = try_decode (tval) - if not good: - if r != -1: - # it should have been bad, but wasn't. - raise ExpectedBad - elif r == -1: - # it should have been good, but wasn't. - raise ExpectedGood - elif r != tval: - # it was a good decode, but the encoding wasn't identical. - raise BadEncoding - else: - # it's all good. - pass - n += 1 - print 'passed %d tests' % (n,) - -go() - diff --git a/tests/__init__.py b/tests/__init__.py index e69de29..62e58d5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- Mode: Python -*- + diff --git a/test/Makefile b/tests/coverage/Makefile similarity index 100% rename from test/Makefile rename to tests/coverage/Makefile diff --git a/test/README.md b/tests/coverage/README.md similarity index 100% rename from test/README.md rename to tests/coverage/README.md diff --git a/tests/coverage/__init__.py b/tests/coverage/__init__.py new file mode 100644 index 0000000..86c5dc0 --- /dev/null +++ b/tests/coverage/__init__.py @@ -0,0 +1,11 @@ + +# build t0_wrap.pyx before doing any of the other tests. + +from tests.utils import generate_c, generate_py, test_reload + +print 'building from __init__ script...' +generate_c ('tests/coverage/t0.asn', 't0', 'tests/coverage') +from distutils.core import run_setup +run_setup ('tests/coverage/setup.py', ['build_ext', '--inplace']) + +generate_py ('tests/coverage/t0.asn', 't0', 'tests/coverage') diff --git a/test/handwritten.c b/tests/coverage/handwritten.c similarity index 100% rename from test/handwritten.c rename to tests/coverage/handwritten.c diff --git a/tests/coverage/old/t0.py b/tests/coverage/old/t0.py new file mode 100644 index 0000000..1604943 --- /dev/null +++ b/tests/coverage/old/t0.py @@ -0,0 +1,39 @@ +# -*- Mode: Python; indent-tabs-mode: nil -*- + +def test(): + + from t0_ber import * + + def HD(s): + return s.decode ('hex') + + data = HD('300602016302016e') + p = Pair() + p.decode (data) + print p.encode().encode('hex') + + tests = [ + HD('6110300e020203e90101ff30003003020101'), + HD('611230100204400000000101ff30003003020101'), + HD('6110300e020203e901010030003003020101'), + HD('61133011020203e901010030003006020101020102'), + HD('61133011020203e901010030030101ff3003020101'), + HD('61163014020203e901010030060101ff0101003003020101'), + HD('603c303a040361626302013202022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0100'), + HD('603c303a040361626302013302022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0100'), + HD('603c303a040361626302013402022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101ff0a0100'), + HD('603c303a040361626302013202022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0101'), + HD('603c303a040361626302013302022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0101'), + HD('603c303a040361626302013402022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101ff0a0101'), + HD('603c303a040361626302013202022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0102'), + HD('603c303a040361626302013302022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101000a0102'), + HD('603c303a040361626302013402022711020417bc927a3020300602010a020165300602010a020165300602010a020165300602010a0201650101ff0a0102'), + ] + + for data in tests: + m = ThingMsg() + m.decode (data) + print m.value + +if __name__ == '__main__': + test() diff --git a/test/t0_python_test.py b/tests/coverage/old/t0_python_test.py similarity index 96% rename from test/t0_python_test.py rename to tests/coverage/old/t0_python_test.py index 1d3d799..47ab483 100644 --- a/test/t0_python_test.py +++ b/tests/coverage/old/t0_python_test.py @@ -4,7 +4,7 @@ from coro.asn1.ber import * from t0_ber import ThingMsg -from t0_gen_test import gen_thingmsg +from t0_gen_cases import gen_thingmsg def try_decode (val): try: diff --git a/test/tint.c b/tests/coverage/old/tint.c similarity index 100% rename from test/tint.c rename to tests/coverage/old/tint.c diff --git a/tests/coverage/setup.py b/tests/coverage/setup.py new file mode 100644 index 0000000..8136063 --- /dev/null +++ b/tests/coverage/setup.py @@ -0,0 +1,27 @@ +from distutils.core import setup +from distutils.extension import Extension +from Cython.Build import cythonize + +exts = [ + Extension ("t0_wrap", ['t0_wrap.pyx', 't0.c', 'tinyber.c']) + ] + +# this is a complete hack to try to get unittest to work with this +# generated module, and avoid trying to compile it more than once (in +# the wrong place). + +import os +if 't0_wrap.so' in os.listdir ('tests/coverage'): + pass +else: + import os + here = os.getcwd() + src, _ = os.path.split (__file__) + os.chdir (src) + try: + setup ( + ext_modules = cythonize (exts), + ) + finally: + os.chdir (here) + diff --git a/test/t0.asn b/tests/coverage/t0.asn similarity index 80% rename from test/t0.asn rename to tests/coverage/t0.asn index b26e1b7..12b0899 100644 --- a/test/t0.asn +++ b/tests/coverage/t0.asn @@ -1,6 +1,6 @@ -- -*- Mode: asn1; indent-tabs-mode: nil -*- --- test module meant to exercise all supported features of tinyber_gen.py +-- test module meant to exercise all supported features of tinyber's code generators. ThingModule DEFINITIONS ::= BEGIN @@ -8,8 +8,9 @@ ThingModule DEFINITIONS ::= BEGIN ThingMsg ::= CHOICE { msg-a [0] MsgA, - msg-b [1] MsgB, - msg-c [50] MsgC + msg-b [1] MsgB, + msg-c [50] MsgC, + msg-d [2] MsgD } Pair ::= SEQUENCE { @@ -46,5 +47,10 @@ ThingModule DEFINITIONS ::= BEGIN tbool BOOLEAN } + MsgD ::= SEQUENCE { + -- test negative ranges -- + x INTEGER (-10..10), + y INTEGER (-10..-5) + } END diff --git a/tests/coverage/t0_c_driver.py b/tests/coverage/t0_c_driver.py new file mode 100644 index 0000000..ff7af69 --- /dev/null +++ b/tests/coverage/t0_c_driver.py @@ -0,0 +1,38 @@ +# -*- Mode: Python -*- + +# run the auto-generated tests on the C codec. + +import unittest + +from tests.coverage.t0_gen_cases import gen_thingmsg +from tests.utils import test_reload, generate_c + +class ExpectedGood (Exception): + pass +class ExpectedBad (Exception): + pass +class BadEncoding (Exception): + pass + +class TestBasic(unittest.TestCase): + + def test_c_coverage (self): + # this is disgusting, but "from tests.coverage.t0_wrap" does not work here. + test_reload() + import sys + sys.path.append ('tests/coverage') + from t0_wrap import try_decode + for tval, good in gen_thingmsg(): + #print tval.encode ('hex'), good + r = try_decode (tval) + if not good: + # it should have been bad, but wasn't. + self.assertEqual (r, -1) + else: + self.assertNotEqual (r, -1, msg=tval) + self.assertEqual (r, tval) + +if __name__ == '__main__': + unittest.main() + + diff --git a/test/t0_gen_test.py b/tests/coverage/t0_gen_cases.py similarity index 55% rename from test/t0_gen_test.py rename to tests/coverage/t0_gen_cases.py index 2feb85e..729c313 100644 --- a/test/t0_gen_test.py +++ b/tests/coverage/t0_gen_cases.py @@ -1,6 +1,6 @@ -from coro.asn1.ber import * -from t0_test import try_decode +#from coro.asn1.ber import * +from cyber.ber import * # this will auto-generate test cases - both good and bad ones - to exhaustively # cover the tinyber codec generated for t0.asn. @@ -15,11 +15,11 @@ def gen_pair(): # unwanted negative integer (SEQUENCE (INTEGER (-5), INTEGER (-6)), False), # junk - ('asdfasdfasdf', False), - ('\xDE\xAD\xBE\xEF', False), - ('x', False), + (b'asdfasdfasdf', False), + (b'\xDE\xAD\xBE\xEF', False), + (b'x', False), # trailing junk - (SEQUENCE (INTEGER (10), INTEGER (101), 'asdfasdf'), False), + (SEQUENCE (INTEGER (10), INTEGER (101), b'asdfasdf'), False), (SEQUENCE (INTEGER (10), INTEGER (101), BOOLEAN(True)), False), ] @@ -33,8 +33,8 @@ def gen_color(): # bad type (INTEGER (99), False), # junk - ('wieuriuwiusdf', False), - ('x', False), + (b'wieuriuwiusdf', False), + (b'x', False), ] def gen_msgb(): @@ -56,12 +56,12 @@ def gen_msgb(): # out of range in y (SEQUENCE (INTEGER (1001), BOOLEAN(False), SEQUENCE(), SEQUENCE (INTEGER (1), INTEGER (1001))), False), # extra data - (SEQUENCE (INTEGER (1001), BOOLEAN(False), SEQUENCE(), BOOLEAN(True), OCTET_STRING ("asdfasdfasdfasdfasdfasdfasdfasdfasdf")), False), + (SEQUENCE (INTEGER (1001), BOOLEAN(False), SEQUENCE(), BOOLEAN(True), OCTET_STRING (b"asdfasdfasdfasdfasdfasdfasdfasdfasdf")), False), # not enough data (SEQUENCE (BOOLEAN(False), BOOLEAN(True)), False), (INTEGER (99), False), - ('ksdjfkjwekrjasdf', False), - ('x', False), + (b'ksdjfkjwekrjasdf', False), + (b'x', False), ] def gen_msga(): @@ -70,38 +70,47 @@ def gen_msga(): for color, good_color in gen_color(): result.extend ([ # -- potentially good data --- - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (50), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), good_pair and good_color), - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (51), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), good_pair and good_color), - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (52), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(True), color), good_pair and good_color), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (50), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), good_pair and good_color), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (51), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), good_pair and good_color), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (52), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(True), color), good_pair and good_color), # --- known to be bad data --- # not enough entries - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (52), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (52), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), # bad first type (SEQUENCE (INTEGER (99), INTEGER (50), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), False), # out of range integers... - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (410), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), False), - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (53), INTEGER (16555), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), False), - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (54), INTEGER (10001), INTEGER (1<<33), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (410), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (53), INTEGER (16555), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (54), INTEGER (10001), INTEGER (1<<33), SEQUENCE (pair,pair,pair,pair), BOOLEAN(False), color), False), # bad type in SEQUENCE OF - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (55), INTEGER (16555), INTEGER (99), SEQUENCE (INTEGER(99)), BOOLEAN(False), color), False), - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (56), INTEGER (16555), INTEGER (99), INTEGER (99), BOOLEAN(False), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (55), INTEGER (16555), INTEGER (99), SEQUENCE (INTEGER(99)), BOOLEAN(False), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (56), INTEGER (16555), INTEGER (99), INTEGER (99), BOOLEAN(False), color), False), # bad type in place of BOOLEAN - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (57), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), INTEGER(-9), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (57), INTEGER (10001), INTEGER (398234234), SEQUENCE (pair,pair,pair,pair), INTEGER(-9), color), False), # negative integers in unexpected places - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (58), INTEGER (10001), INTEGER (-1000), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (59), INTEGER (-100), INTEGER (-1000), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), - (SEQUENCE (OCTET_STRING ('abc'), INTEGER (-20), INTEGER (-100), INTEGER (-1000), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (58), INTEGER (10001), INTEGER (-1000), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (59), INTEGER (-100), INTEGER (-1000), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), + (SEQUENCE (OCTET_STRING (b'abc'), INTEGER (-20), INTEGER (-100), INTEGER (-1000), SEQUENCE (pair,pair,pair), BOOLEAN(True), color), False), ]) return result def gen_msgc(): return [ - (SEQUENCE (OCTET_STRING (''), BOOLEAN (True)), True), - (SEQUENCE (OCTET_STRING ('x' * 499), BOOLEAN (True)), True), - (SEQUENCE (OCTET_STRING ('x' * 501), BOOLEAN (True)), False), + (SEQUENCE (OCTET_STRING (b''), BOOLEAN (True)), True), + (SEQUENCE (OCTET_STRING (b'x' * 499), BOOLEAN (True)), True), + (SEQUENCE (OCTET_STRING (b'x' * 501), BOOLEAN (True)), False), ] +def gen_msgd(): + # x INTEGER (-10..10), + # y INTEGER (-10..-5) + # exhaustive test around the range + for i in range (-20, 20): + for j in range (-20, 20): + good = (-10 <= i <= 10) and (-10 <= j <= -5) + yield (SEQUENCE (INTEGER (i), INTEGER (j)), good) + def gen_thingmsg(): result = [] for msgb, good in gen_msgb(): @@ -116,4 +125,6 @@ def gen_thingmsg(): result.append ((APPLICATION (1, True, msga), False),) for msgc, good in gen_msgc(): result.append ((APPLICATION (50, True, msgc), good)) + for msgd, good in gen_msgd(): + result.append ((APPLICATION (2, True, msgd), good)) return result diff --git a/tests/coverage/t0_test_encoder.py b/tests/coverage/t0_test_encoder.py new file mode 100644 index 0000000..22dfd52 --- /dev/null +++ b/tests/coverage/t0_test_encoder.py @@ -0,0 +1,129 @@ +# -*- Mode: Python -*- + +# exhaustive test of the encoder, using t0.asn + +# Note: the encoder does not [yet] check constraints, only the decoder. +# this test suite will need slight tweaking once that is added. + +import unittest +from tests.utils import test_reload, generate_c + +import sys +sys.path.append ('tests/coverage') + +import t0_ber +print t0_ber.__file__ + +from t0_ber import * + +class NoError (Exception): + pass + +def union_error (*errors): + r = NoError + # return the most specific error when possible + for e in errors: + if e is not NoError: + if r is NoError: + r = e + else: + # more than one specific error, use the base class + return DecodingError + return r + +def gen_pair(): + return [ + # good + (NoError, Pair (a=10, b=101)), + # out of range integer + (ConstraintViolation, Pair (a=10, b=10)), + (ConstraintViolation, Pair (a=1001, b=10)), + # unwanted negative integer + (ConstraintViolation, Pair (a=-5, b=-6)), + ] + +def gen_color(): + return [ + (NoError, Color ('red')), + (NoError, Color ('blue')), + (NoError, Color ('green')), + (BadEnum, Color ('orange')), + ] + +def gen_msgc(): + return [ + (NoError, MsgC (lstr = b'testing', tbool=False)), + (NoError, MsgC (lstr = b'x' * 499, tbool=False)), + (ConstraintViolation, MsgC (lstr = b'x' * 501, tbool=False)), + ] + +def gen_msgb(): + return [ + (NoError, MsgB (a=1001, b=True, x=[], y=[1])), + (NoError, MsgB (a=1<<30, b=True, x=[], y=[1])), + (NoError, MsgB (a=1001, b=False, x=[], y=[1])), + (NoError, MsgB (a=1001, b=False, x=[], y=[1, 2])), + # exactly one x + (NoError, MsgB (a=1001, b=False, x=[True], y=[1, 2])), + # exactly two x + (NoError, MsgB (a=1001, b=False, x=[True, False], y=[1, 2])), + # too many x + (ConstraintViolation, MsgB (a=1001, b=False, x=[True, False, True], y=[1, 2])), + # < 1 y + (ConstraintViolation, MsgB (a=1001, b=False, x=[], y=[])), + # out of range y + (ConstraintViolation, MsgB (a=1001, b=False, x=[], y=[1, 1001])), + ] + +def gen_msga(): + for error0, pair in gen_pair(): + for error1, color in gen_color(): + r0 = [ + # potentially good data + (NoError, MsgA (toctet=b'abc', t8int=50, t16int=10001, t32int=398234234, tarray=[pair, pair, pair, pair], tbool=False, tenum=color)), + (NoError, MsgA (toctet=b'abc', t8int=51, t16int=10001, t32int=398234234, tarray=[pair, pair, pair, pair], tbool=False, tenum=color)), + (NoError, MsgA (toctet=b'abc', t8int=52, t16int=10001, t32int=398234234, tarray=[pair, pair, pair, pair], tbool=False, tenum=color)), + (NoError, MsgA (toctet=b'abc', t8int=255, t16int=10001, t32int=398234234, tarray=[pair, pair, pair, pair], tbool=False, tenum=color)), + (ConstraintViolation, MsgA (toctet=b'abc', t8int=256, t16int=10001, t32int=398234234, tarray=[pair, pair, pair, pair], tbool=False, tenum=color)), + (ConstraintViolation, MsgA (toctet=b'abc', t8int=-1, t16int=10001, t32int=398234234, tarray=[pair, pair, pair, pair], tbool=False, tenum=color)), + # not enough entries + (ConstraintViolation, MsgA (toctet=b'abc', t8int=52, t16int=10001, t32int=398234234, tarray=[pair, pair, pair], tbool=False, tenum=color)), + # bad first type + (ConstraintViolation, MsgA (toctet=99, t8int=52, t16int=10001, t32int=398234234, tarray=[pair, pair, pair], tbool=False, tenum=color)), + ] + for error2, msg in r0: + yield union_error (error0, error1, error2), msg + +def gen_thingmsg(): + for error, msga in gen_msga(): + yield (error, ThingMsg (msga)) + for error, msgb in gen_msgb(): + yield (error, ThingMsg (msgb)) + for error, msgc in gen_msgc(): + yield (error, ThingMsg (msgc)) + +class TestEncoder(unittest.TestCase): + + # the codec does not currently check constraints in the *encoder*, + # so to verify contraint checking we do a round trip. + def round_trip (self, ob): + ob0 = ob.__class__() + encoded = ob.encode() + ob0.decode (encoded) + self.assertEqual (ob0, ob) + raise NoError + + def test_round_trip (self): + test_reload() + # all generators, simplest to the most complex + gens = [gen_pair, gen_color, gen_msgc, gen_msgb, gen_msga, gen_thingmsg] + n = 0 + for gen in gens: + for expected, ob in gen(): + with self.assertRaises (expected): + self.round_trip (ob) + n += 1 + # XXX would be nice if there was some way to report the total number of tests here. + +if __name__ == '__main__': + unittest.main() diff --git a/test/t0_test.pyx b/tests/coverage/t0_wrap.pyx similarity index 100% rename from test/t0_test.pyx rename to tests/coverage/t0_wrap.pyx diff --git a/tests/test_choice.py b/tests/test_choice.py index 0276e04..e0bb47a 100644 --- a/tests/test_choice.py +++ b/tests/test_choice.py @@ -7,13 +7,13 @@ from tinyber.py_nodes import PythonBackend as Backend from tinyber import py_nodes as nodes -from tests.utils import generate, test_reload +from tests.utils import generate_py, test_reload class TestBasic(unittest.TestCase): @classmethod def setUpClass(cls): - generate("tests/test_choice.asn1", "gen_choice") + generate_py("tests/test_choice.asn1", "gen_choice", 'tests') @classmethod def tearDownClass(cls): diff --git a/tests/utils.py b/tests/utils.py index 8276210..cb4adfd 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,7 +6,7 @@ from tinyber import py_nodes as nodes -def generate(infilename, outfilename): +def generate_py(infilename, outfilename, path): class FakeArgs(object): no_standalone = False @@ -19,7 +19,6 @@ class FakeArgs(object): assert (len(modules) == 1) module_name = outfilename - path = "tests" args = FakeArgs() # pull in the python-specific node implementations @@ -29,6 +28,32 @@ class FakeArgs(object): backend = Backend(args, walker, module_name, path) backend.generate_code() +from tinyber.c_nodes import CBackend +from tinyber import c_nodes + +def generate_c(infilename, outfilename, path): + class FakeArgs(object): + no_standalone = False + + import os + with open(infilename) as f: + asn1def = f.read() + + parse_tree = parser.parse_asn1(asn1def) + modules = build_semantic_model(parse_tree) + assert (len(modules) == 1) + + module_name = outfilename + args = FakeArgs() + + # pull in the python-specific node implementations + walker = Walker(modules[0], c_nodes) + walker.walk() + + backend = CBackend(args, walker, module_name, path) + backend.generate_code() + + def test_reload(): import sys sys.path[:0] = '.' diff --git a/tinyber/_codec.pyx b/tinyber/_codec.pyx index 72fa3bc..56b4e73 100644 --- a/tinyber/_codec.pyx +++ b/tinyber/_codec.pyx @@ -36,6 +36,9 @@ class BadChoice (DecodingError): class ExtraData (DecodingError): pass +class BadEnum (DecodingError): + pass + # flags for BER tags cdef enum FLAGS: FLAGS_UNIVERSAL = 0x00 @@ -171,11 +174,10 @@ cdef class Decoder: if n & 0x80: # negative n -= 0x100 - else: - while length: - n = n << 8 | self.pop_byte() - length -= 1 - return n + while length: + n = n << 8 | self.pop_byte() + length -= 1 + return n def next_INTEGER (self, min_val, max_val): self.check (TAGS_INTEGER) @@ -332,7 +334,7 @@ cdef class Encoder: cpdef emit_OCTET_STRING (self, s): with self.TLV (TAGS_OCTET_STRING): - self.emit (s) + self.emit (bytearray(s)) cpdef emit_BOOLEAN (self, v): with self.TLV (TAGS_BOOLEAN): @@ -352,6 +354,10 @@ class ASN1: def decode (self, data): b = Decoder (data) self._decode (b) + def __eq__ (self, other): + return isinstance (other, self.__class__) and self.value == other.value + def __ne__ (self, other): + return not self.__eq__ (other) def __repr__ (self): return '<%s %r>' % (self.__class__.__name__, self.value) @@ -360,6 +366,14 @@ class SEQUENCE (ASN1): def __init__ (self, **args): for k, v in args.iteritems(): setattr (self, k, v) + def __eq__ (self, other): + if not isinstance (other, self.__class__): + return False + else: + for name in self.__slots__: + if getattr (self, name) != getattr (other, name): + return False + return True def __repr__ (self): r = [] for name in self.__slots__: @@ -389,9 +403,15 @@ class ENUMERATED (ASN1): value = 'NoValueDefined' def _decode (self, Decoder src): v = src.next_ENUMERATED() - self.value = self.tags_r[v] + try: + self.value = self.tags_r[v] + except KeyError: + raise BadEnum (v) def _encode (self, Encoder dst): with dst.TLV (TAGS_ENUMERATED): - dst.emit_integer (self.tags_f[self.value]) + try: + dst.emit_integer(self.tags_f[self.value]) + except KeyError: + raise BadEnum (self.value) def __repr__ (self): return '<%s %s>' % (self.__class__.__name__, self.value) diff --git a/tinyber/c_nodes.py b/tinyber/c_nodes.py index 9acf50c..d469a0f 100644 --- a/tinyber/c_nodes.py +++ b/tinyber/c_nodes.py @@ -365,7 +365,7 @@ class CBackend: def __init__ (self, args, walker, module_name, path): self.args = args self.walker = walker - self.module_name = module_name + _, self.module_name = os.path.split (module_name) self.path = path self.base_path = os.path.join(path, module_name) diff --git a/tinyber/codec.py b/tinyber/codec.py index cd76e96..8ba15a7 100644 --- a/tinyber/codec.py +++ b/tinyber/codec.py @@ -41,6 +41,9 @@ class ExtraData(DecodingError): pass +class BadEnum(DecodingError): + pass + class FLAG: UNIVERSAL = 0x00 STRUCTURED = 0x20 @@ -162,11 +165,10 @@ def get_integer(self, length): if n & 0x80: # negative n -= 0x100 - else: - while length: - n = n << 8 | self.pop_byte() - length -= 1 - return n + while length: + n = n << 8 | self.pop_byte() + length -= 1 + return n def next_INTEGER(self, min_val, max_val): self.check(TAG.INTEGER) @@ -295,7 +297,7 @@ def emit_INTEGER(self, n): def emit_OCTET_STRING(self, s): with self.TLV(TAG.OCTETSTRING): - self.emit(s) + self.emit(bytearray(s)) def emit_BOOLEAN(self, v): with self.TLV(TAG.BOOLEAN): @@ -320,6 +322,12 @@ def decode(self, data): b = Decoder(data) self._decode(b) + def __eq__ (self, other): + return self.value == other.value + + def __ne__ (self, other): + return not self.__eq__ (other) + def __repr__(self): return '<%s %r>' % (self.__class__.__name__, self.value) @@ -331,6 +339,12 @@ def __init__(self, **args): for k, v in args.iteritems(): setattr(self, k, v) + def __eq__ (self, other): + for name in self.__slots__: + if getattr (self, name) != getattr (other, name): + return False + return True + def __repr__(self): r = [] for name in self.__slots__: @@ -363,11 +377,17 @@ class ENUMERATED(ASN1): def _decode(self, src): v = src.next_ENUMERATED() - self.value = self.tags_r[v] + try: + self.value = self.tags_r[v] + except KeyError: + raise BadEnum (v) def _encode(self, dst): with dst.TLV(TAG.ENUMERATED): - dst.emit_integer(self.tags_f[self.value]) + try: + dst.emit_integer(self.tags_f[self.value]) + except KeyError: + raise BadEnum (self.value) def __repr__(self): return '<%s %s>' % (self.__class__.__name__, self.value)