From 55f44d472eb96a42320d97e92bcf990a2c31dd9e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 23 Oct 2023 07:44:57 +0300 Subject: [PATCH 1/8] Improve test coverage for gmpy2_hash.c (bulk tests) --- test/test_mpc.py | 17 ++++++++++++++--- test/test_mpfr.py | 43 ++++++++++++++++++++++++++----------------- test/test_mpq.py | 23 ++++++++++++++++------- test/test_mpz.py | 24 +++++++++++++++--------- 4 files changed, 71 insertions(+), 36 deletions(-) diff --git a/test/test_mpc.py b/test/test_mpc.py index ea6cd691..1f1fdf51 100644 --- a/test/test_mpc.py +++ b/test/test_mpc.py @@ -1,9 +1,11 @@ import pytest +from hypothesis import example, given, settings +from hypothesis.strategies import complex_numbers +from supportclasses import a, b, c, cx, d, q, r, z import gmpy2 -from gmpy2 import (mpc, cmp, cmp_abs, nan, random_state, mpc_random, - to_binary, from_binary, get_context, is_nan, mpq, mpfr) -from supportclasses import a, b, c, d, cx, z, q, r +from gmpy2 import (cmp, cmp_abs, from_binary, get_context, is_nan, mpc, + mpc_random, mpfr, mpq, nan, random_state, to_binary) def test_mpc_cmp(): @@ -186,3 +188,12 @@ def test_mpc_divmod(): pytest.raises(TypeError, lambda: ctx.divmod(mpc(1,2),mpc(3,4))) pytest.raises(TypeError, lambda: divmod(mpc(1,2), mpc(1,2))) pytest.raises(TypeError, lambda: ctx.divmod(mpc(1,2),mpc(3,4))) + + +@settings(max_examples=1000) +@given(complex_numbers(allow_nan=False)) +@example(complex()) +@example(complex(-1)) +@example(complex(-2)) +def test_mpc_hash(c): + assert hash(mpc(c)) == hash(c) diff --git a/test/test_mpfr.py b/test/test_mpfr.py index 6fc07434..56caf518 100644 --- a/test/test_mpfr.py +++ b/test/test_mpfr.py @@ -1,15 +1,17 @@ +import math +import sys from decimal import Decimal from fractions import Fraction import pytest -from hypothesis import given, example, settings +from hypothesis import example, given, settings from hypothesis.strategies import floats +from supportclasses import a, b, c, d, q, r, z import gmpy2 -from gmpy2 import (gamma_inc, mpfr, cmp, cmp_abs, zero, nan, mpz, mpq, - to_binary, from_binary, is_nan, random_state, - mpfr_grandom, mpfr_nrandom, mpc) -from supportclasses import a, b, c, d, q, r, z +from gmpy2 import (cmp, cmp_abs, from_binary, gamma_inc, is_nan, mpc, mpfr, + mpfr_grandom, mpfr_nrandom, mpq, mpz, nan, random_state, + to_binary, zero) def test_mpfr_gamma_inc(): @@ -60,18 +62,25 @@ def test_mpfr_conversion(): pytest.raises(TypeError, lambda: mpfr(d)) -def test_mpfr_hash(): - assert hash(mpfr('123.456')) == hash(float('123.456')) - assert hash(mpfr('123.5')) == hash(float('123.5')) - assert hash(mpfr('0')) == hash(float('0')) - assert hash(mpfr('1')) == hash(float('1')) - assert hash(mpfr('2')) == hash(float('2')) - assert hash(mpfr('-1')) == hash(float('-1')) - assert hash(mpfr('Inf')) == hash(float('Inf')) - assert hash(mpfr('-Inf')) == hash(float('-Inf')) - assert hash(mpfr('-0')) == hash(float('-0')) - assert hash(mpfr('123.456')) != hash(Decimal('123.456')) - assert hash(mpfr('123.5')) == hash(Decimal('123.5')) +@settings(max_examples=1000) +@given(floats()) +@example(0.0) +@example(1.0) +@example(2.0) +@example(-1.0) +@example(123.456) +@example(123.5) +@example(float('inf')) +@example(-float('inf')) +@example(float('nan')) +def test_mpfr_hash(x): + if math.isnan(x): + if sys.version_info < (3, 10): + assert hash(mpfr(x)) == hash(x) == sys.hash_info.nan + else: + assert hash(mpfr(x)) != hash(x) + else: + assert hash(mpfr(x)) == hash(x) @given(floats()) diff --git a/test/test_mpq.py b/test/test_mpq.py index 2c811e7c..08a74c2a 100644 --- a/test/test_mpq.py +++ b/test/test_mpq.py @@ -1,16 +1,17 @@ -import numbers import math +import numbers import pickle +import sys from decimal import Decimal from fractions import Fraction import pytest -from hypothesis import given, example, settings -from hypothesis.strategies import integers +from hypothesis import example, given, settings +from hypothesis.strategies import fractions, integers +from supportclasses import a, b, c, d, q, z import gmpy2 -from gmpy2 import mpq, mpz, cmp, cmp_abs, from_binary, to_binary, mpc, mpfr -from supportclasses import a, b, c, d, q, z +from gmpy2 import cmp, cmp_abs, from_binary, mpc, mpfr, mpq, mpz, to_binary def test_mpz_constructor(): @@ -92,8 +93,16 @@ def test_mpq_to_from_binary(p, q): assert x == from_binary(to_binary(x)) -def test_mpq_hash(): - hash(mpq(123456,1000)) == hash(Decimal('123.456')) +@settings(max_examples=1000) +@given(fractions()) +@example(Fraction(0)) +@example(Fraction(-1)) +@example(Fraction(-2)) +@example(Fraction(15432, 125)) +@example(Fraction(1, sys.hash_info.modulus)) +@example(Fraction(-1, sys.hash_info.modulus)) +def test_mpq_hash(q): + assert hash(mpq(q)) == hash(q) def test_mpq_digits(): diff --git a/test/test_mpz.py b/test/test_mpz.py index 06ede8a0..8a1e42a8 100644 --- a/test/test_mpz.py +++ b/test/test_mpz.py @@ -1,17 +1,16 @@ import math import numbers import pickle -from decimal import Decimal -from hypothesis import assume, given, example, settings +from hypothesis import assume, example, given, settings from hypothesis.strategies import booleans, integers, sampled_from -from pytest import raises, mark +from pytest import mark, raises +from supportclasses import a, b, c, d, q, z import gmpy2 -from gmpy2 import (mpz, pack, unpack, cmp, cmp_abs, to_binary, from_binary, - random_state, mpz_random, mpz_urandomb, mpz_rrandomb, - mp_version, mpq, mpfr, mpc) -from supportclasses import a, b, c, d, z, q +from gmpy2 import (cmp, cmp_abs, from_binary, mp_version, mpc, mpfr, mpq, mpz, + mpz_random, mpz_rrandomb, mpz_urandomb, pack, random_state, + to_binary, unpack) def test_mpz_to_bytes_interface(): @@ -260,8 +259,15 @@ def test_mpz_to_from_binary(n): assert x == from_binary(to_binary(x)) -def test_mpz_hash(): - assert hash(mpz(123)) == hash(Decimal(123)) +@settings(max_examples=1000) +@given(integers()) +@example(0) +@example(1) +@example(-1) +@example(-2) +@example(123) +def test_mpz_hash(n): + assert hash(mpz(n)) == hash(n) def test_mpz_ceil(): From 446cbbf2eefb1e70998cfdf5f1002f34c463f3f2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 23 Oct 2023 09:20:36 +0300 Subject: [PATCH 2/8] Remove inaccessible code (undefined _PyHASH_MODULUS) --- src/gmpy2_hash.c | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/src/gmpy2_hash.c b/src/gmpy2_hash.c index eb014dd2..660a5f3b 100644 --- a/src/gmpy2_hash.c +++ b/src/gmpy2_hash.c @@ -27,7 +27,6 @@ static Py_hash_t GMPy_MPZ_Hash_Slot(MPZ_Object *self) { -#ifdef _PyHASH_MODULUS Py_hash_t hash; if (self->hash_cache != -1) { @@ -42,28 +41,11 @@ GMPy_MPZ_Hash_Slot(MPZ_Object *self) hash = -2; } return (self->hash_cache = hash); -#else - unsigned long x; - - if (self->hash_cache != -1) { - return self->hash_cache; - } - - x = (unsigned long)mpn_mod_1(self->z->_mp_d, mpz_size(self->z), ULONG_MAX); - if (mpz_sgn(self->z) < 0) { - x = x * -1; - } - if (x == (unsigned long)-1) { - x = (unsigned long)-2; - } - return (self->hash_cache = (long)x); -#endif } static Py_hash_t GMPy_MPQ_Hash_Slot(MPQ_Object *self) { -#ifdef _PyHASH_MODULUS Py_hash_t hash = 0; mpz_t temp, temp1, mask; @@ -108,27 +90,11 @@ GMPy_MPQ_Hash_Slot(MPQ_Object *self) mpz_clear(mask); self->hash_cache = hash; return hash; -#else - PyObject *temp; - - if (self->hash_cache != -1) { - return self->hash_cache; - } - - if (!(temp = GMPy_PyFloat_From_MPQ(self, NULL))) { - SYSTEM_ERROR("Could not convert 'mpq' to float."); - return -1; - } - self->hash_cache = PyObject_Hash(temp); - Py_DECREF(temp); - return self->hash_cache; -#endif } static Py_hash_t _mpfr_hash(mpfr_t f) { -#ifdef _PyHASH_MODULUS Py_uhash_t hash = 0; Py_ssize_t exp; size_t msize; @@ -146,7 +112,6 @@ _mpfr_hash(mpfr_t f) } else { #if PY_VERSION_HEX >= 0x030A00A0 - // Python 3.10 return _Py_HashPointer(f); #else return _PyHASH_NAN; @@ -180,14 +145,6 @@ _mpfr_hash(mpfr_t f) hash = (Py_uhash_t)(-2); } return (Py_hash_t)hash; -#else - double temp; - CTXT_Object *context = NULL; - - CHECK_CONTEXT(context); - temp = mpfr_get_d(f, GET_MPFR_ROUND(context)); - return _Py_HashDouble(temp); -#endif } static Py_hash_t @@ -223,4 +180,3 @@ GMPy_MPC_Hash_Slot(MPC_Object *self) self->hash_cache = combined; return (Py_hash_t)combined; } - From f5acf05dc24d4b286de81b9ca7a37974e9487afe Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 23 Oct 2023 10:31:21 +0300 Subject: [PATCH 3/8] Run coverage tests also on CPython 3.8 --- .github/workflows/pip_install_gmpy2.yml | 31 ++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pip_install_gmpy2.yml b/.github/workflows/pip_install_gmpy2.yml index 194a34e1..4b7bc5db 100644 --- a/.github/workflows/pip_install_gmpy2.yml +++ b/.github/workflows/pip_install_gmpy2.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.11] + python-version: [3.8, 3.11] os: [ubuntu-22.04] runs-on: ${{ matrix.os }} steps: @@ -34,19 +34,27 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - run: | + - name: Install Libs + run: | sudo apt-get update sudo apt-get install libmpc-dev texlive texlive-latex-extra latexmk lcov - - run: pip install --upgrade pip - - run: pip --verbose install --editable .[docs,tests] - - run: python test/runtests.py - - run: PYTHONPATH=`pwd`/gmpy2 python test_cython/runtests.py - - run: sphinx-build --color -W --keep-going -b doctest docs build/sphinx/doctest - - run: sphinx-build --color -W --keep-going -b html docs build/sphinx/html - - run: sphinx-build --color -W --keep-going -b latex docs build/sphinx/latex - - run: make -C build/sphinx/latex all-pdf + - name: Install dependencies + run: | + pip install --upgrade pip + pip --verbose install --editable .[docs,tests] + - name: Tests + run: | + python test/runtests.py + PYTHONPATH=`pwd`/gmpy2 python test_cython/runtests.py + - name: Building docs + if: matrix.python-version == 3.11 + run: | + sphinx-build --color -W --keep-going -b doctest docs build/sphinx/doctest + sphinx-build --color -W --keep-going -b html docs build/sphinx/html + sphinx-build --color -W --keep-going -b latex docs build/sphinx/latex + make -C build/sphinx/latex all-pdf + # XXX: why this doesn't work with pip install --editable above? - name: Run coverage tests - # XXX: why this doesn't work with pip install --editable above? run: | pip uninstall -y gmpy2 python setup.py clean @@ -62,6 +70,7 @@ jobs: gcov_args: --no-external - name: Archive build artifacts uses: actions/upload-artifact@v3 + if: matrix.python-version == 3.11 with: path: | build/sphinx/doctest/ From 8f212ced5516fe0c91f2dfb5b886ede01fb59571 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 23 Oct 2023 11:57:23 +0300 Subject: [PATCH 4/8] Add lcov pragmas in src/gmpy2_mpmath.c --- src/gmpy2_mpmath.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/gmpy2_mpmath.c b/src/gmpy2_mpmath.c index 59c2b464..d8f2823f 100644 --- a/src/gmpy2_mpmath.c +++ b/src/gmpy2_mpmath.c @@ -33,24 +33,30 @@ mpmath_build_mpf(long sign, MPZ_Object *man, PyObject *exp, mp_bitcnt_t bc) PyObject *tup, *tsign, *tbc; if (!(tup = PyTuple_New(4))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)man); Py_DECREF(exp); return NULL; + /* LCOV_EXCL_STOP */ } if (!(tsign = PyLong_FromLong(sign))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)man); Py_DECREF(exp); Py_DECREF(tup); return NULL; + /* LCOV_EXCL_STOP */ } if (!(tbc = GMPy_PyLong_FromMpBitCnt(bc))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)man); Py_DECREF(exp); Py_DECREF(tup); Py_DECREF(tsign); return NULL; + /* LCOV_EXCL_STOP */ } PyTuple_SET_ITEM(tup, 0, tsign); @@ -148,9 +154,11 @@ Pympz_mpmath_normalize_fast(PyObject *self, PyObject *const *args, Py_ssize_t na } if (!(upper = GMPy_MPZ_New(NULL)) || !(lower = GMPy_MPZ_New(NULL))) { + /* LCOV_EXCL_START */ Py_XDECREF((PyObject*)upper); Py_XDECREF((PyObject*)lower); return NULL; + /* LCOV_EXCL_STOP */ } if (bc > prec) { @@ -201,16 +209,20 @@ Pympz_mpmath_normalize_fast(PyObject *self, PyObject *const *args, Py_ssize_t na } if (!(tmp = PyLong_FromUnsignedLong((unsigned long)shift))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); return NULL; + /* LCOV_EXCL_STOP */ } if (!(newexp = PyNumber_Add(exp, tmp))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); Py_DECREF(tmp); return NULL; + /* LCOV_EXCL_STOP */ } Py_DECREF(tmp); bc = prec; @@ -226,17 +238,21 @@ Pympz_mpmath_normalize_fast(PyObject *self, PyObject *const *args, Py_ssize_t na mpz_tdiv_q_2exp(upper->z, upper->z, zbits); if (!(tmp = GMPy_PyLong_FromMpBitCnt(zbits))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); Py_DECREF(newexp); return NULL; + /* LCOV_EXCL_STOP */ } if (!(newexp2 = PyNumber_Add(newexp, tmp))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); Py_DECREF(tmp); Py_DECREF(newexp); return NULL; + /* LCOV_EXCL_STOP */ } Py_DECREF(newexp); Py_DECREF(tmp); @@ -298,10 +314,12 @@ Pympz_mpmath_create_fast(PyObject *self, PyObject *const *args, Py_ssize_t nargs upper = GMPy_MPZ_New(NULL); lower = GMPy_MPZ_New(NULL); if (!upper || !lower) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)man); Py_XDECREF((PyObject*)upper); Py_XDECREF((PyObject*)lower); return NULL; + /* LCOV_EXCL_STOP */ } /* Extract sign, make man positive, and set bit count */ @@ -362,16 +380,20 @@ Pympz_mpmath_create_fast(PyObject *self, PyObject *const *args, Py_ssize_t nargs } } if (!(tmp = PyLong_FromUnsignedLong((unsigned long)shift))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); return NULL; + /* LCOV_EXCL_STOP */ } if (!(newexp = PyNumber_Add(exp, tmp))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)man); Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); Py_DECREF(tmp); return NULL; + /* LCOV_EXCL_STOP */ } Py_DECREF(tmp); bc = prec; @@ -386,19 +408,23 @@ Pympz_mpmath_create_fast(PyObject *self, PyObject *const *args, Py_ssize_t nargs mpz_tdiv_q_2exp(upper->z, upper->z, zbits); if (!(tmp = GMPy_PyLong_FromMpBitCnt(zbits))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)man); Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); Py_DECREF(newexp); return NULL; + /* LCOV_EXCL_STOP */ } if (!(newexp2 = PyNumber_Add(newexp, tmp))) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)man); Py_DECREF((PyObject*)upper); Py_DECREF((PyObject*)lower); Py_DECREF(tmp); Py_DECREF(newexp); return NULL; + /* LCOV_EXCL_STOP */ } Py_DECREF(newexp); Py_DECREF(tmp); From b46451f3430ecf68251726ad2d27b9c88012573d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 24 Oct 2023 05:08:41 +0300 Subject: [PATCH 5/8] Improve test coverage for gmpy2_mpc.c --- src/gmpy2_mpc.c | 2 -- test/test_mpc.py | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/gmpy2_mpc.c b/src/gmpy2_mpc.c index aaf954b2..a73fb6ae 100644 --- a/src/gmpy2_mpc.c +++ b/src/gmpy2_mpc.c @@ -263,5 +263,3 @@ static PyTypeObject MPC_Type = GMPy_MPC_NewInit, /* tp_new */ 0, /* tp_free */ }; - - diff --git a/test/test_mpc.py b/test/test_mpc.py index 1f1fdf51..e75379e1 100644 --- a/test/test_mpc.py +++ b/test/test_mpc.py @@ -197,3 +197,24 @@ def test_mpc_divmod(): @example(complex(-2)) def test_mpc_hash(c): assert hash(mpc(c)) == hash(c) + + +def test_mpc_exc(): + gmpy2.set_context(gmpy2.ieee(32)) + + ctx = gmpy2.get_context() + ctx.trap_overflow = True + ctx.trap_underflow = True + + c = mpc(0.1 + 0.1j) + + pytest.raises(gmpy2.UnderflowResultError, lambda: c**201) + pytest.raises(gmpy2.OverflowResultError, lambda: c**-201) + + ctx.trap_inexact = True + + pytest.raises(gmpy2.InexactResultError, lambda: mpc(0.25)**0.25) + + ctx.trap_invalid = True + + pytest.raises(gmpy2.InvalidOperationError, lambda: mpc(mpfr('nan'))) From 513551b1aa3d5f45d51c7f57796a2928e2111c47 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 24 Oct 2023 05:37:35 +0300 Subject: [PATCH 6/8] Improve test coverage for gmpy2_mod.c Add missing type-casts in test_mpz_arithmetics() --- src/gmpy2_mod.c | 1 - test/test_mpfr.py | 5 +++++ test/test_mpz.py | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gmpy2_mod.c b/src/gmpy2_mod.c index 5bc9fe15..2a65469a 100644 --- a/src/gmpy2_mod.c +++ b/src/gmpy2_mod.c @@ -361,4 +361,3 @@ GMPy_Context_Mod(PyObject *self, PyObject *args) PyTuple_GET_ITEM(args, 1), context); } - diff --git a/test/test_mpfr.py b/test/test_mpfr.py index 56caf518..9ac59854 100644 --- a/test/test_mpfr.py +++ b/test/test_mpfr.py @@ -254,6 +254,11 @@ def test_mpfr_divmod(): pytest.raises(gmpy2.InvalidOperationError, lambda: divmod(mpfr(1), gmpy2.inf())) +def test_mpfr_mod(): + r = mpfr('0.0') % mpfr('-1.0') + assert r.is_zero() and r.is_signed() + + def test_mpfr_subnormalize(): gmpy2.set_context(gmpy2.ieee(64)) diff --git a/test/test_mpz.py b/test/test_mpz.py index 8a1e42a8..fa2dabc8 100644 --- a/test/test_mpz.py +++ b/test/test_mpz.py @@ -155,6 +155,8 @@ def test_mpz_pickling(): @example(14, 105) @example(64, 123456789012345678901234567890) def test_mpz_arithmetics(i, z): + i, z = map(mpz, [i, z]) + assert int(i) + int(z) == i + z assert int(z) + int(i) == z + i @@ -168,13 +170,16 @@ def test_mpz_arithmetics(i, z): if z: assert int(i) // int(z) == i // z assert int(i) % int(z) == i % z + assert i % int(z) == i % z assert divmod(int(i), int(z)) == divmod(i, z) if i: assert int(z) // int(i) == z // i assert int(z) % int(i) == z % i + assert z % int(i) == z % i assert divmod(int(z), int(i)) == divmod(z, i) + @settings(max_examples=1000) @given(integers(min_value=0), integers(min_value=1, max_value=100000)) From ed3036c8f5cb24c637a35a4a98564af7b426092f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 24 Oct 2023 06:03:17 +0300 Subject: [PATCH 7/8] Improve test coverage for gmpy2_divmod.c --- test/test_mpfr.py | 2 ++ test/test_mpz.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/test/test_mpfr.py b/test/test_mpfr.py index 9ac59854..6bee22fe 100644 --- a/test/test_mpfr.py +++ b/test/test_mpfr.py @@ -253,6 +253,8 @@ def test_mpfr_divmod(): with gmpy2.local_context(trap_invalid=True): pytest.raises(gmpy2.InvalidOperationError, lambda: divmod(mpfr(1), gmpy2.inf())) + assert divmod(mpfr(111), mpfr(-222)) == (mpfr('-1.0'), mpfr('-111.0')) + def test_mpfr_mod(): r = mpfr('0.0') % mpfr('-1.0') diff --git a/test/test_mpz.py b/test/test_mpz.py index fa2dabc8..20614917 100644 --- a/test/test_mpz.py +++ b/test/test_mpz.py @@ -154,6 +154,7 @@ def test_mpz_pickling(): @example(-11, 75) @example(14, 105) @example(64, 123456789012345678901234567890) +@example(321, -123) def test_mpz_arithmetics(i, z): i, z = map(mpz, [i, z]) @@ -171,13 +172,19 @@ def test_mpz_arithmetics(i, z): assert int(i) // int(z) == i // z assert int(i) % int(z) == i % z assert i % int(z) == i % z + assert int(i) % z == i % z assert divmod(int(i), int(z)) == divmod(i, z) + assert divmod(i, int(z)) == divmod(i, z) + assert divmod(int(i), z) == divmod(i, z) if i: assert int(z) // int(i) == z // i assert int(z) % int(i) == z % i assert z % int(i) == z % i + assert int(z) % i == z % i assert divmod(int(z), int(i)) == divmod(z, i) + assert divmod(z, int(i)) == divmod(z, i) + assert divmod(int(z), i) == divmod(z, i) @settings(max_examples=1000) From 9c21e098912b27a29d97074d716a49a7a5738769 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 24 Oct 2023 06:48:09 +0300 Subject: [PATCH 8/8] Improve test coverage for gmpy2_mpfr_misc.c --- src/gmpy2_mpfr_misc.c | 8 ++++++++ test/test_functions.py | 37 +++++++++++++++++++++++++++++++++++-- test/test_misc.py | 6 ++++++ test/test_mpfr.py | 13 +++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/gmpy2_mpfr_misc.c b/src/gmpy2_mpfr_misc.c index 00bf2c9f..13c2eed8 100644 --- a/src/gmpy2_mpfr_misc.c +++ b/src/gmpy2_mpfr_misc.c @@ -448,9 +448,11 @@ GMPy_MPFR_Integer_Ratio_Method(PyObject *self, PyObject *args) num = GMPy_MPZ_New(context); den = GMPy_MPZ_New(context); if (!num || !den) { + /* LCOV_EXCL_START */ Py_XDECREF((PyObject*)num); Py_XDECREF((PyObject*)den); return NULL; + /* LCOV_EXCL_STOP */ } if (mpfr_zero_p(MPFR(self))) { @@ -472,8 +474,10 @@ GMPy_MPFR_Integer_Ratio_Method(PyObject *self, PyObject *args) } result = Py_BuildValue("(NN)", (PyObject*)num, (PyObject*)den); if (!result) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)num); Py_DECREF((PyObject*)den); + /* LCOV_EXCL_STOP */ } return result; } @@ -505,9 +509,11 @@ GMPy_MPFR_Mantissa_Exp_Method(PyObject *self, PyObject *args) mantissa = GMPy_MPZ_New(context); exponent = GMPy_MPZ_New(context); if (!mantissa || !exponent) { + /* LCOV_EXCL_START */ Py_XDECREF((PyObject*)mantissa); Py_XDECREF((PyObject*)exponent); return NULL; + /* LCOV_EXCL_STOP */ } if (mpfr_zero_p(MPFR(self))) { @@ -520,8 +526,10 @@ GMPy_MPFR_Mantissa_Exp_Method(PyObject *self, PyObject *args) } result = Py_BuildValue("(NN)", (PyObject*)mantissa, (PyObject*)exponent); if (!result) { + /* LCOV_EXCL_START */ Py_DECREF((PyObject*)mantissa); Py_DECREF((PyObject*)exponent); + /* LCOV_EXCL_STOP */ } return result; } diff --git a/test/test_functions.py b/test/test_functions.py index 16bd9db9..19492246 100644 --- a/test/test_functions.py +++ b/test/test_functions.py @@ -1,8 +1,9 @@ import pytest import gmpy2 -from gmpy2 import (root, rootn, zero, mpz, mpq, mpfr, mpc, is_nan, maxnum, - minnum, fma, fms, ieee, fmma, fmms) +from gmpy2 import (can_round, fac, fma, fmma, fmms, fms, get_exp, ieee, is_nan, + maxnum, minnum, mpc, mpfr, mpq, mpz, root, rootn, set_exp, + zero) def test_root(): @@ -133,3 +134,35 @@ def test_trigonometric(): assert gmpy2.atanh(mpc(2.0, 3.0)) == gmpy2.atanh(complex(2, 3)) assert gmpy2.tanh(mpc(4,5)) == mpc('1.0005630461157933-0.00036520305451130409j') + + +def test_get_exp(): + ctx = gmpy2.get_context() + ctx.trap_erange = True + + pytest.raises(gmpy2.RangeError, lambda: get_exp(mpfr('inf'))) + + +def test_set_exp(): + pytest.raises(ValueError, lambda: set_exp(mpfr('1.0'), int(fac(100)))) + + gmpy2.set_context(gmpy2.ieee(32)) + ctx = gmpy2.get_context() + ctx.trap_erange = True + + pytest.raises(gmpy2.RangeError, lambda: set_exp(mpfr('1.0'), 1000)) + + ctx.trap_erange = False + assert set_exp(mpfr('1.0'), 1000) == mpfr('1.0') + + +def test_can_round(): + pytest.raises(TypeError, lambda: can_round(mpfr('1.1'), 10, "spam")) + pytest.raises(ValueError, lambda: can_round(mpfr('1.1'), 10, 111, 111, 111)) + pytest.raises(ValueError, lambda: can_round(mpfr('1.1'), 10, 1, 111, 111)) + pytest.raises(ValueError, lambda: can_round(mpfr('1.1'), 10, 1, 1, -111)) + + x = mpfr('-1.112') + + assert can_round(x, 10, 1, 1, 1) + assert not can_round(x, 10, 1, 1, 10) diff --git a/test/test_misc.py b/test/test_misc.py index c335e5c3..c60aa185 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -1,3 +1,5 @@ +import sys + import gmpy2 @@ -13,3 +15,7 @@ def test_misc(): '3 or later. The supported versions of the GMP, ' 'MPFR, and MPC libraries are also licensed ' 'under LGPL 3 or later.') + + +def test_sizeof(): + assert sys.getsizeof(gmpy2.mpfr('1.0')) > 0 diff --git a/test/test_mpfr.py b/test/test_mpfr.py index 6bee22fe..245db419 100644 --- a/test/test_mpfr.py +++ b/test/test_mpfr.py @@ -288,3 +288,16 @@ def fmt(v): assert fmt(gmpy2.next_toward(a, 10)) == '1.00000000000000000000001p-126' gmpy2.set_context(gmpy2.context()) + + +def test_mpfr_as_integer_ratio(): + assert mpfr('1.1e+2').as_integer_ratio() == (mpz(110), mpz(1)) + + +def test_mpfr_round(): + pytest.raises(TypeError, lambda: round(mpfr('1.0'), "spam")) + + r = round(mpfr('-0.0'), 123) + assert r.is_zero() and r.is_signed() + + assert round(mpfr('12.34'), -1) == mpfr('10.0')