. "$BST_LIB" # BST-TIMEOUT: 1800 # THREE python3 starts, and one of them carries the largest # # pure-Python import this image has ever been asked for. # # # # They are a bare `import sympy' (`import'), a second whose # # STDERR is the assertion (`import-quiet'), and the probe -- # # which takes ONE start for 92 measurements. # # # # sympy 1.12 is ~1,400 modules and `import sympy' pulls the # # bulk of them plus the whole of mpmath. On the x86-64 # # control the import is 0.18 s and the whole probe 1.25 s; # # the interpreter START on this arm has measured at ~60 s, # # and interpreted work here runs an order of magnitude # # slower again, so 1800 is three starts at 300 s plus room # # for an import and a probe body that are both far larger # # than any python port before this one. # # # # NOT MEASURED ON TARGET YET: this port's first on-target # # run is the batch verify, and the measured elapsed replaces # # this note in the test plan rather than in this comment, # # which would cost an image build to keep the shipped suite # # byte-identical. If the three starts prove slower than this # # on target the RIGHT fix is this number, not fewer cases -- # # a suite-timeout red is a batch-wide cost. # # python3-sympy 1.12 (meta-openembedded/meta-python # recipes-devtools/python/python3-sympy_1.12.bb, `inherit pypi setuptools3', # PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHY THIS SUITE ASSERTS EXACT VALUES AND NOT TOLERANCES. # ============================================================================ # # Almost every numeric package ported to this image has to be scored against a # tolerance, because the answer is a float and the last bits belong to the # platform. sympy is the opposite kind of subject: it is a computer algebra # system, so `simplify(sin(x)**2 + cos(x)**2 - 1)' is the integer ZERO or the # port is wrong. There is no rounding to argue about and no epsilon anybody had # to choose, which means a red here is a real divergence rather than a threshold # somebody set carelessly. # # The identities are therefore asserted BOTH ways -- the printed form (`0') and # `is_zero' on the resulting object -- because `S.Zero' and a float `0.0' print # identically and mean different things. # # ============================================================================ # IT COMPILES NOTHING, WHICH IS WHAT MAKES IT PORTABLE HERE AT ALL. # ============================================================================ # # Pure Python: `inherit pypi setuptools3' over an sdist with no C. That matters # more than usual on this image, whose python3 is ONE STATIC BINARY with an # empty lib-dynload and no dynamic loader (BRINTOS-1476) -- a module carrying a # compiled extension could not be imported here whatever it built to. # `pkg-no-so' and `pkg-only-python' assert that from the packaged file list, and # `pkg-so-in-closure' asserts it from inside the interpreter. # # gmpy2 IS THE ADJACENT TRAP, AND IT IS PRESENT. sympy uses it as a faster # integer backend WHEN IT CAN IMPORT IT; it is a C extension, and the sibling # port BRINTOS-2440 established that one loads on this arm, so what is in use # here is the gmpy backend rather than the pure-Python fallback. # `gmpy2-absent' and `ground-types' hold both halves, scored against each other. # Every value in this file was verified identical under BOTH backends on the # control, so which one is in use is not what any expectation below rests on -- # and the target run bears that out: 134 of 136 cases were green on the gmpy # backend against expectations derived under the pure-Python one. # # ============================================================================ # THE OTHER HALF OF THIS PORT IS python3-mpmath, AND THAT IS ASSERTED, NOT # ASSUMED. # ============================================================================ # # sympy does every numeric evaluation through mpmath: `N(pi, 50)' is # `sympy.core.evalf' handing the request down to `mpmath.libmp'. mpmath is the # sibling port in this Epic (BRINTOS-2436), so the two being wired together ON # TARGET -- rather than each working alone -- is a fact this suite has to # establish rather than infer. # # `mpmath-in-modules', `mpmath-version' and `mpmath-file' read the module sympy # actually reached, out of `sys.modules', never from a separate import of the # probe's own -- which would be green on an image where sympy had silently # fallen back to something else. `evalf-libmp-module' names the binding itself. # And `n-pi-50' asserts the same fifty digits the mpmath port asserts, which is # the case that cannot pass unless both packages are present and correct. # # ============================================================================ # NOTHING HERE TOUCHES THIS PLATFORM'S KNOWN WEAK SPOTS, ON PURPOSE. # ============================================================================ # # No thread is started (brintos/linux-in-the-browser#85), no signal is armed # (alarm(2) was ENOSYS before the brintos/linux#77 fix) and no subprocess is # spawned. sympy has code paths that use all three -- # `sympy.utilities.timeutils', the plotting backends, the doctest runner -- and # none is on the path of anything below. So a red here is about # the interpreter or the library, and that narrowing is the reason these cases # were chosen and not others. # # ============================================================================ # THREE THINGS THE TICKET ASKED FOR THAT THE PACKAGE DOES NOT DO. # ============================================================================ # # All three were found by reading the packaged source and running it, and all # three are scored as the behaviour that IS there rather than left out: # # A ZERO DENOMINATOR DOES NOT RAISE. The ticket asks for "a `Rational' # denominator of zero ... asserted to raise the documented exception rather # than to hang". It does neither. sympy's arithmetic is projective, so a zero # denominator is a VALUE: `Rational(1, 0)' is `zoo' (ComplexInfinity, the # single point at infinity) and `Rational(0, 0)' is `nan'. Nothing is raised # and nothing hangs. `rational-zero-denominator' and the three cases beside it # score that, by TYPE as well as by printed form. A suite that had asserted # the exception would have been red on a correct port from the first boot. # # `1/0' IS NOT AN ERROR EITHER, for the same reason -- `sympy-one-over-zero' # is `zoo'. `err-python-zero-division' is pinned next to it so the divergence # from plain Python integer division, which DOES raise, is legible as a # divergence rather than looking like a missing case. # # AN EQUATION WITH NO SOLUTION RETURNS AN EMPTY LIST, it does not raise: # `no-solution-empty-list'. Only an equation sympy cannot CLOSE raises, and # `err-no-closed-form' pins that it is `NotImplementedError' -- the distinction # matters because an empty list reads to a caller as "no solutions", which # would be false. # # ============================================================================ # WHERE THE EXPECTED VALUES CAME FROM. # ============================================================================ # # Every literal below was MEASURED on an x86-64 control -- CPython 3.12.3 # against the image's 3.12 -- by running data/python3-sympy-probe.py against the # trees unpacked from this build's own `python3-sympy_1.12-r0_wasm32.deb' and # `python3-mpmath_1.3.0-r0_wasm32.deb', built at recipe scope. The packaging # literals come from those same .debs' control files and file lists. # # The control is run under `python3 -S', so the host's site-packages is off # `sys.path' and the closure the probe measures is the packaged one. That also # means the control ran on the pure-Python backend while the target runs on # gmpy2's -- which is why every value here was checked under BOTH before it was # asserted, and why the backend pair is the one thing scored dynamically. # # ============================================================================ # UPSTREAM'S OWN TESTS SHIP, AND THEY ARE NOT RUN HERE. # ============================================================================ # # Unlike most packages in this corpus sympy ships its test suite INSIDE the # installed package -- `sympy/*/tests/' -- so it lands on the image whether # anybody wants it or not. The recipe does not `inherit ptest', so there is no # `${PN}-ptest' package; `pkg-no-ptest' asserts that. Running upstream's suite # on target is a separate question of scale from this port and is not attempted: # it is tens of thousands of cases against an interpreter an order of magnitude # slower than the control. P=python3-sympy bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/sympy MPDIR=$SITE/mpmath DI=$SITE/sympy-1.12.dist-info PKGLIST=/var/lib/dpkg/info/python3-sympy.list PROBE=$BST_DATA/python3-sympy-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-sympy.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------ what shipped --- check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/no-module-py test ! -f "$SITE/sympy.py" check $P/init-file test -s "$PKGDIR/__init__.py" check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" # THE FOUR SUBPACKAGES EVERY CASE BELOW REACHES, named individually rather than # as a count: `core' for the expression tree, `simplify' for the identities, # `integrals' and `solvers' for the two hardest operations here. A package that # installed only part of the tree would still import and would fail much later. check $P/core-dir test -d "$PKGDIR/core" check $P/simplify-dir test -d "$PKGDIR/simplify" check $P/integrals-dir test -d "$PKGDIR/integrals" check $P/solvers-dir test -d "$PKGDIR/solvers" # THE SIBLING PORT'S TREE, asserted from THIS suite. sympy cannot evaluate a # number without it, so its absence would red half this file with a message # about sympy; this case says plainly which package is missing. check $P/mpmath-dir test -d "$MPDIR" # UPSTREAM'S TESTS DO SHIP -- see the header. Asserted rather than ignored, so # that a release which stopped shipping them is a visible change in what this # image carries rather than a silent one. check $P/upstream-tests-ship test -d "$PKGDIR/core/tests" # THE CLI DOES SHIP, and it is the one binary this port puts on the image. # sympy's dist-info declares `isympy = isympy:main' under `[console_scripts]', # so `setuptools3' generates /usr/bin/isympy and the wheel installs the # `isympy' MODULE beside the package rather than inside it -- which is why # `top_level.txt' names two things and not one. # # IT IS NOT RUN HERE. Starting it costs a fourth python3 start, and its only # non-interactive surface is `--version'; the module's presence and the script's # shebang are what this port can assert cheaply, and the suite's bound is # already sized by three starts. check $P/isympy-cli test -x /usr/bin/isympy check $P/isympy-module test -s "$SITE/isympy.py" check $P/isympy-not-in-pkg test ! -e "$PKGDIR/isympy.py" check_out $P/isympy-shebang "#!/usr/bin/env python3" head -n 1 /usr/bin/isympy # PEP 561: sympy IS annotated and ships the marker, so a consumer's type checker # reads its annotations. That is also why `pkg-only-python' below has to allow a # `py.typed' -- it is a file with no extension at all. check $P/py-typed test -f "$PKGDIR/py.typed" cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # WHAT THE PACKAGE SHIPS BESIDES PYTHON, ENUMERATED RATHER THAN WAIVED. sympy is # not a pure `.py' tree: it carries parser grammars and fixture corpora, and an # allow-list that simply said "and anything else" would assert nothing. Each # extension below was read off THIS build's own file list and each has a reason # to be there: # # .g4 ANTLR grammars for the LaTeX and Autolev parsers # .al the Autolev parser's test-example corpus # .xsl the MathML printer's stylesheets (sympy/utilities/mathml/data) # .png reference images for the plotting module's own tests # .txt LICENSE.txt beside the LaTeX grammar, and a corpus README # py.typed the PEP 561 marker -- a file with NO extension, which is why the # case below matches it by name # # A compiled extension could not be imported on this arm at all (BRINTOS-1476), # so this is the packaging-side statement of what `pkg-so-in-closure' says from # inside the interpreter -- and anything appearing here that is not on the list # is a finding about what a release started shipping. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; *.g4|*.al|*.xsl|*.png) continue ;; *.txt) continue ;; */py.typed) continue ;; /usr/bin/isympy) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # EVERY MODULE IS BYTE-COMPILED. 1447 `.py' and 1447 `.pyc': the counts are # asserted as a PAIR rather than individually, because what matters is that # `do_install' left none behind -- a module without its `.pyc' is compiled on # first import, into a read-only rootfs, on every single start. check_eq $P/pkg-py-count "1447" "$(grep -c '\.py$' "$W/pkgf")" check_eq $P/pkg-pyc-count "1447" "$(grep -c '\.pyc$' "$W/pkgf")" check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: sympy" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 1.12" cat "$DI/METADATA" # `top_level.txt' NAMES TWO THINGS, and both are pinned: the package, and the # free-standing `isympy' module the console script imports. check_out $P/dist-info-top-level "sympy" cat "$DI/top_level.txt" check_out $P/dist-info-top-level-isympy "isympy" cat "$DI/top_level.txt" # THE ENTRY POINT ITSELF, which is what generated /usr/bin/isympy above. check_out $P/dist-info-entry-point "isympy = isympy:main" \ cat "$DI/entry_points.txt" # THE WHEEL WAS BUILT BY setuptools' bdist_wheel, which is what `setuptools3' # reduces to here -- the case that says the build class did its job -- and it is # a PURE wheel, which is the packaging-side statement that nothing compiled. check_out $P/dist-info-generator "Generator: bdist_wheel" cat "$DI/WHEEL" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" # ------------------------------------------------------------- the package --- sed -n '/^Package: python3-sympy$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $P/pkg-known test -s "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" # THE DEPENDENCY THIS WHOLE PORT IS SEQUENCED AROUND. meta-python declares # `python3-mpmath' in RDEPENDS and the built .deb carries it, so `dpkg' would # refuse to configure sympy without it -- which is why this ticket is taken # after its sibling. `pkg-depends-mpmath' is the case that says the declaration # survived into the package rather than being lost in packaging. check_out $P/pkg-depends-mpmath "python3-mpmath" cat "$W/dep" check_out $P/pkg-depends-core "python3-core" cat "$W/dep" check_out $P/pkg-depends-misc "python3-misc" cat "$W/dep" # NO -ptest PACKAGE IS BUILT -- the recipe does not `inherit ptest' at all, even # though upstream's tests ship inside the main package. check_eq $P/pkg-no-ptest "" \ "$(sed -n '/^Package: python3-sympy-ptest$/p' /var/lib/dpkg/status 2>/dev/null)" # AND THE SIBLING IS ACTUALLY INSTALLED, not merely depended on. A `Depends' # line is a claim about the archive; this is the fact about the image. check_out $P/mpmath-pkg-installed "Status: install ok installed" \ sh -c 'sed -n "/^Package: python3-mpmath\$/,/^\$/p" /var/lib/dpkg/status' # --------------------------------------------------------------- it imports --- # THE CASE THE WHOLE PORT TURNS ON, and by far the most expensive single thing # this suite does. Everything below it is unscorable without it. check $P/import python3 -c 'import sympy' # ...AND SAYS NOTHING WHILE DOING IT. sympy warns on some configurations -- a # missing optional backend, a deprecated stdlib call -- and a warning here would # land on a caller's stderr on every start. check_eq $P/import-quiet "" \ "$(python3 -c 'import sympy' 2>&1)" if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present bst_probe $P/probe-run "$W/probe" 1200 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "92" "$(pv emitted_count)" # ---------------------------------------------------------------- identity --- check_eq $P/import-sympy "ok" "$(pv import_sympy)" # THE MODULE THE PROBE MEASURED IS THE PACKAGED ONE. The probe sits in # ${libdir}/brintos-tests/data and the interpreter puts a script's own directory # first on sys.path, so this is the case that would catch a probe having # shadowed the module it measures. check_eq $P/module-file "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/version-attr "1.12" "$(pv version_attr)" check_eq $P/python-major-minor "3.12" "$(pv python_major_minor)" # ------------------------------------------- the sibling port, from inside --- # READ OFF `sys.modules', not imported by the probe -- see the header. These # four are what say sympy and mpmath are wired together on THIS image. check_eq $P/mpmath-in-modules "yes" "$(pv mpmath_in_modules)" check_eq $P/mpmath-version "1.3.0" "$(pv mpmath_version)" check_eq $P/mpmath-file "$MPDIR/__init__.py" "$(pv mpmath_file)" check_eq $P/evalf-libmp-module "mpmath.libmp" "$(pv evalf_libmp_module)" # ----------------------------------------------------- no compiled backend --- # SCOPED TO THESE TWO PACKAGES' OWN TREES. A closure-wide sweep would be a # statement about the interpreter and would have two different right answers on # the control and here; what is a fact about sympy and mpmath is that neither # ships a compiled object. check_eq $P/pkg-so-in-closure "" "$(pv pkg_so_in_closure)" # gmpy2 IS A C EXTENSION AND IT DOES LOAD HERE. The sibling port BRINTOS-2440 # put python3-gmpy2 2.1.5 on this image's IMAGE_INSTALL, so sympy imports it and # selects the `gmpy' backend. The pure-Python expectation this pair first # carried was a PREDICTION written before that port was in the image, and on the # batch image of 2026-09-10 it was the only thing red in 136 cases. # # The two are now scored AGAINST EACH OTHER rather than against a pinned # backend: what has to hold is that sympy's choice FOLLOWS gmpy2's # importability. That keeps the regression this pair was put here to catch -- a # silent change of every integer operation's implementation -- pointing in both # directions, instead of trading one hardcoded backend for the other. # # Every value below was verified identical under BOTH backends on the control, # and the 134 cases that passed beside these two on the gmpy backend are what # says so on target as well. case "$(pv gmpy2_absent)" in yes) _want_ground="python" ;; no) _want_ground="gmpy" ;; *) _want_ground="" ;; esac check_eq $P/gmpy2-absent "no" "$(pv gmpy2_absent)" check_eq $P/ground-types "$_want_ground" "$(pv ground_types)" # ------------------------------------------------- identities: exactly zero --- # NINE IDENTITIES, EACH SCORED TWICE: the printed form, and `is_zero' on the # object. `S.Zero' and a float `0.0' print identically, so the printed form # alone would pass on a result that had silently become numeric. check_eq $P/id-trig-pythag "0" "$(pv id_trig_pythag)" check_eq $P/id-trig-pythag-iszero "yes" "$(pv id_trig_pythag_iszero)" check_eq $P/id-trig-double "0" "$(pv id_trig_double)" check_eq $P/id-trig-double-iszero "yes" "$(pv id_trig_double_iszero)" check_eq $P/id-trig-tan "0" "$(pv id_trig_tan)" check_eq $P/id-trig-tan-iszero "yes" "$(pv id_trig_tan_iszero)" check_eq $P/id-hyper-pythag "0" "$(pv id_hyper_pythag)" check_eq $P/id-hyper-pythag-iszero "yes" "$(pv id_hyper_pythag_iszero)" # THE TWO LOGARITHMIC ONES NEED POSITIVE SYMBOLS AND THE PROBE DECLARES THEM SO. # `log(x*y) - log(x) - log(y)' is NOT identically zero over the complex plane -- # it is off by a multiple of 2*pi*I on the wrong branch -- so an unqualified # symbol would (correctly) refuse to simplify. Getting a `0' here therefore also # says the assumptions system is working, which is a second thing worth knowing. check_eq $P/id-log-product "0" "$(pv id_log_product)" check_eq $P/id-log-product-iszero "yes" "$(pv id_log_product_iszero)" check_eq $P/id-log-power "0" "$(pv id_log_power)" check_eq $P/id-log-power-iszero "yes" "$(pv id_log_power_iszero)" check_eq $P/id-exp-log "0" "$(pv id_exp_log)" check_eq $P/id-exp-log-iszero "yes" "$(pv id_exp_log_iszero)" check_eq $P/id-poly-diff2 "0" "$(pv id_poly_diff2)" check_eq $P/id-poly-diff2-iszero "yes" "$(pv id_poly_diff2_iszero)" check_eq $P/id-poly-diff3 "0" "$(pv id_poly_diff3)" check_eq $P/id-poly-diff3-iszero "yes" "$(pv id_poly_diff3_iszero)" # ------------------------------------------------------------ factorisation --- # A DIFFERENCE THAT CANCELS TO ZERO IS THE WEAKER STATEMENT -- expansion alone # would produce it. These four ask for the factorisation itself. check_eq $P/factor-diff-squares "(x - 1)*(x + 1)" "$(pv factor_diff_squares)" check_eq $P/factor-cubic "(x - 1)*(x**2 + x + 1)" "$(pv factor_cubic)" check_eq $P/factor-quadratic "(x + 2)*(x + 3)" "$(pv factor_quadratic)" check_eq $P/factor-roundtrip-iszero "yes" "$(pv factor_roundtrip_iszero)" # -------------------------------------------------------------- derivatives --- check_eq $P/diff-poly "3*x**2" "$(pv diff_poly)" check_eq $P/diff-sin "cos(x)" "$(pv diff_sin)" check_eq $P/diff-exp "exp(x)" "$(pv diff_exp)" check_eq $P/diff-chain "2*x*cos(x**2)" "$(pv diff_chain)" check_eq $P/diff-product "x*exp(x) + exp(x)" "$(pv diff_product)" check_eq $P/diff-quotient "(x*cos(x) - sin(x))/x**2" "$(pv diff_quotient)" check_eq $P/diff-second "-sin(x)" "$(pv diff_second)" check_eq $P/diff-partial "6*x*y**2" "$(pv diff_partial)" # ---------------------------------------------------------------- integrals --- check_eq $P/int-poly "x**3/3" "$(pv int_poly)" check_eq $P/int-sin "-cos(x)" "$(pv int_sin)" check_eq $P/int-exp "exp(x)" "$(pv int_exp)" check_eq $P/int-recip "log(x)" "$(pv int_recip)" check_eq $P/int-definite-poly "1/3" "$(pv int_definite_poly)" check_eq $P/int-definite-sin "2" "$(pv int_definite_sin)" # THE ONE IMPROPER INTEGRAL, and the case that most exercises the machinery: a # closed form over an infinite interval whose answer is a symbolic sqrt(pi) # rather than a float. It is also the slowest case in the probe. check_eq $P/int-gaussian "sqrt(pi)" "$(pv int_gaussian)" # THE FUNDAMENTAL THEOREM, AS AN EXACT ZERO. Differentiating an antiderivative # returns the integrand, which is a stronger statement about both operations # than either of them measured alone. check_eq $P/int-diff-inverse-iszero "yes" "$(pv int_diff_inverse_iszero)" # -------------------------------------------------------------------- solve --- # THE TWO DOCUMENTED ROOTS, IN ORDER. `solve' returns them sorted for these # inputs, so the joined string is deterministic. check_eq $P/solve-quadratic "2,3" "$(pv solve_quadratic)" check_eq $P/solve-quadratic-count "2" "$(pv solve_quadratic_count)" check_eq $P/solve-irrational "-sqrt(2),sqrt(2)" "$(pv solve_irrational)" check_eq $P/solve-complex "-I,I" "$(pv solve_complex)" # A DOUBLE ROOT IS RETURNED ONCE, not twice: `solve' answers with the solution # SET, not with multiplicity. That is the shape a caller most often gets wrong. check_eq $P/solve-repeated "1" "$(pv solve_repeated)" check_eq $P/solve-linear "4" "$(pv solve_linear)" # THE ROOTS ARE EXACT, not merely printed exactly: substituting one back gives # the integer zero rather than something that rounds to it. check_eq $P/solve-root-substitutes-iszero "yes" \ "$(pv solve_root_substitutes_iszero)" # ---------------------------------------------------------------- rationals --- # EACH EXACT CASE IS PINNED BESIDE THE FLOAT THAT IS NOT, so the assertion reads # as the comparison it is rather than as a bare `yes'. check_eq $P/rat-sum "1/2" "$(pv rat_sum)" check_eq $P/rat-third-times-three "1" "$(pv rat_third_times_three)" check_eq $P/rat-tenths-exact "yes" "$(pv rat_tenths_exact)" check_eq $P/float-tenths-exact "no" "$(pv float_tenths_exact)" check_eq $P/rat-thirds-cancel-iszero "yes" "$(pv rat_thirds_cancel_iszero)" # THE FLOAT CONTROL THAT HAPPENS TO BE TRUE, and it is pinned as measured rather # than as the story would prefer: `(1/3)*3 - 1' IS exactly 0.0 in IEEE double, # because the two rounding errors cancel. Asserting `no' here would have been a # tidier narrative and a wrong expectation; the tenths case above is the one # where float arithmetic actually fails. check_eq $P/float-thirds-cancel-zero "yes" "$(pv float_thirds_cancel_zero)" check_eq $P/rat-type "Rational" "$(pv rat_type)" # NORMALISED AT CONSTRUCTION, and the sign carried on the numerator. check_eq $P/rat-normalised "1/2" "$(pv rat_normalised)" check_eq $P/rat-negative-denominator "-1/2" "$(pv rat_negative_denominator)" # ---------------------------------------------------- fifty digits, via mpmath --- # THE CASE THAT CANNOT PASS UNLESS BOTH PACKAGES ARE PRESENT AND CORRECT. The # digits are produced by mpmath's libmp through sympy's evalf; the sibling port # asserts the same fifty. check_eq $P/n-pi-50 "3.1415926535897932384626433832795028841971693993751" \ "$(pv n_pi_50)" check_eq $P/n-pi-50-len "50" "$(pv n_pi_50_len)" check_eq $P/n-e-50 "2.7182818284590452353602874713526624977572470937000" \ "$(pv n_e_50)" check_eq $P/n-sqrt2-50 "1.4142135623730950488016887242096980785696718753769" \ "$(pv n_sqrt2_50)" # THE SAME REQUEST TWICE IS THE SAME STRING. An evalf leaking precision state # between calls would show up here and nowhere else. check_eq $P/n-pi-50-stable "yes" "$(pv n_pi_50_stable)" check_eq $P/n-pi-15 "3.14159265358979" "$(pv n_pi_15)" # ------------------------------------------------------- sympify and srepr --- # THE ROUND TRIP OVER A COMMITTED EXPRESSION STRING. `srepr' is sympy's lossless # form -- `str' is not, because it drops assumptions -- so this is the pair that # says an expression survives being written down and read back. check_eq $P/sympify-str "Integral(sin(x)/x, (x, 0, pi))" "$(pv sympify_str)" check_eq $P/srepr-head "Integral" "$(pv srepr_head)" check_eq $P/srepr-roundtrip-equal "yes" "$(pv srepr_roundtrip_equal)" check_eq $P/srepr-roundtrip-iszero "yes" "$(pv srepr_roundtrip_iszero)" # A PARSED INTEGER IS A sympy Integer, NOT A PYTHON int, and `1/3' parses to an # exact Rational rather than to a float. Both are the difference between sympy's # parser and `eval'. check_eq $P/sympify-int-type "Integer" "$(pv sympify_int_type)" check_eq $P/sympify-rational "1/3" "$(pv sympify_rational)" check_eq $P/sympify-rational-type "Rational" "$(pv sympify_rational_type)" # --------------------------------------------------------------- error paths --- # EACH RAISES THE DOCUMENTED EXCEPTION RATHER THAN HANGING. The probe's bound is # what would catch a hang; these are what catch the other failure -- a silent # wrong answer where an error was owed. The TYPE is asserted and the message is # not: sympy's messages carry the offending expression and are not stable across # releases, while the type is the contract. check_eq $P/err-unparseable "" "$(pv err_unparseable)" check_eq $P/err-unbalanced "" "$(pv err_unbalanced)" check_eq $P/err-no-closed-form "" "$(pv err_no_closed_form)" check_eq $P/err-bad-symbol "" "$(pv err_bad_symbol)" check_eq $P/err-python-zero-division "" \ "$(pv err_python_zero_division)" # ------------------------------------- the zero denominator does NOT raise --- # THE TICKET ASKED FOR AN EXCEPTION HERE AND THERE IS NONE -- see the header. # sympy's arithmetic is projective, so a zero denominator is a VALUE. Scored by # TYPE as well as by printed form, because `zoo' and `nan' are singletons rather # than strings, and a release that started raising would red all four. check_eq $P/rational-zero-denominator "zoo" "$(pv rational_zero_denominator)" check_eq $P/rational-zero-denominator-type "ComplexInfinity" \ "$(pv rational_zero_denominator_type)" check_eq $P/rational-zero-over-zero "nan" "$(pv rational_zero_over_zero)" check_eq $P/rational-zero-over-zero-type "NaN" \ "$(pv rational_zero_over_zero_type)" check_eq $P/sympy-one-over-zero "zoo" "$(pv sympy_one_over_zero)" # AND AN EQUATION WITH NO SOLUTION RETURNS AN EMPTY LIST rather than raising -- # which is why `err-no-closed-form' above is worth pinning separately. check_eq $P/no-solution-empty-list "[]" "$(pv no_solution_empty_list)" rm -rf "$W" 2>/dev/null