From 02e606608ed979bd689eb46d15e31cc2e08fba1b Mon Sep 17 00:00:00 2001 From: Bernie Hackett Date: Tue, 4 Apr 2017 13:08:21 -0700 Subject: [PATCH] PYTHON-1213 - Make decimal128 work with cdecimal --- .evergreen/config.yml | 27 +++++++++++++ .evergreen/run-cdecimal-tests.sh | 12 ++++++ bson/decimal128.py | 13 +++++-- cdecimal_test.py | 66 ++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 .evergreen/run-cdecimal-tests.sh create mode 100644 cdecimal_test.py diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 6e8dc4a5b..033f1534a 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -345,6 +345,15 @@ functions: ${PREPARE_SHELL} PYTHON_BINARY=${PYTHON_BINARY} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} sh ${PROJECT_DIRECTORY}/.evergreen/run-mockupdb-tests.sh + "run cdecimal tests": + - command: shell.exec + type: test + params: + working_dir: "src" + script: | + ${PREPARE_SHELL} + PYTHON_BINARY=${PYTHON_BINARY} sh ${PROJECT_DIRECTORY}/.evergreen/run-cdecimal-tests.sh + "run doctests": - command: shell.exec type: test @@ -681,6 +690,15 @@ tasks: TOPOLOGY: "replica_set" - func: "run mod_wsgi tests" + - name: "cdecimal" + tags: ["cdecimal"] + commands: + - func: "bootstrap mongo-orchestration" + vars: + VERSION: "latest" + TOPOLOGY: "server" + - func: "run cdecimal tests" + # }}} @@ -1316,6 +1334,15 @@ buildvariants: tasks: - name: "doctests" +- matrix_name: "cdecimal" + matrix_spec: {python-version: ["2.6", "2.7"]} + display_name: "cdecimal ${python-version}" + batchtime: 10080 # 7 days + run_on: + - ubuntu1604-test + tasks: + - name: "cdecimal" + # Platform notes # i386 builds of OpenSSL or Cyrus SASL are not available # Ubuntu14.04 only supports 2.6+ with SSL diff --git a/.evergreen/run-cdecimal-tests.sh b/.evergreen/run-cdecimal-tests.sh new file mode 100644 index 000000000..1a341a502 --- /dev/null +++ b/.evergreen/run-cdecimal-tests.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -o xtrace +set -o errexit + +virtualenv -p ${PYTHON_BINARY} cdecimaltest +trap "deactivate; rm -rf cdecimaltest" EXIT HUP +. cdecimaltest/bin/activate +# No cdecimal tarballs on pypi. +pip install http://www.bytereef.org/software/mpdecimal/releases/cdecimal-2.3.tar.gz +python -c 'import sys; print(sys.version)' +python cdecimal_test.py diff --git a/bson/decimal128.py b/bson/decimal128.py index 8a0b3cf3b..da9655370 100644 --- a/bson/decimal128.py +++ b/bson/decimal128.py @@ -84,9 +84,12 @@ _CTX_OPTIONS = { decimal.Inexact] } -if _PY3: +try: + # Python >= 3.3, cdecimal + decimal.Context(clamp=1) # pylint: disable=unexpected-keyword-arg _CTX_OPTIONS['clamp'] = 1 -else: +except TypeError: + # Python < 3.3 _CTX_OPTIONS['_clamp'] = 1 _DEC128_CTX = decimal.Context(**_CTX_OPTIONS.copy()) @@ -273,7 +276,7 @@ class Decimal128(object): elif (high & _NAN) == _NAN: return decimal.Decimal((sign, (), 'n')) elif (high & _INF) == _INF: - return decimal.Decimal((sign, (0,), 'F')) + return decimal.Decimal((sign, (), 'F')) if (high & _EXPONENT_MASK) == _EXPONENT_MASK: exponent = ((high & 0x1fffe00000000000) >> 47) - _EXPONENT_BIAS @@ -296,7 +299,9 @@ class Decimal128(object): arr[0] = (high & mask) >> 48 # Have to convert bytearray to bytes for python 2.6. - digits = [int(digit) for digit in str(_from_bytes(bytes(arr), 'big'))] + # cdecimal only accepts a tuple for digits. + digits = tuple( + int(digit) for digit in str(_from_bytes(bytes(arr), 'big'))) with decimal.localcontext(_DEC128_CTX) as ctx: return ctx.create_decimal((sign, digits, exponent)) diff --git a/cdecimal_test.py b/cdecimal_test.py new file mode 100644 index 000000000..88262ffd9 --- /dev/null +++ b/cdecimal_test.py @@ -0,0 +1,66 @@ +# Copyright 2017 MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test PyMongo with cdecimal monkey-patched over stdlib decimal.""" + +import getopt +import sys + +try: + import cdecimal + _HAVE_CDECIMAL = True +except ImportError: + _HAVE_CDECIMAL = False + + +def run(args): + """Run tests with cdecimal monkey-patched over stdlib decimal.""" + # Monkey-patch. + sys.modules['decimal'] = cdecimal + + # Run the tests. + sys.argv[:] = ['setup.py', 'test'] + list(args) + import setup + + +def main(): + """Parse options and run tests.""" + usage = """python %s + +Test PyMongo with cdecimal monkey-patched over decimal.""" % (sys.argv[0],) + + try: + opts, args = getopt.getopt( + sys.argv[1:], "h", ["help"]) + except getopt.GetoptError as err: + print(str(err)) + print(usage) + sys.exit(2) + + for option_name, _ in opts: + if option_name in ("-h", "--help"): + print(usage) + sys.exit() + else: + assert False, "unhandled option" + + if not _HAVE_CDECIMAL: + print("The cdecimal package is not installed.") + sys.exit(1) + + run(args) # Command line args to setup.py, like what test to run. + + +if __name__ == '__main__': + main()