From 99b84cea4d6b074066fe0596b54683e43fa4ef4a Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Thu, 17 Mar 2022 18:58:18 +0300 Subject: [PATCH 1/3] ci: setup GitHub Actions testing pipeline Before this patch, tarantool/tarantool-python CI was based on Travis CI (Linux runs) and Appveyor (Windows runs). This PR introduces GitHub Actions workflow files for both Linux and Windows test runs. Pipelines run for different supported tarantool, Python and msgpack package versions to ensure compatibility. tarantool instance is started in each Windows pipeline under WSL to run tests instead of using an external server (like in Appveyor). Since we start a new tarantool instance for each run, clean() procedure was removed. (The main reason to remove it was failing with "ER_DROP_FUNCTION: Can't drop function 1: function is SQL built-in" error on newer 2.x on bootstrap.) Testing pipeline for tarantool artifacts was also updated. Travis CI and Appveyor badges were replaced with GitHub Actions badge. Closes #182 --- .github/workflows/reusable_testing.yml | 34 +++- .github/workflows/testing.yml | 210 ++++++++++++++++++++++++ README.rst | 7 +- test/suites/lib/tarantool_python_ci.lua | 2 +- 4 files changed, 245 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/testing.yml diff --git a/.github/workflows/reusable_testing.yml b/.github/workflows/reusable_testing.yml index c9528f8b..70dc4386 100644 --- a/.github/workflows/reusable_testing.yml +++ b/.github/workflows/reusable_testing.yml @@ -12,6 +12,26 @@ on: jobs: run_tests: runs-on: ubuntu-20.04 + + strategy: + fail-fast: false + matrix: + python: + - '2.7' + - '3.4' + - '3.5' + - '3.6' + - '3.7' + - '3.8' + - '3.9' + - '3.10' + msgpack-deps: + - 'msgpack-python==0.4.0' + - 'msgpack==0.5.0' + - 'msgpack==0.6.2' + - 'msgpack==1.0.0' + # latest msgpack will be installed as a part of requirements.txt + - '' steps: - name: Clone the tarantool-python connector uses: actions/checkout@v2 @@ -28,12 +48,22 @@ jobs: # dependencies when migrating to other OS version. run: sudo dpkg -i tarantool*.deb - - name: Setup python3 for tests + - name: Setup Python for tests uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: ${{ matrix.python }} + + - name: Install msgpack python package + if: matrix.msgpack-deps != '' + run: pip install ${{ matrix.msgpack-deps }} - name: Install connector requirements + # msgpack package is a replacement for deprecated msgpack-python. + # To test compatibility with msgpack-python we must ignore + # requirements.txt install, since it will install newest msgpack. + # Beware that if any new dependency will be added to requirements.txt, + # this step will be invalid. + if: matrix.msgpack-deps != 'msgpack-python==0.4.0' run: pip install -r requirements.txt - name: Install test requirements diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 00000000..98b413ce --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,210 @@ +name: testing + +on: + push: + pull_request: + +jobs: + run_tests_linux: + # We want to run on external PRs, but not on our own internal + # PRs as they'll be run by the push to the branch. + # + # The main trick is described here: + # https://github.com/Dart-Code/Dart-Code/pull/2375 + if: github.event_name == 'push' || + github.event.pull_request.head.repo.full_name != github.repository + + runs-on: ubuntu-20.04 + + strategy: + fail-fast: false + matrix: + tarantool: + - '1.10' + - '2.8' + - '2.9' + - '2.x-latest' + python: + - '2.7' + - '3.5' + - '3.6' + - '3.7' + - '3.8' + - '3.9' + - '3.10' + msgpack-deps: + # latest msgpack will be installed as a part of requirements.txt + - '' + + # Adding too many elements to three-dimentional matrix results in + # too many test cases. It causes GitHub webpages to fail with + # "This page is taking too long to load." error. Thus we use + # pairwise testing. + include: + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack-python==0.4.0' + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack==0.5.0' + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack==0.6.2' + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack==1.0.0' + + steps: + - name: Clone the connector + uses: actions/checkout@v2 + + - name: Install tarantool ${{ matrix.tarantool }} + if: matrix.tarantool != '2.x-latest' + uses: tarantool/setup-tarantool@v1 + with: + tarantool-version: ${{ matrix.tarantool }} + + - name: Install latest tarantool 2.x + if: matrix.tarantool == '2.x-latest' + run: | + curl -L https://tarantool.io/pre-release/2/installer.sh | sudo bash + sudo apt install -y tarantool tarantool-dev + + - name: Setup Python for tests + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install msgpack python package + if: matrix.msgpack-deps != '' + run: pip install ${{ matrix.msgpack-deps }} + + - name: Install connector requirements + # msgpack package is a replacement for deprecated msgpack-python. + # To test compatibility with msgpack-python we must ignore + # requirements.txt install, since it will install newest msgpack. + # Beware that if any new dependency will be added to requirements.txt, + # this step will be invalid. + if: matrix.msgpack-deps != 'msgpack-python==0.4.0' + run: pip install -r requirements.txt + + - name: Install test requirements + run: pip install -r requirements-test.txt + + - name: Run tests + run: make test + + run_tests_windows: + # We want to run on external PRs, but not on our own internal + # PRs as they'll be run by the push to the branch. + # + # The main trick is described here: + # https://github.com/Dart-Code/Dart-Code/pull/2375 + if: github.event_name == 'push' || + github.event.pull_request.head.repo.full_name != github.repository + + runs-on: windows-2022 + + strategy: + fail-fast: false + matrix: + tarantool: + - '1.10' + - '2.8' + - '2.9' + - '2.x-latest' + python: + - '2.7' + - '3.4' + - '3.5' + - '3.6' + - '3.7' + - '3.8' + - '3.9' + - '3.10' + msgpack-deps: + # latest msgpack will be installed as a part of requirements.txt + - '' + + # Adding too many elements in three-dimentional matrix results in + # too many test cases, which causes GitHub webpages to fail with + # "This page is taking too long to load" error, so we use pairwise + # testing. + include: + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack-python==0.4.0' + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack==0.5.0' + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack==0.6.2' + - tarantool: '2.8' + python: '3.10' + msgpack-deps: 'msgpack==1.0.0' + + steps: + - name: Clone the connector + uses: actions/checkout@v2 + + - name: Setup Python for tests + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python }} + + - name: Install msgpack python package + if: matrix.msgpack-deps != '' + run: pip install ${{ matrix.msgpack-deps }} + + - name: Install connector requirements + # msgpack package is a replacement for deprecated msgpack-python. + # To test compatibility with msgpack-python we must ignore + # requirements.txt install, since it will install newest msgpack. + # Beware that if any new dependency will be added to requirements.txt, + # this step will be invalid. + if: matrix.msgpack-deps != 'msgpack-python==0.4.0' + run: pip install -r requirements.txt + + - name: Install test requirements + run: pip install -r requirements-test.txt + + - name: Setup WSL for tarantool + uses: Vampire/setup-wsl@v1 + with: + distribution: Ubuntu-20.04 + + - name: Install tarantool ${{ matrix.tarantool }} for WSL + if: matrix.tarantool != '2.x-latest' + shell: wsl-bash_Ubuntu-20.04 {0} + run: | + curl -L https://tarantool.io/installer.sh | VER=${{ matrix.tarantool }} bash -s -- --type "release" + sudo apt install -y tarantool tarantool-dev + + - name: Install latest tarantool 2.x for WSL + if: matrix.tarantool == '2.x-latest' + shell: wsl-bash_Ubuntu-20.04 {0} + run: | + curl -L https://tarantool.io/pre-release/2/installer.sh | sudo bash + sudo apt install -y tarantool tarantool-dev + + - name: Setup test tarantool instance + shell: wsl-bash_Ubuntu-20.04 {0} + run: | + rm -f ./tarantool.pid ./tarantool.log + TNT_PID=$(tarantool ./test/suites/lib/tarantool_python_ci.lua > tarantool.log 2>&1 & echo $!) + touch tarantool.pid + echo $TNT_PID > ./tarantool.pid + + - name: Run tests + env: + REMOTE_TARANTOOL_HOST: localhost + REMOTE_TARANTOOL_CONSOLE_PORT: 3302 + run: make test + + - name: Stop test tarantool instance + if: ${{ always() }} + shell: wsl-bash_Ubuntu-20.04 {0} + run: | + cat tarantool.log || true + kill $(cat tarantool.pid) || true diff --git a/README.rst b/README.rst index 3b5af060..aa0167d5 100644 --- a/README.rst +++ b/README.rst @@ -11,11 +11,8 @@ This package is a pure-python client library for `Tarantool`_. .. _`GitHub`: https://github.com/tarantool/tarantool-python .. _`Issue tracker`: https://github.com/tarantool/tarantool-python/issues -.. image:: https://travis-ci.org/tarantool/tarantool-python.svg?branch=master - :target: https://travis-ci.org/tarantool/tarantool-python - -.. image:: https://ci.appveyor.com/api/projects/status/github/tarantool/tarantool-python?branch=master - :target: https://ci.appveyor.com/project/tarantool/tarantool-python +.. image:: https://github.com/tarantool/tarantool-python/actions/workflows/testing.yml/badge.svg?branch=master + :target: https://github.com/tarantool/tarantool-python/actions/workflows/testing.yml Download and Install -------------------- diff --git a/test/suites/lib/tarantool_python_ci.lua b/test/suites/lib/tarantool_python_ci.lua index 2305c0d7..0b21b86a 100644 --- a/test/suites/lib/tarantool_python_ci.lua +++ b/test/suites/lib/tarantool_python_ci.lua @@ -343,5 +343,5 @@ end -- }}} -clean() +-- clean() init() From 93fefbe8de4f2776804b621cf1c1871907130618 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Tue, 22 Mar 2022 15:36:00 +0300 Subject: [PATCH 2/3] test: skip flaky ping test on Windows Ping test started to fail (in ~70% cases) after Windows CI migration from Appveyor to GitHub Actions. As a part of this migration, the test tarantool instance was changed from an instance started on a remote Linux server to an instance started on the same Windows server under WSL. Thus, request time has shortened and it supposedly caused the ping test to fail. Python documentation [1] declares that ...though the time is always returned as a floating point number, not all systems provide time with a better precision than 1 second... Particularly, StackOverflow answer [2] argues that For Linux and Mac precision is +- 1 microsecond or 0.001 milliseconds. Python on Windows uses +- 16 milliseconds precision due to clock implementation problems due to process interrupts. based on Windows documentation [3]. Assuming that ping requests between the same server services can easily be under 1 ms, it caused the test to fail. This patch adds skip for the flaky test, refer to #214 for further development. 1. https://docs.python.org/3/library/time.html#time.time 2. https://stackoverflow.com/a/1938096/11646599 3. https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/high-resolution-timers Follows up #182, part of #214 --- test/suites/test_dml.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/suites/test_dml.py b/test/suites/test_dml.py index e3922a10..6463778f 100644 --- a/test/suites/test_dml.py +++ b/test/suites/test_dml.py @@ -146,6 +146,9 @@ def test_05_ping(self): # Simple ping test # * No exceptions are raised # * Ping time > 0 + if sys.platform.startswith("win"): + self.skipTest("Windows clock precision causes test to fail sometimes, see #214") + self.assertTrue(self.con.ping() > 0) self.assertEqual(self.con.ping(notime=True), "Success") From 155b1d7060c25b9ab474fb729c7c98fd6da70ba6 Mon Sep 17 00:00:00 2001 From: Georgy Moiseev Date: Tue, 15 Mar 2022 17:00:48 +0300 Subject: [PATCH 3/3] types: support working with binary for Python 3 Before this patch, both bytes and str were encoded as mp_str. It was possible to work with utf and non-utf strings, but not with varbinary [1] (mp_bin). This patch adds varbinary support for Python 3 by default. Python 2 connector behavior remains the same. For encoding="utf-8" (default), the following behavior is expected now: (Python 3 -> Tarantool -> Python 3) bytes -> mp_bin (varbinary) -> bytes str -> mp_str (string) -> str For encoding=None, the following behavior is expected now: (Python 3 -> Tarantool -> Python 3) bytes -> mp_str (string) -> bytes This patch changes current behavior for Python 3. Now bytes objects encoded to varbinary by default. bytes objects are also supported as keys. This patch does not add new restrictions (like "do not permit to use str in encoding=None mode because result may be confusing") to preserve current behavior (for example, using space name as str in schema get_space). 1. https://github.com/tarantool/tarantool/issues/4201 Closes #105 --- tarantool/request.py | 15 ++++-- tarantool/utils.py | 14 ++++-- test/suites/lib/skip.py | 82 ++++++++++++++++++++++++------- test/suites/test_dml.py | 104 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 189 insertions(+), 26 deletions(-) diff --git a/tarantool/request.py b/tarantool/request.py index d1a5a829..241a961f 100644 --- a/tarantool/request.py +++ b/tarantool/request.py @@ -4,6 +4,7 @@ Request types definitions ''' +import sys import collections import msgpack import hashlib @@ -84,8 +85,13 @@ def __init__(self, conn): # The option controls whether to pack binary (non-unicode) # string values as mp_bin or as mp_str. # - # The default behaviour of the connector is to pack both - # bytes and Unicode strings as mp_str. + # The default behaviour of the Python 2 connector is to pack + # both bytes and Unicode strings as mp_str. + # + # The default behaviour of the Python 3 connector (since + # default encoding is "utf-8") is to pack bytes as mp_bin + # and Unicode strings as mp_str. encoding=None mode must + # be used to work with non-utf strings. # # msgpack-0.5.0 (and only this version) warns when the # option is unset: @@ -98,7 +104,10 @@ def __init__(self, conn): # just always set it for all msgpack versions to get rid # of the warning on msgpack-0.5.0 and to keep our # behaviour on msgpack-1.0.0. - packer_kwargs['use_bin_type'] = False + if conn.encoding is None or sys.version_info.major == 2: + packer_kwargs['use_bin_type'] = False + else: + packer_kwargs['use_bin_type'] = True self.packer = msgpack.Packer(**packer_kwargs) diff --git a/tarantool/utils.py b/tarantool/utils.py index c365e7cb..7909e12a 100644 --- a/tarantool/utils.py +++ b/tarantool/utils.py @@ -6,7 +6,10 @@ if sys.version_info.major == 2: string_types = (basestring, ) integer_types = (int, long) + supported_types = integer_types + string_types + (float,) + ENCODING_DEFAULT = None + if sys.version_info.minor < 6: binary_types = (str, ) else: @@ -17,10 +20,13 @@ def strxor(rhs, lhs): return "".join(chr(ord(x) ^ ord(y)) for x, y in zip(rhs, lhs)) elif sys.version_info.major == 3: - binary_types = (bytes, ) - string_types = (str, ) - integer_types = (int, ) + binary_types = (bytes, ) + string_types = (str, ) + integer_types = (int, ) + supported_types = integer_types + string_types + binary_types + (float,) + ENCODING_DEFAULT = "utf-8" + from base64 import decodebytes as base64_decode def strxor(rhs, lhs): @@ -43,7 +49,7 @@ def check_key(*args, **kwargs): elif args[0] is None and kwargs['select']: return [] for key in args: - assert isinstance(key, integer_types + string_types + (float,)) + assert isinstance(key, supported_types) return list(args) diff --git a/test/suites/lib/skip.py b/test/suites/lib/skip.py index 495a716c..f8f5a475 100644 --- a/test/suites/lib/skip.py +++ b/test/suites/lib/skip.py @@ -1,20 +1,15 @@ import functools import pkg_resources import re +import sys -SQL_SUPPORT_TNT_VERSION = '2.0.0' - -def skip_or_run_sql_test(func): - """Decorator to skip or run SQL-related tests depending on the tarantool +def skip_or_run_test_tarantool(func, REQUIRED_TNT_VERSION, msg): + """Decorator to skip or run tests depending on the tarantool version. - Tarantool supports SQL-related stuff only since 2.0.0 version. So this - decorator should wrap every SQL-related test to skip it if the tarantool - version < 2.0.0 is used for testing. - - Also, it can be used with the 'setUp' method for skipping the whole test - suite. + Also, it can be used with the 'setUp' method for skipping + the whole test suite. """ @functools.wraps(func) @@ -28,16 +23,69 @@ def wrapper(self, *args, **kwargs): ).group() tnt_version = pkg_resources.parse_version(self.tnt_version) - sql_support_tnt_version = pkg_resources.parse_version( - SQL_SUPPORT_TNT_VERSION - ) + support_version = pkg_resources.parse_version(REQUIRED_TNT_VERSION) - if tnt_version < sql_support_tnt_version: - self.skipTest( - 'Tarantool %s does not support SQL' % self.tnt_version - ) + if tnt_version < support_version: + self.skipTest('Tarantool %s %s' % (self.tnt_version, msg)) if func.__name__ != 'setUp': func(self, *args, **kwargs) return wrapper + + +def skip_or_run_test_python_major(func, REQUIRED_PYTHON_MAJOR, msg): + """Decorator to skip or run tests depending on the Python major + version. + + Also, it can be used with the 'setUp' method for skipping + the whole test suite. + """ + + @functools.wraps(func) + def wrapper(self, *args, **kwargs): + if func.__name__ == 'setUp': + func(self, *args, **kwargs) + + major = sys.version_info.major + if major != REQUIRED_PYTHON_MAJOR: + self.skipTest('Python %s connector %s' % (major, msg)) + + if func.__name__ != 'setUp': + func(self, *args, **kwargs) + + return wrapper + + +def skip_or_run_sql_test(func): + """Decorator to skip or run SQL-related tests depending on the + tarantool version. + + Tarantool supports SQL-related stuff only since 2.0.0 version. + So this decorator should wrap every SQL-related test to skip it if + the tarantool version < 2.0.0 is used for testing. + """ + + return skip_or_run_test_tarantool(func, '2.0.0', 'does not support SQL') + + +def skip_or_run_varbinary_test(func): + """Decorator to skip or run VARBINARY-related tests depending on + the tarantool version. + + Tarantool supports VARBINARY type only since 2.2.1 version. + See https://github.com/tarantool/tarantool/issues/4201 + """ + + return skip_or_run_test_tarantool(func, '2.2.1', + 'does not support VARBINARY type') + + +def skip_or_run_mp_bin_test(func): + """Decorator to skip or run mp_bin-related tests depending on + the Python version. + + Python 2 connector do not support mp_bin. + """ + + return skip_or_run_test_python_major(func, 3, 'does not support mp_bin') \ No newline at end of file diff --git a/test/suites/test_dml.py b/test/suites/test_dml.py index 6463778f..a11c9bf5 100644 --- a/test/suites/test_dml.py +++ b/test/suites/test_dml.py @@ -6,6 +6,7 @@ import unittest import tarantool +from .lib.skip import skip_or_run_mp_bin_test, skip_or_run_varbinary_test from .lib.tarantool_server import TarantoolServer class TestSuite_Request(unittest.TestCase): @@ -16,7 +17,13 @@ def setUpClass(self): self.srv = TarantoolServer() self.srv.script = 'test/suites/box.lua' self.srv.start() - self.con = tarantool.Connection(self.srv.host, self.srv.args['primary']) + + args = [self.srv.host, self.srv.args['primary']] + self.con = tarantool.Connection(*args) + self.con_encoding_utf8 = tarantool.Connection(*args, encoding='utf-8') + self.con_encoding_none = tarantool.Connection(*args, encoding=None) + self.conns = [self.con, self.con_encoding_utf8, self.con_encoding_none] + self.adm = self.srv.admin self.space_created = self.adm("box.schema.create_space('space_1')") self.adm(""" @@ -31,6 +38,7 @@ def setUpClass(self): parts = {2, 'num', 3, 'str'}, unique = false}) """.replace('\n', ' ')) + self.space_created = self.adm("box.schema.create_space('space_2')") self.adm(""" box.space['space_2']:create_index('primary', { @@ -38,10 +46,56 @@ def setUpClass(self): parts = {1, 'num'}, unique = true}) """.replace('\n', ' ')) + + self.adm("box.schema.create_space('space_str')") + self.adm(""" + box.space['space_str']:create_index('primary', { + type = 'tree', + parts = {1, 'str'}, + unique = true}) + """.replace('\n', ' ')) + + self.adm("box.schema.create_space('space_varbin')") + self.adm(""" + box.space['space_varbin']:create_index('primary', { + type = 'tree', + parts = {1, 'varbinary'}, + unique = true}) + """.replace('\n', ' ')) + self.adm(""" + buffer = require('buffer') + ffi = require('ffi') + + function encode_bin(bytes) + local tmpbuf = buffer.ibuf() + local p = tmpbuf:alloc(3 + #bytes) + p[0] = 0x91 + p[1] = 0xC4 + p[2] = #bytes + for i, c in pairs(bytes) do + p[i + 3 - 1] = c + end + return tmpbuf + end + + function bintuple_insert(space, bytes) + local tmpbuf = encode_bin(bytes) + ffi.cdef[[ + int box_insert(uint32_t space_id, const char *tuple, const char *tuple_end, box_tuple_t **result); + ]] + ffi.C.box_insert(space.id, tmpbuf.rpos, tmpbuf.wpos, nil) + end + """) self.adm("json = require('json')") self.adm("fiber = require('fiber')") self.adm("uuid = require('uuid')") + def assertNotRaises(self, func, *args, **kwargs): + try: + func(*args, **kwargs) + except Exception as e: + self.fail('Function raised Exception: %s' % repr(e)) + def setUp(self): # prevent a remote tarantool from clean our session if self.srv.is_started(): @@ -54,7 +108,8 @@ def test_00_00_authenticate(self): self.assertIsNone(self.srv.admin(""" box.schema.user.grant('test', 'execute,read,write', 'universe') """)) - self.assertEqual(self.con.authenticate('test', 'test')._data, None) + for con in self.conns: + self.assertEqual(con.authenticate('test', 'test')._data, None) def test_00_01_space_created(self): # Check that space is created in setUpClass @@ -302,6 +357,51 @@ def test_12_update_fields(self): [[2, 'help', 7]] ) + def test_13_00_string_insert_encoding_utf8_behavior(self): + self.assertNotRaises( + self.con_encoding_utf8.insert, + 'space_str', [ 'test_13_00' ]) + + def test_13_01_string_select_encoding_utf8_behavior(self): + self.adm(r"box.space['space_str']:insert{'test_13_01'}") + + strdata = 'test_13_01' + resp = self.con_encoding_utf8.select('space_str', [strdata]) + self.assertEquals(resp[0][0], strdata) + + @skip_or_run_mp_bin_test + @skip_or_run_varbinary_test + def test_13_02_varbinary_insert_encoding_utf8_behavior(self): + self.assertNotRaises( + self.con_encoding_utf8.insert, + 'space_varbin', [ b'test_13_02' ]) + + @skip_or_run_mp_bin_test + @skip_or_run_varbinary_test + def test_13_03_varbinary_select_encoding_utf8_behavior(self): + self.adm(r""" + bintuple_insert( + box.space['space_varbin'], + {0xDE, 0xAD, 0xBE, 0xAF, 0x13, 0x03}) + """) + + bindata = bytes(bytearray.fromhex('DEADBEAF1303')) + resp = self.con_encoding_utf8.select('space_varbin', [bindata]) + self.assertEquals(resp[0][0], bindata) + + def test_14_00_string_insert_encoding_none_behavior(self): + self.assertNotRaises( + self.con_encoding_none.insert, + 'space_str', + [ bytes(bytearray.fromhex('DEADBEAF1400')) ]) + + def test_14_01_string_select_encoding_none_behavior(self): + self.adm(r"box.space['space_str']:insert{'\xDE\xAD\xBE\xAF\x14\x01'}") + + bindata = bytes(bytearray.fromhex('DEADBEAF1401')) + resp = self.con_encoding_none.select('space_str', [bindata]) + self.assertEquals(resp[0][0], bindata) + @classmethod def tearDownClass(self): self.con.close()