741 lines
18 KiB
Diff
741 lines
18 KiB
Diff
diff --git a/src/sage/ext_data/valgrind/valgrind-python.supp b/src/sage/ext_data/valgrind/valgrind-python.supp
|
|
new file mode 100644
|
|
index 00000000000..16aa2858484
|
|
--- /dev/null
|
|
+++ b/src/sage/ext_data/valgrind/valgrind-python.supp
|
|
@@ -0,0 +1,480 @@
|
|
+# From the CPython repository with the suppressions for _PyObject_Free
|
|
+# and _PyObject_Realloc enabled. See the upstream suppression file for
|
|
+# details:
|
|
+#
|
|
+# https://github.com/python/cpython/blob/main/Misc/valgrind-python.supp
|
|
+
|
|
+# all tool names: Addrcheck,Memcheck,cachegrind,helgrind,massif
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Invalid read of size 4
|
|
+ Memcheck:Addr4
|
|
+ fun:address_in_range
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Invalid read of size 4
|
|
+ Memcheck:Value4
|
|
+ fun:address_in_range
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Invalid read of size 8 (x86_64 aka amd64)
|
|
+ Memcheck:Value8
|
|
+ fun:address_in_range
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
|
|
+ Memcheck:Cond
|
|
+ fun:address_in_range
|
|
+}
|
|
+
|
|
+#
|
|
+# Leaks (including possible leaks)
|
|
+# Hmmm, I wonder if this masks some real leaks. I think it does.
|
|
+# Will need to fix that.
|
|
+#
|
|
+
|
|
+{
|
|
+ Suppress leaking the GIL after a fork.
|
|
+ Memcheck:Leak
|
|
+ fun:malloc
|
|
+ fun:PyThread_allocate_lock
|
|
+ fun:PyEval_ReInitThreads
|
|
+}
|
|
+
|
|
+{
|
|
+ Suppress leaking the autoTLSkey. This looks like it shouldn't leak though.
|
|
+ Memcheck:Leak
|
|
+ fun:malloc
|
|
+ fun:PyThread_create_key
|
|
+ fun:_PyGILState_Init
|
|
+ fun:Py_InitializeEx
|
|
+ fun:Py_Main
|
|
+}
|
|
+
|
|
+{
|
|
+ Hmmm, is this a real leak or like the GIL?
|
|
+ Memcheck:Leak
|
|
+ fun:malloc
|
|
+ fun:PyThread_ReInitTLS
|
|
+}
|
|
+
|
|
+{
|
|
+ Handle PyMalloc confusing valgrind (possibly leaked)
|
|
+ Memcheck:Leak
|
|
+ fun:realloc
|
|
+ fun:_PyObject_GC_Resize
|
|
+ fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
|
|
+}
|
|
+
|
|
+{
|
|
+ Handle PyMalloc confusing valgrind (possibly leaked)
|
|
+ Memcheck:Leak
|
|
+ fun:malloc
|
|
+ fun:_PyObject_GC_New
|
|
+ fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
|
|
+}
|
|
+
|
|
+{
|
|
+ Handle PyMalloc confusing valgrind (possibly leaked)
|
|
+ Memcheck:Leak
|
|
+ fun:malloc
|
|
+ fun:_PyObject_GC_NewVar
|
|
+ fun:COMMENT_THIS_LINE_TO_DISABLE_LEAK_WARNING
|
|
+}
|
|
+
|
|
+#
|
|
+# Non-python specific leaks
|
|
+#
|
|
+
|
|
+{
|
|
+ Handle pthread issue (possibly leaked)
|
|
+ Memcheck:Leak
|
|
+ fun:calloc
|
|
+ fun:allocate_dtv
|
|
+ fun:_dl_allocate_tls_storage
|
|
+ fun:_dl_allocate_tls
|
|
+}
|
|
+
|
|
+{
|
|
+ Handle pthread issue (possibly leaked)
|
|
+ Memcheck:Leak
|
|
+ fun:memalign
|
|
+ fun:_dl_allocate_tls_storage
|
|
+ fun:_dl_allocate_tls
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Invalid read of size 4
|
|
+ Memcheck:Addr4
|
|
+ fun:_PyObject_Free
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Invalid read of size 4
|
|
+ Memcheck:Value4
|
|
+ fun:_PyObject_Free
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
|
|
+ Memcheck:Addr8
|
|
+ fun:_PyObject_Free
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
|
|
+ Memcheck:Value8
|
|
+ fun:_PyObject_Free
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
|
|
+ Memcheck:Cond
|
|
+ fun:_PyObject_Free
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Invalid read of size 4
|
|
+ Memcheck:Addr4
|
|
+ fun:_PyObject_Realloc
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Invalid read of size 4
|
|
+ Memcheck:Value4
|
|
+ fun:_PyObject_Realloc
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
|
|
+ Memcheck:Addr8
|
|
+ fun:_PyObject_Realloc
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Use of uninitialised value of size 8
|
|
+ Memcheck:Value8
|
|
+ fun:_PyObject_Realloc
|
|
+}
|
|
+
|
|
+{
|
|
+ ADDRESS_IN_RANGE/Conditional jump or move depends on uninitialised value
|
|
+ Memcheck:Cond
|
|
+ fun:_PyObject_Realloc
|
|
+}
|
|
+
|
|
+###
|
|
+### All the suppressions below are for errors that occur within libraries
|
|
+### that Python uses. The problems to not appear to be related to Python's
|
|
+### use of the libraries.
|
|
+###
|
|
+
|
|
+{
|
|
+ Generic ubuntu ld problems
|
|
+ Memcheck:Addr8
|
|
+ obj:/lib/ld-2.4.so
|
|
+ obj:/lib/ld-2.4.so
|
|
+ obj:/lib/ld-2.4.so
|
|
+ obj:/lib/ld-2.4.so
|
|
+}
|
|
+
|
|
+{
|
|
+ Generic gentoo ld problems
|
|
+ Memcheck:Cond
|
|
+ obj:/lib/ld-2.3.4.so
|
|
+ obj:/lib/ld-2.3.4.so
|
|
+ obj:/lib/ld-2.3.4.so
|
|
+ obj:/lib/ld-2.3.4.so
|
|
+}
|
|
+
|
|
+{
|
|
+ DBM problems, see test_dbm
|
|
+ Memcheck:Param
|
|
+ write(buf)
|
|
+ fun:write
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ fun:dbm_close
|
|
+}
|
|
+
|
|
+{
|
|
+ DBM problems, see test_dbm
|
|
+ Memcheck:Value8
|
|
+ fun:memmove
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ fun:dbm_store
|
|
+ fun:dbm_ass_sub
|
|
+}
|
|
+
|
|
+{
|
|
+ DBM problems, see test_dbm
|
|
+ Memcheck:Cond
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ fun:dbm_store
|
|
+ fun:dbm_ass_sub
|
|
+}
|
|
+
|
|
+{
|
|
+ DBM problems, see test_dbm
|
|
+ Memcheck:Cond
|
|
+ fun:memmove
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ obj:/usr/lib/libdb1.so.2
|
|
+ fun:dbm_store
|
|
+ fun:dbm_ass_sub
|
|
+}
|
|
+
|
|
+{
|
|
+ GDBM problems, see test_gdbm
|
|
+ Memcheck:Param
|
|
+ write(buf)
|
|
+ fun:write
|
|
+ fun:gdbm_open
|
|
+
|
|
+}
|
|
+
|
|
+{
|
|
+ Uninitialised byte(s) false alarm, see bpo-35561
|
|
+ Memcheck:Param
|
|
+ epoll_ctl(event)
|
|
+ fun:epoll_ctl
|
|
+ fun:pyepoll_internal_ctl
|
|
+}
|
|
+
|
|
+{
|
|
+ ZLIB problems, see test_gzip
|
|
+ Memcheck:Cond
|
|
+ obj:/lib/libz.so.1.2.3
|
|
+ obj:/lib/libz.so.1.2.3
|
|
+ fun:deflate
|
|
+}
|
|
+
|
|
+{
|
|
+ Avoid problems w/readline doing a putenv and leaking on exit
|
|
+ Memcheck:Leak
|
|
+ fun:malloc
|
|
+ fun:xmalloc
|
|
+ fun:sh_set_lines_and_columns
|
|
+ fun:_rl_get_screen_size
|
|
+ fun:_rl_init_terminal_io
|
|
+ obj:/lib/libreadline.so.4.3
|
|
+ fun:rl_initialize
|
|
+}
|
|
+
|
|
+# Valgrind emits "Conditional jump or move depends on uninitialised value(s)"
|
|
+# false alarms on GCC builtin strcmp() function. The GCC code is correct.
|
|
+#
|
|
+# Valgrind bug: https://bugs.kde.org/show_bug.cgi?id=264936
|
|
+{
|
|
+ bpo-38118: Valgrind emits false alarm on GCC builtin strcmp()
|
|
+ Memcheck:Cond
|
|
+ fun:PyUnicode_Decode
|
|
+}
|
|
+
|
|
+
|
|
+###
|
|
+### These occur from somewhere within the SSL, when running
|
|
+### test_socket_sll. They are too general to leave on by default.
|
|
+###
|
|
+###{
|
|
+### somewhere in SSL stuff
|
|
+### Memcheck:Cond
|
|
+### fun:memset
|
|
+###}
|
|
+###{
|
|
+### somewhere in SSL stuff
|
|
+### Memcheck:Value4
|
|
+### fun:memset
|
|
+###}
|
|
+###
|
|
+###{
|
|
+### somewhere in SSL stuff
|
|
+### Memcheck:Cond
|
|
+### fun:MD5_Update
|
|
+###}
|
|
+###
|
|
+###{
|
|
+### somewhere in SSL stuff
|
|
+### Memcheck:Value4
|
|
+### fun:MD5_Update
|
|
+###}
|
|
+
|
|
+# Fedora's package "openssl-1.0.1-0.1.beta2.fc17.x86_64" on x86_64
|
|
+# See http://bugs.python.org/issue14171
|
|
+{
|
|
+ openssl 1.0.1 prng 1
|
|
+ Memcheck:Cond
|
|
+ fun:bcmp
|
|
+ fun:fips_get_entropy
|
|
+ fun:FIPS_drbg_instantiate
|
|
+ fun:RAND_init_fips
|
|
+ fun:OPENSSL_init_library
|
|
+ fun:SSL_library_init
|
|
+ fun:init_hashlib
|
|
+}
|
|
+
|
|
+{
|
|
+ openssl 1.0.1 prng 2
|
|
+ Memcheck:Cond
|
|
+ fun:fips_get_entropy
|
|
+ fun:FIPS_drbg_instantiate
|
|
+ fun:RAND_init_fips
|
|
+ fun:OPENSSL_init_library
|
|
+ fun:SSL_library_init
|
|
+ fun:init_hashlib
|
|
+}
|
|
+
|
|
+{
|
|
+ openssl 1.0.1 prng 3
|
|
+ Memcheck:Value8
|
|
+ fun:_x86_64_AES_encrypt_compact
|
|
+ fun:AES_encrypt
|
|
+}
|
|
+
|
|
+#
|
|
+# All of these problems come from using test_socket_ssl
|
|
+#
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:BN_bin2bn
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:BN_num_bits_word
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Value4
|
|
+ fun:BN_num_bits_word
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:BN_mod_exp_mont_word
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:BN_mod_exp_mont
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Param
|
|
+ write(buf)
|
|
+ fun:write
|
|
+ obj:/usr/lib/libcrypto.so.0.9.7
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:RSA_verify
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Value4
|
|
+ fun:RSA_verify
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Value4
|
|
+ fun:DES_set_key_unchecked
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Value4
|
|
+ fun:DES_encrypt2
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ obj:/usr/lib/libssl.so.0.9.7
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Value4
|
|
+ obj:/usr/lib/libssl.so.0.9.7
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:BUF_MEM_grow_clean
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:memcpy
|
|
+ fun:ssl3_read_bytes
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Cond
|
|
+ fun:SHA1_Update
|
|
+}
|
|
+
|
|
+{
|
|
+ from test_socket_ssl
|
|
+ Memcheck:Value4
|
|
+ fun:SHA1_Update
|
|
+}
|
|
+
|
|
+{
|
|
+ test_buffer_non_debug
|
|
+ Memcheck:Addr4
|
|
+ fun:PyUnicodeUCS2_FSConverter
|
|
+}
|
|
+
|
|
+{
|
|
+ test_buffer_non_debug
|
|
+ Memcheck:Addr4
|
|
+ fun:PyUnicode_FSConverter
|
|
+}
|
|
+
|
|
+{
|
|
+ wcscmp_false_positive
|
|
+ Memcheck:Addr8
|
|
+ fun:wcscmp
|
|
+ fun:_PyOS_GetOpt
|
|
+ fun:Py_Main
|
|
+ fun:main
|
|
+}
|
|
+
|
|
+# Additional suppressions for the unified decimal tests:
|
|
+{
|
|
+ test_decimal
|
|
+ Memcheck:Addr4
|
|
+ fun:PyUnicodeUCS2_FSConverter
|
|
+}
|
|
+
|
|
+{
|
|
+ test_decimal2
|
|
+ Memcheck:Addr4
|
|
+ fun:PyUnicode_FSConverter
|
|
+}
|
|
+
|
|
diff --git a/src/sage/symbolic/ginac/numeric.cpp b/src/sage/symbolic/ginac/numeric.cpp
|
|
index b40ed64edb5..8c55861c147 100644
|
|
--- a/src/sage/symbolic/ginac/numeric.cpp
|
|
+++ b/src/sage/symbolic/ginac/numeric.cpp
|
|
@@ -1576,6 +1576,62 @@ const numeric numeric::div(const numeric &other) const {
|
|
}
|
|
}
|
|
|
|
+
|
|
+// Compute `a^b` as an integer, where a is an integer. Assign to ``res``` if it is integral, or return ``false``.
|
|
+// The nonnegative real root is taken for even denominators. To be used inside numeric::integer_rational_power,
|
|
+// to handle the special case of integral ``a``.
|
|
+bool integer_rational_power_of_mpz(
|
|
+ numeric& res,
|
|
+ const numeric& a,
|
|
+ const numeric& b
|
|
+) {
|
|
+ if (a.t != MPZ)
|
|
+ throw std::runtime_error("integer_rational_power_of_mpz: bad input");
|
|
+ mpz_t z;
|
|
+ mpz_init(z);
|
|
+ mpz_set_ui(z, 0);
|
|
+ int sgn = mpz_sgn(a.v._bigint);
|
|
+ if (mpz_cmp_ui(a.v._bigint, 1) == 0
|
|
+ or mpz_cmp_ui(mpq_numref(b.v._bigrat), 0) == 0)
|
|
+ mpz_set_ui(z, 1);
|
|
+ else if (sgn == 0) {
|
|
+ res = *_num0_p;
|
|
+ mpz_clear(z);
|
|
+ return true;
|
|
+ }
|
|
+ else if (sgn < 0 and mpz_cmp_ui(mpq_denref(b.v._bigrat), 1)) {
|
|
+ mpz_clear(z);
|
|
+ return false;
|
|
+ } else {
|
|
+ if (not mpz_fits_ulong_p(mpq_numref(b.v._bigrat))
|
|
+ or not mpz_fits_ulong_p(mpq_denref(b.v._bigrat))) {
|
|
+ // too big to take roots/powers
|
|
+ mpz_clear(z);
|
|
+ return false;
|
|
+ }
|
|
+ if (mpz_cmp_ui(mpq_denref(b.v._bigrat), 2) == 0) {
|
|
+ if (mpz_perfect_square_p(a.v._bigint)) {
|
|
+ mpz_sqrt(z, a.v._bigint);
|
|
+ } else {
|
|
+ mpz_clear(z);
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ bool exact = mpz_root(z, a.v._bigint,
|
|
+ mpz_get_ui(mpq_denref(b.v._bigrat)));
|
|
+ if (not exact) {
|
|
+ mpz_clear(z);
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ mpz_pow_ui(z, z, mpz_get_ui(mpq_numref(b.v._bigrat)));
|
|
+ }
|
|
+ res = numeric(z); // transfers ownership, no mpz_clear
|
|
+ return true;
|
|
+}
|
|
+
|
|
+
|
|
// Compute `a^b` as an integer, if it is integral, or return ``false``.
|
|
// The nonnegative real root is taken for even denominators.
|
|
bool numeric::integer_rational_power(numeric& res,
|
|
@@ -1598,13 +1654,12 @@ bool numeric::integer_rational_power(numeric& res,
|
|
if (a.v._long < 0
|
|
and mpz_cmp_ui(mpq_denref(b.v._bigrat), 1))
|
|
return false;
|
|
- long z;
|
|
if (not mpz_fits_ulong_p(mpq_numref(b.v._bigrat))
|
|
or not mpz_fits_ulong_p(mpq_denref(b.v._bigrat)))
|
|
// too big to take roots/powers
|
|
return false;
|
|
if (b.is_equal(*_num1_2_p)) {
|
|
- z = std::lround(std::sqrt(a.v._long));
|
|
+ long z = std::lround(std::sqrt(a.v._long));
|
|
if (a.v._long == z*z) {
|
|
res = numeric(z);
|
|
return true;
|
|
@@ -1613,44 +1668,11 @@ bool numeric::integer_rational_power(numeric& res,
|
|
}
|
|
return integer_rational_power(res, a.to_bigint(), b);
|
|
}
|
|
- if (a.t != MPZ)
|
|
- throw std::runtime_error("integer_rational_power: bad input");
|
|
- int sgn = mpz_sgn(a.v._bigint);
|
|
- mpz_t z;
|
|
- mpz_init(z);
|
|
- mpz_set_ui(z, 0);
|
|
- if (mpz_cmp_ui(a.v._bigint, 1) == 0
|
|
- or mpz_cmp_ui(mpq_numref(b.v._bigrat), 0) == 0)
|
|
- mpz_set_ui(z, 1);
|
|
- else if (sgn == 0) {
|
|
- res = *_num0_p;
|
|
- return true;
|
|
- }
|
|
- else if (sgn < 0 and mpz_cmp_ui(mpq_denref(b.v._bigrat), 1))
|
|
- return false;
|
|
- else {
|
|
- if (not mpz_fits_ulong_p(mpq_numref(b.v._bigrat))
|
|
- or not mpz_fits_ulong_p(mpq_denref(b.v._bigrat)))
|
|
- // too big to take roots/powers
|
|
- return false;
|
|
- if (mpz_cmp_ui(mpq_denref(b.v._bigrat), 2) == 0) {
|
|
- if (mpz_perfect_square_p(a.v._bigint))
|
|
- mpz_sqrt(z, a.v._bigint);
|
|
- else
|
|
- return false;
|
|
- }
|
|
- else {
|
|
- bool exact = mpz_root(z, a.v._bigint,
|
|
- mpz_get_ui(mpq_denref(b.v._bigrat)));
|
|
- if (not exact)
|
|
- return false;
|
|
- }
|
|
- mpz_pow_ui(z, z, mpz_get_ui(mpq_numref(b.v._bigrat)));
|
|
- }
|
|
- res = numeric(z);
|
|
- return true;
|
|
+ // otherwise: a is integer
|
|
+ return integer_rational_power_of_mpz(res, a, b);
|
|
}
|
|
|
|
+
|
|
// for a^b return c,d such that a^b = c*d^b
|
|
// only for MPZ/MPQ base and MPQ exponent
|
|
void rational_power_parts(const numeric& a_orig, const numeric& b_orig,
|
|
diff --git a/src/sage/tests/memcheck/__init__.py b/src/sage/tests/memcheck/__init__.py
|
|
new file mode 100644
|
|
index 00000000000..e69de29bb2d
|
|
diff --git a/src/sage/tests/memcheck/run_tests.py b/src/sage/tests/memcheck/run_tests.py
|
|
new file mode 100644
|
|
index 00000000000..6ff4503a81b
|
|
--- /dev/null
|
|
+++ b/src/sage/tests/memcheck/run_tests.py
|
|
@@ -0,0 +1,24 @@
|
|
+import types
|
|
+
|
|
+
|
|
+def run_tests() -> None:
|
|
+ """
|
|
+ Run all memcheck tests
|
|
+ """
|
|
+ from sage.tests.memcheck import symbolic_expression
|
|
+ run_tests_in_module(symbolic_expression)
|
|
+
|
|
+
|
|
+def run_tests_in_module(mod: types.ModuleType) -> None:
|
|
+ """
|
|
+ Run all memcheck tests in the given module
|
|
+ """
|
|
+ for entry in dir(mod):
|
|
+ if not entry.startswith('test_'):
|
|
+ continue
|
|
+ test_func = getattr(mod, entry)
|
|
+ test_func()
|
|
+
|
|
+
|
|
+if __name__ == '__main__':
|
|
+ run_tests()
|
|
diff --git a/src/sage/tests/memcheck/run_tests_in_valgrind.py b/src/sage/tests/memcheck/run_tests_in_valgrind.py
|
|
new file mode 100644
|
|
index 00000000000..df5ad0e92b2
|
|
--- /dev/null
|
|
+++ b/src/sage/tests/memcheck/run_tests_in_valgrind.py
|
|
@@ -0,0 +1,35 @@
|
|
+"""
|
|
+Launch valgrind and run the memory leak tests
|
|
+
|
|
+
|
|
+From the commandline, run
|
|
+
|
|
+ sage -python -m sage.tests.memcheck.run_tests_in_valgrind
|
|
+
|
|
+to launch valgrind and execute the memory leak tests. Requires valgrind
|
|
+to be installed. Alternatively, run as a unit test:
|
|
+
|
|
+ sage: from sage.tests.memcheck.run_tests_in_valgrind import run_tests_in_valgrind
|
|
+ sage: run_tests_in_valgrind() # optional - valgrind
|
|
+"""
|
|
+
|
|
+import subprocess
|
|
+
|
|
+
|
|
+def run_tests_in_valgrind() -> None:
|
|
+ """
|
|
+ Run the sage.tests.memcheck.run_tests module inside valgrind
|
|
+ """
|
|
+ subprocess.check_call([
|
|
+ 'valgrind',
|
|
+ '--suppressions=src/sage/ext_data/valgrind/valgrind-python.supp',
|
|
+ '--show-possibly-lost=no',
|
|
+ '--show-reachable=no',
|
|
+ './venv/bin/python',
|
|
+ '-m',
|
|
+ 'sage.tests.memcheck.run_tests'
|
|
+ ])
|
|
+
|
|
+
|
|
+if __name__ == '__main__':
|
|
+ run_tests_in_valgrind()
|
|
diff --git a/src/sage/tests/memcheck/symbolic_expression.py b/src/sage/tests/memcheck/symbolic_expression.py
|
|
new file mode 100644
|
|
index 00000000000..52182fbe62d
|
|
--- /dev/null
|
|
+++ b/src/sage/tests/memcheck/symbolic_expression.py
|
|
@@ -0,0 +1,11 @@
|
|
+from sage.tests.memcheck.verify_no_leak import verify_no_leak
|
|
+
|
|
+
|
|
+def test_sqrt_sqrt_2() -> None:
|
|
+ from sage.misc.functional import sqrt
|
|
+ T2 = sqrt(2)
|
|
+
|
|
+ def sqrt_T2() -> None:
|
|
+ sqrt(T2)
|
|
+
|
|
+ verify_no_leak(sqrt_T2)
|
|
diff --git a/src/sage/tests/memcheck/verify_no_leak.py b/src/sage/tests/memcheck/verify_no_leak.py
|
|
new file mode 100644
|
|
index 00000000000..89ca90cf89c
|
|
--- /dev/null
|
|
+++ b/src/sage/tests/memcheck/verify_no_leak.py
|
|
@@ -0,0 +1,27 @@
|
|
+from typing import Tuple, Sequence, List, Callable, Any
|
|
+import valgrind
|
|
+
|
|
+
|
|
+def verify_no_leak(callback: Callable[[], Any],
|
|
+ repeat: int = 10000,
|
|
+ fuzzy: int = 10,
|
|
+ ) -> None:
|
|
+ """
|
|
+ Verify that the callback does not generate new definitely lost blocks
|
|
+
|
|
+ Raises an assertion if the callback leaks memory
|
|
+ """
|
|
+ callback() # warm_up
|
|
+ initial_blocks = (0, 0, 0, 0)
|
|
+ valgrind.memcheck_do_leak_check()
|
|
+ initial_blocks = valgrind.memcheck_count_leak_blocks()
|
|
+ for _ in range(repeat):
|
|
+ callback()
|
|
+ valgrind.memcheck_do_leak_check()
|
|
+ leak_blocks = valgrind.memcheck_count_leak_blocks()
|
|
+ leak = leak_blocks[0] - initial_blocks[0]
|
|
+ if leak < repeat - fuzzy:
|
|
+ return # callback did not leak at least once per call
|
|
+ blocks = round(leak / repeat, 2)
|
|
+ message = f'{callback} leaked {blocks} block on average ({repeat} iterations)'
|
|
+ raise AssertionError(message)
|