. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, and they are the whole cost. Every # # assertion below either reads the filesystem/dpkg with # # shell builtins or greps the output of one of those three. # # # # They are a bare `import', a second one whose STDERR is # # the assertion (`import-quiet'), and the probe -- which # # takes ONE start for a hundred and forty-five # # measurements. The work inside the probe is arithmetic # # over a few dozen floats, microseconds beside an # # interpreter start on this arm, which is tens of seconds # # before the first line of the program runs. # # # # 1200 is 400s per start, the same budget 99-python3-parse # # and 99-python3-aniso8601 set for their three, and roughly # # six times the ~60s a python3 start has measured at here. # # # # A suite-timeout red is a batch-wide cost, so if the three # # starts prove slower than that on target the RIGHT fix is # # this number, not fewer cases. # # # # 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. # # python3-jdcal 1.4.1 (meta-openembedded/meta-python # recipes-devtools/python/python3-jdcal_1.4.1.bb, `inherit pypi setuptools3 # ptest', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # Conversion between Julian dates and calendar dates, in BOTH the Gregorian and # the Julian calendars, and in both directions: `gcal2jd'/`jd2gcal' and # `jcal2jd'/`jd2jcal'. Plus `is_leap' and the `ipart' helper. Six public # callables, 440 lines of Python, and ONE import -- `math'. # # Both calendars are PROLEPTIC in both directions: the functions run straight # through 1582 without the ten-day reform gap and without refusing a date that # never historically existed. That is stated by the docstring and it is the # premise of the whole reform group below. # # ============================================================================ # THIS IS PURE ARITHMETIC ON NUMBERS -- NO CLOCK, NO TIMEZONE, NO LOCALE. # ============================================================================ # # Nothing in this module reads a clock, a zone, an environment variable or a # locale, and nothing it returns depends on when the suite runs. Every instant # it converts is one the caller supplied. That is what makes the suite # reproducible, and it is also why the FLOAT handling is the part worth # checking rather than assuming: with the clock removed, the only thing left # that could behave differently on a new arm is the double arithmetic. # # ============================================================================ # THE FRACTIONAL DAY IS COMPARED EXACTLY, AND THAT IS THE POINT OF THIS SUITE. # ============================================================================ # # `jd2gcal' returns `(y, m, d, f)' where `f' is the fraction of a day, computed # as float arithmetic over `math.modf'. Fractions that are not representable in # binary come back carrying their full error -- upstream's own docstring # records `jd2gcal(2400000.5, 51544.245)' as `0.245000000002...' and not # `0.245'. # # Every `f-*' case below asserts the `repr' of that double, which in Python 3 is # the shortest string that round-trips to the same value -- so two doubles # differing in the last bit have different reprs. A `%.6f' comparison would # hide exactly the difference these cases exist to catch. If this image's # wasm32 doubles ever rounded differently from an x86-64 control, it would show # HERE and nowhere else in this suite. # # ============================================================================ # `is_leap' DOES NOT RETURN A bool, AND THAT IS THE PORT'S MOST USEFUL FINDING. # ============================================================================ # # `return not x and (y or not z)' over three `math.fmod' results, which are # FLOATS. `and'/`or' evaluate to one of their operands rather than to a bool, # so for a year divisible by 4 but not by 100 the function returns `y' itself: # # is_leap(2012) -> 12.0 (a float, not True) # is_leap(2000) -> True (`y' is 0.0, so the `or' falls through) # is_leap(2011) -> False (the `not x' arm) # # It is correctly TRUTHY in every case -- `if is_leap(y)' is right, and # `leap-disagreements' below scores it against the interpreter's own # `calendar.isleap' over six centuries and expects zero. But # `is_leap(2012) is True' is False, `json.dumps' writes `12.0', and # `assertEqual(is_leap(2012), True)' fails. Three cases assert the TYPE by name # for the three shapes, because the value alone cannot say it. # # ============================================================================ # THE REFORM IDENTITY IS OFF BY ONE FROM THE ONE A READER REACHES FOR. # ============================================================================ # # The reform made Thursday 1582-10-04 (Julian) the day BEFORE Friday 1582-10-15 # (Gregorian) -- the ten days between were skipped, so those two labels are # CONSECUTIVE days, not the same day. So: # # jcal2jd(1582,10,4) == gcal2jd(1582,10,15) is FALSE # jcal2jd(1582,10,5) == gcal2jd(1582,10,15) is TRUE # # `1582-10-05' Julian never historically occurred; it is available here only # because the functions are proleptic. Both the true identity and the false one # are asserted, so a release that changed the convention cannot slip past by # making one of them pass for a new reason. # # This was written the wrong way round first and the probe is what caught it -- # recorded here because a reader checking the suite against a history book will # reach for the naive identity too. # # ============================================================================ # THERE IS NO INPUT VALIDATION ANYWHERE IN THIS MODULE. # ============================================================================ # # Out-of-range and negative months and days are NORMALISED rather than refused: # `gcal2jd(2000,2,30)' is the 1st of March and `gcal2jd(2000,-2,-4)' is # 1999-09-26. A caller who passes a bad date expecting a `ValueError' gets a # DIFFERENT DATE instead, silently. That is this package's most dangerous # behaviour and it gets five cases. # # The same absence points the other way on types: `gcal2jd' opens with # `int(year)', and `int' takes a numeric string, so `gcal2jd("2000","1","1")' # is not an error but the right answer. A NON-numeric string is a `ValueError' # from inside `int()', while every other bad type is a `TypeError' from the # arithmetic -- a different `except' clause, so both are asserted. # # ============================================================================ # UPSTREAM'S OWN TEST SUITE IS NOT ON THIS IMAGE, AND NOT BECAUSE OF THIS PORT. # ============================================================================ # # The recipe does `inherit ptest' and carries a `do_install_ptest' that copies # `test_jdcal.py' to `${PTEST_PATH}'. But this distro's DISTRO_FEATURES is # `ipv4 x11 wayland' with no `ptest', so `ptest.bbclass' never creates the # `${PN}-ptest' package and nothing is staged. # # The other route is closed too: `setup.py' uses `py_modules=["jdcal"]', so # `test_jdcal.py' is not part of the distribution and never lands in # site-packages -- unlike python3-aniso8601 beside it, whose bare # `find_packages()' ships nine unittest files into the runtime .deb. # # So BOTH routes to upstream's 127-line test file are closed on this image, the # suite asserts that absence rather than leaving it to be discovered, and the # hand-written cases below stand in its place. This is the same shape already # recorded for python3-pytz in this directory. # # ============================================================================ # WHAT IS MEASURED AND WHAT IS PREDICTED. # ============================================================================ # # EVERY expected value in this file was MEASURED on an x86-64 control (CPython # 3.12.3, the same minor this image ships) against the tree unpacked from the # sdist the SRC_URI sha256 pins -- the sha256 was verified to match the recipe. # # NOTHING here has been measured on wasm32-hwjs: this port's first on-target # run is the batch verify. The expectation that the two agree is a PREDICTION, # and the `f-*' group is precisely where it could fail. A red there is a # genuine finding about this arm's doubles and not a typo in a fixture. P=python3-jdcal # THE CANARY, which asserts nothing about python3-jdcal. # # A suite's FIRST BST result line is lost between the guest's printf and the # host's read -- per-suite, deterministic, and NOT a defect in the host tool # (BRINTOS-1149 established that and filed it as linux-in-the-browser#177). The # harness computes its pass count as total-minus-failures, so THE DROPPED LINE # IS COUNTED AS A PASS. A nothing-case therefore stands in that position and the # eaten line costs nothing. It sits ahead of the `require' guards deliberately, # because a `require' that skips would emit its skip as the first line and be # eaten too. # # Remove this case when linux-in-the-browser#177 is fixed. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages MOD=$SITE/jdcal.py DI=$SITE/jdcal-1.4.1.dist-info PKGLIST=/var/lib/dpkg/info/python3-jdcal.list PROBE=$BST_DATA/python3-jdcal-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-jdcal.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- the payload --- # Where the package has to land. `import jdcal' further down is the functional # statement; these are the ones that say WHICH copy answered, so a package that # imported out of some other path on sys.path is distinguishable from one that # imported out of what this image installed. check $P/site-packages-dir test -d "$SITE" # A SINGLE MODULE, NOT A DIRECTORY. setup.py uses `py_modules=["jdcal"]', so # site-packages gets one `jdcal.py' -- the opposite shape from # python3-aniso8601's package tree beside it. This is the case that fails a # release which grew a package, loudly and here, rather than nowhere. check $P/module-file test -s "$MOD" check $P/no-package-dir test ! -d "$SITE/jdcal" # The precompiled bytecode, which is what `import-quiet' below turns on. check $P/pycache test -s "$SITE/__pycache__/jdcal.cpython-312.pyc" # NO SHIPPED SCRIPT. This package is a library and installs no executable. # Asserted so a release that grew a console_scripts entry point announces # itself here. check $P/no-cli test ! -e /usr/bin/jdcal # NO COMPILED EXTENSION ANYWHERE THE PACKAGE PUT ANYTHING. On this image a .so # could not be loaded at all -- python3 is ONE STATIC BINARY with an empty # lib-dynload and no dynamic loader (BRINTOS-1476) -- so this is the case that # fails a version bump which grew one, at install rather than at `import'. # # The list is copied to tmpfs first and never redirected from the rootfs: a # top-level stdin redirect on the virtiofs root makes dup2(2) return 513 and ash # EXITS, ending the suite silently (brintos/linux#73). 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)" # EVERY REGULAR FILE THE PACKAGE SHIPS IS PYTHON OR ITS METADATA. The case # above names the failure that is expected; this one catches the shape nobody # predicted -- a binary, a .pyd, a stray data file -- without having to # enumerate what it might be. # # DIRECTORIES ARE SKIPPED BY ASKING THE FILESYSTEM, not by pattern. A dpkg # .list carries every path the package owns, directories included and WITHOUT a # trailing slash -- `/usr/lib/python3.12/site-packages/__pycache__' and # `.../jdcal-1.4.1.dist-info' among them. A regex that excluded a trailing `/' # therefore let both through and reported this package's own directories as # stray files; `test -d' is what actually answers the question. # # The loop reads from the tmpfs copy staged above and NOT from the rootfs list: # a top-level `while ... done < ' makes dup2(2) return 513 on the # virtiofs root and ash EXITS, ending the suite silently # (brintos/linux#73). Reading a $TMPDIR copy is the shape # check-suite-redirects.py names as the remedy, and the loop is at the top level # so `_bad' survives it. # The metadata directory is version-stamped (jdcal-1.4.1.dist-info), never a # bare dist-info, so the glob anchors on the suffix. _bad= while IFS= read -r _p; do [ -d "$_p" ] && continue case "$_p" in *.py|*.pyc) continue ;; *.dist-info/*) continue ;; esac _bad="$_bad $_p" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # ---------------------------------------------------------- the metadata --- check $P/dist-info-dir test -d "$DI" check_out $P/dist-info-version "Version: 1.4.1" cat "$DI/METADATA" check_out $P/dist-info-name "Name: jdcal" cat "$DI/METADATA" # A PURE-PYTHON WHEEL. `Root-Is-Purelib: true' is the packaging-side statement # of the same fact `pkg-no-so' asserts through the file list. check_out $P/dist-info-summary \ "Summary: Julian dates from proleptic Gregorian and Julian calendars." \ cat "$DI/METADATA" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-top-level "jdcal" cat "$DI/top_level.txt" # ------------------------------------------------------- the dpkg status --- dpkg -s python3-jdcal > "$W/st" 2>/dev/null || : > "$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" # THE DEPENDS LINE IS `python3-core' AND NOTHING ELSE, WHICH IS THIS PORT'S # PACKAGING FINDING. # # The upstream recipe declares four splits -- python3-compression, python3-io, # python3-pprint, python3-shell -- and jdcal.py imports NONE of them. Its only # import is `math', which poky's python3-manifest.json puts in `core'. The # bbappend removes all four; see it for the refusal measurement behind that. # # Asserted positively AND negatively, because the removal is the change and a # release that re-acquired one of them must red here. check_out $P/pkg-depends-core "python3-core" cat "$W/dep" check_eq $P/pkg-depends-no-compression "" "$(grep -o 'python3-compression' "$W/dep")" check_eq $P/pkg-depends-no-io "" "$(grep -o 'python3-io' "$W/dep")" check_eq $P/pkg-depends-no-pprint "" "$(grep -o 'python3-pprint' "$W/dep")" check_eq $P/pkg-depends-no-shell "" "$(grep -o 'python3-shell' "$W/dep")" # NO -ptest PACKAGE AND NO STAGED TEST FILE -- see the header. The recipe # inherits `ptest' and this distro has no `ptest' DISTRO_FEATURE, so # ptest.bbclass never creates the split. Asserted so that a distro change which # turned the feature on is NOTICED here rather than silently doubling this # package's footprint. check_eq $P/no-ptest-package "" \ "$(dpkg -s python3-jdcal-ptest 2>/dev/null | grep '^Status: install ok installed')" check $P/no-ptest-dir test ! -d /usr/lib/python3-jdcal/ptest # ...and the second closed route: `py_modules' keeps test_jdcal.py out of the # distribution, so it is not in site-packages either. check $P/no-test-file-in-site test ! -e "$SITE/test_jdcal.py" # ------------------------------------------------------------- the import --- check $P/import python3 -c 'import jdcal' # THE IMPORT IS SILENT. A bytecode mismatch, a SyntaxWarning or a # DeprecationWarning would all still let the import SUCCEED while printing to # stderr, so the case above cannot see them. This one asserts the stderr is # empty, which is what makes a python3 minor bump that changed the magic, or a # missing __pycache__, visible here. check_eq $P/import-quiet "" \ "$(python3 -c 'import jdcal' 2>&1)" # ------------------------------------------------------------- the probe --- # ONE interpreter start for every measurement below; see the header for why. # The bound is generous because a python3 start on this arm is tens of seconds # and the arithmetic inside the probe is free beside it. check $P/probe-present test -s "$PROBE" bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } # THE PROBE EITHER RAN OR EVERY CASE BELOW IS UNSCORABLE. Without this guard a # probe that never started emits 140-odd reds all reading got:'' -- which looks # like a broken package and is actually a broken interpreter start. if ! test -s "$OUT"; then bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" else bst_ok $P/probe-output fi # ...and that it ran to the END, so a probe killed half way through is # distinguishable from one whose values disagree. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "145" "$(pv emitted_count)" # ----------------------------------------------------------- the identity --- # WHICH COPY ANSWERED. If this ever names the probe's own directory instead of # site-packages, the probe has shadowed the module it is measuring -- see the # probe's header for why it is not called jdcal.py. check_eq $P/module-answered "$MOD" "$(pv module_file)" check_eq $P/is-module "no" "$(pv is_package)" check_eq $P/version-attr "1.4.1" "$(pv version_attr)" # The version in the module and the version in the metadata are the same # string. setup.py reads `jdcal.__version__' to build the distribution, so a # disagreement means the .py and the dist-info came from different trees. check_eq $P/version-agrees "1.4.1" "$(pv version_attr)" # The two module-level constants. `MJD_0' is the first element of EVERY # converter return, so pinning it makes a change there a one-case red rather # than a spray across the whole corpus. check_eq $P/const-mjd-0 "2400000.5" "$(pv mjd_0)" check_eq $P/const-mjd-jd2000 "51544.5" "$(pv mjd_jd2000)" # ---------------------------------------------------------------- the API --- # All six public callables reachable at the module root, asserted before # anything about what they return. check_eq $P/api-ipart "yes" "$(pv has_ipart)" check_eq $P/api-is-leap "yes" "$(pv has_is_leap)" check_eq $P/api-gcal2jd "yes" "$(pv has_gcal2jd)" check_eq $P/api-jd2gcal "yes" "$(pv has_jd2gcal)" check_eq $P/api-jcal2jd "yes" "$(pv has_jcal2jd)" check_eq $P/api-jd2jcal "yes" "$(pv has_jd2jcal)" # NO `__all__', so `from jdcal import *' takes the module's whole public surface # INCLUDING the `math' it imported. Harmless, and the one namespace surprise # here, so it is stated rather than left to be met. check_eq $P/api-no-all "no" "$(pv has_all_attr)" check_eq $P/api-star-leaks-math "yes" "$(pv star_exports_math)" # ------------------------------------------------ gcal2jd: the corpus --- # `jd1' is documented to be MJD_0 always and `jd2' is the MJD. Each case # asserts BOTH halves: the constant proves the documented shape, the MJD is the # measurement. # # Every value below was measured on the x86-64 control -- see the header. check_eq $P/gcal-2000-jd1 "2400000.5" "$(pv g_epoch_2000_jd1)" check_eq $P/gcal-2000-jd2 "51544.0" "$(pv g_epoch_2000_jd2)" # THE REFORM BOUNDARY, both sides, in the PROLEPTIC Gregorian these functions # implement: there is no ten-day gap here, so the 4th and the 15th of October # 1582 are 11 days apart and not 1. That is the whole reason the corpus spans # this date. check_eq $P/gcal-reform-oct04 "-100851.0" "$(pv g_reform_oct04_jd2)" check_eq $P/gcal-reform-oct15 "-100840.0" "$(pv g_reform_oct15_jd2)" check_eq $P/gcal-reform-gap "11.0" "$(pv g_reform_gap)" # Leap days: one in a divisible-by-400 century year, one in an ordinary leap # year. check_eq $P/gcal-2000-feb29 "51603.0" "$(pv g_leap_2000_feb29_jd2)" check_eq $P/gcal-2012-feb29 "55986.0" "$(pv g_leap_2012_feb29_jd2)" # FAR FROM THE EPOCH IN BOTH DIRECTIONS, which is what exercises the two-part # representation the ticket asks about: the MJD half runs past 2**21 here, and # the pair carries bits that the single sum jd1+jd2 could not. check_eq $P/gcal-m4699 "-2395215.0" "$(pv g_far_past_m4699_jd2)" check_eq $P/gcal-m1999 "-1408776.0" "$(pv g_far_past_m1999_jd2)" check_eq $P/gcal-year-0 "-678774.0" "$(pv g_year_zero_jd2)" check_eq $P/gcal-year-1 "-678368.0" "$(pv g_year_one_jd2)" check_eq $P/gcal-2245 "141393.0" "$(pv g_far_future_2245_jd2)" check_eq $P/gcal-1678 "-65812.0" "$(pv g_year_1678_jd2)" check_eq $P/gcal-1950 "33282.0" "$(pv g_year_1950_jd2)" # ------------------------------------------- the round trip, every case --- # `' means jd2gcal(*gcal2jd(y,m,d)) == (y,m,d,0.0) exactly. A red here # prints the tuple that came back instead, so it NAMES the drift rather than # only denying the equality. check_eq $P/rt-2000 "" "$(pv rt_epoch_2000)" check_eq $P/rt-reform-04 "" "$(pv rt_reform_oct04)" check_eq $P/rt-reform-15 "" "$(pv rt_reform_oct15)" check_eq $P/rt-2000-feb29 "" "$(pv rt_leap_2000_feb29)" check_eq $P/rt-2012-feb29 "" "$(pv rt_leap_2012_feb29)" check_eq $P/rt-m4699 "" "$(pv rt_far_past_m4699)" check_eq $P/rt-m1999 "" "$(pv rt_far_past_m1999)" check_eq $P/rt-year-0 "" "$(pv rt_year_zero)" check_eq $P/rt-year-1 "" "$(pv rt_year_one)" check_eq $P/rt-2245 "" "$(pv rt_far_future_2245)" check_eq $P/rt-1678 "" "$(pv rt_year_1678)" check_eq $P/rt-1950 "" "$(pv rt_year_1950)" # THE FIRST THREE FIELDS ARE `int' AND THE FOURTH IS `float'. Asserted by type # name because the first three come out of `int()' over floats and a release # that dropped the conversion would still compare EQUAL to the integers above. check_eq $P/rt-types "int,int,int,float" "$(pv rt_types)" # ---------------------------------------------- the fractional day, EXACTLY --- # The group the ticket asks for; see the header for why these are compared as # reprs and not approximately. Each is the fraction `jd2gcal' returns for the # 1st of January 2000 plus the given fraction of a day -- and the DATE is # asserted beside it, so a fraction that came back right on the wrong day is # not scored as a pass. check_eq $P/frac-midnight-date "2000-01-01" "$(pv f_midnight_date)" check_eq $P/frac-midnight "0.0" "$(pv f_midnight)" check_eq $P/frac-noon-date "2000-01-01" "$(pv f_noon_date)" check_eq $P/frac-noon "0.5" "$(pv f_noon)" # THE BINARY-EXACT ONES: a quarter, three quarters and an eighth are all sums # of negative powers of two, so they survive the arithmetic unchanged and their # reprs are short. check_eq $P/frac-quarter "0.25" "$(pv f_quarter)" check_eq $P/frac-three-quarter "0.75" "$(pv f_three_quarter)" check_eq $P/frac-eighth "0.125" "$(pv f_exact_eighth)" # THE INEXACT ONES, TO THE LAST BIT. These two are the cases this whole suite # is built around: `0.245' and `0.1' are not representable in binary, so the # fraction comes back carrying the accumulated error of the modf-and-add path, # and upstream's docstring records the same digits. # # A red on either of these -- and a GREEN on every other case in this file -- # is a real finding about this arm's double arithmetic, not a broken fixture. check_eq $P/frac-inexact-245 "0.24500000000261934" "$(pv f_inexact_245)" check_eq $P/frac-inexact-1 "0.09999999999854481" "$(pv f_inexact_1)" # ...and the property behind the two groups, stated as a yes/no so the suite # says WHICH claim it is making rather than leaving it to a reader comparing # digit strings. check_eq $P/frac-quarter-exact "yes" "$(pv f_quarter_is_exact)" check_eq $P/frac-245-inexact "no" "$(pv f_245_is_exact)" # ------------------------------------------ no validation: NORMALISATION --- # The dangerous behaviour, and it gets five cases. A bad date is not refused -- # it is silently converted into a DIFFERENT, valid one. check_eq $P/norm-negative "51447.0" "$(pv g_norm_neg)" check_eq $P/norm-negative-agrees "yes" "$(pv g_norm_neg_agrees)" check_eq $P/norm-feb30 "51604.0" "$(pv g_norm_feb30)" check_eq $P/norm-feb30-agrees "yes" "$(pv g_norm_feb30_agrees)" check_eq $P/norm-month-13-agrees "yes" "$(pv g_norm_month13_agrees)" # ---------------------------------------------------- jcal2jd / jd2jcal --- # The Julian-calendar half, over the same shape of corpus and with the same # round-trip assertion. check_eq $P/jcal-2000 "51557.0" "$(pv j_epoch_2000_jd2)" check_eq $P/jcal-reform-oct04 "-100841.0" "$(pv j_reform_oct04_jd2)" check_eq $P/jcal-1900-feb29 "15091.0" "$(pv j_leap_1900_jd2)" check_eq $P/jcal-year-1 "-678577.0" "$(pv j_year_one_jd2)" check_eq $P/jcal-m4712 "-2400001.0" "$(pv j_far_past_m4712_jd2)" check_eq $P/jcal-1950 "33295.0" "$(pv j_year_1950_jd2)" check_eq $P/jcal-jd1-is-mjd0 "2400000.5" "$(pv j_epoch_2000_jd1)" check_eq $P/jrt-2000 "" "$(pv jrt_epoch_2000)" check_eq $P/jrt-reform-04 "" "$(pv jrt_reform_oct04)" check_eq $P/jrt-1900-feb29 "" "$(pv jrt_leap_1900)" check_eq $P/jrt-year-1 "" "$(pv jrt_year_one)" check_eq $P/jrt-m4712 "" "$(pv jrt_far_past_m4712)" check_eq $P/jrt-1950 "" "$(pv jrt_year_1950)" # ------------------------------------------- the two calendars, together --- # See the header: the identity a reader reaches for is the FALSE one. Both are # asserted so a convention change cannot make the true one pass for a new # reason. check_eq $P/reform-identity "yes" "$(pv reform_identity)" check_eq $P/reform-naive-identity "no" "$(pv reform_naive_identity)" # The two labels name consecutive days -- one apart, which is what the pair # above encodes. check_eq $P/reform-consecutive "1.0" "$(pv reform_consecutive)" # ...and the standing offset between the calendars in 1582, measured on the # SAME label in each: ten days, which is the quantity the reform skipped. check_eq $P/reform-offset "-10.0" "$(pv reform_offset_days)" # THE CENTURY YEAR THE TWO CALENDARS DISAGREE ABOUT, taken through both. 1900 # IS a leap year in the Julian calendar and is NOT in the Gregorian, so # 1900-02-29 round-trips unchanged on the Julian side and NORMALISES to the 1st # of March on the Gregorian one. This is the ticket's "century cases where the # two calendars disagree", asserted through the converters rather than only # through `is_leap'. check_eq $P/julian-1900-feb29-exists "(1900, 2, 29)" "$(pv jul_1900_feb29_rt)" check_eq $P/greg-1900-feb29-normalises "(1900, 3, 1)" "$(pv greg_1900_feb29_normalises)" # ---------------------------------------------------------------- is_leap --- # The Gregorian rule, over the four turn-of-century years where it differs from # the Julian one, plus a divisible-by-400 pair and three ordinary years. check_eq $P/leap-1600 "yes" "$(pv leap_1600)" check_eq $P/leap-1700 "no" "$(pv leap_1700)" check_eq $P/leap-1800 "no" "$(pv leap_1800)" check_eq $P/leap-1900 "no" "$(pv leap_1900)" check_eq $P/leap-2000 "yes" "$(pv leap_2000)" check_eq $P/leap-2100 "no" "$(pv leap_2100)" check_eq $P/leap-2400 "yes" "$(pv leap_2400)" check_eq $P/leap-2012 "yes" "$(pv leap_2012)" check_eq $P/leap-2011 "no" "$(pv leap_2011)" check_eq $P/leap-1996 "yes" "$(pv leap_1996)" # THE RETURN TYPE IS NOT `bool' -- see the header. Three shapes, each asserted # by type name AND by repr, because the value alone cannot say it: `12.0' and # `True' are both truthy and neither `==' nor a shell string compare on the # yes/no above could tell them apart. check_eq $P/leap-2012-repr "12.0" "$(pv leap_2012_repr)" check_eq $P/leap-2012-type "float" "$(pv leap_2012_type)" check_eq $P/leap-2012-not-true "no" "$(pv leap_2012_is_true)" check_eq $P/leap-2000-repr "True" "$(pv leap_2000_repr)" check_eq $P/leap-2000-type "bool" "$(pv leap_2000_type)" check_eq $P/leap-2011-repr "False" "$(pv leap_2011_repr)" check_eq $P/leap-2011-type "bool" "$(pv leap_2011_type)" check_eq $P/leap-1900-repr "False" "$(pv leap_1900_repr)" check_eq $P/leap-1900-type "bool" "$(pv leap_1900_type)" # ...AND IT IS NEVERTHELESS CORRECT. Scored against the interpreter's own # `calendar.isleap' over six centuries, as a COUNT of disagreements rather than # as 600 cases, so the group above reads as "the type is odd" and not as "the # answer is wrong". check_eq $P/leap-disagreements "0" "$(pv leap_disagreements)" check_eq $P/leap-first-bad "" "$(pv leap_first_disagreement)" # ------------------------------------------------------------------ ipart --- # The documented helper, and the one place `math.modf' shows in the API. # NEGATIVE VALUES TRUNCATE TOWARD ZERO rather than flooring -- `ipart(-3.7)' is # `-3.0' and not `-4.0', which is not what a caller reaching for `//' would # get. It returns a FLOAT, not an int. check_eq $P/ipart-pos "3.0" "$(pv ipart_pos)" check_eq $P/ipart-neg "-3.0" "$(pv ipart_neg)" check_eq $P/ipart-zero "0.0" "$(pv ipart_zero)" check_eq $P/ipart-type "float" "$(pv ipart_type)" # ------------------------------------------------------- the failure path --- # Asserted by exception TYPE rather than by message: the messages come from # `int()' and from the arithmetic and a CPython bump may reword any of them; # the TYPE is what a caller's `except' clause names. check_eq $P/err-leap-str "TypeError" "$(pv err_leap_str)" check_eq $P/err-leap-none "TypeError" "$(pv err_leap_none)" check_eq $P/err-gcal-none "TypeError" "$(pv err_gcal_none)" check_eq $P/err-gcal-arity "TypeError" "$(pv err_gcal_arity)" check_eq $P/err-jd2gcal-str "TypeError" "$(pv err_jd2gcal_str)" # THE ONE THAT IS NOT A TypeError. A non-numeric string dies inside `int()', # which raises `ValueError' -- a different `except' clause from every case # above, and the reason this is asserted separately rather than folded in. check_eq $P/err-gcal-badstr "ValueError" "$(pv err_gcal_badstr)" # A NUMERIC STRING IS ACCEPTED, not refused -- see the header. `int("2000")' is # 2000, so this is the right answer and not an error, and a reader who has just # read the group above will assume otherwise. check_eq $P/coerce-str-year "51544.0" "$(pv coerce_str_year)" check_eq $P/coerce-str-all "51544.0" "$(pv coerce_str_all)" check_eq $P/coerce-str-agrees "yes" "$(pv coerce_str_agrees)" # A FLOAT YEAR IS ACCEPTED AND TRUNCATED -- upstream's own docstring passes # 1678.0 in its example list, and it must give the same answer as the int. check_eq $P/float-year "-65812.0" "$(pv float_year_ok)" # ------------------------------------------------------------- the closure --- # NOTHING OUTSIDE THE STANDARD LIBRARY IS REACHED BY THE IMPORT. This image's # python3 is ONE STATIC BINARY with an empty lib-dynload and no dynamic loader # (BRINTOS-1476), so a release that grew a third-party dependency would fail at # `import' here rather than resolve it. # # Scored against `sys.stdlib_module_names' rather than against a literal list: # what the import delta CONTAINS depends on what the interpreter had already # loaded, so a literal here would red on target for a reason that has nothing # to do with jdcal. check_eq $P/pulled-nonstdlib "" "$(pv pulled_nonstdlib)" # The ONE stdlib module it needs, named positively. `math' is in the # python3-core split, which is why this port declares no RDEPENDS of its own. check_eq $P/needs-math "yes" "$(pv needs_math)" # ...AND THE FOUR SPLITS THE UPSTREAM RECIPE DECLARES, NAMED NEGATIVELY. This # is the runtime half of the bbappend's `RDEPENDS:${PN}:remove': the packaging # half is `pkg-depends-no-*' above, and this one says the import genuinely does # not reach them rather than merely that the .deb no longer says so. check_eq $P/unreached-gzip "yes" "$(pv unreached_gzip)" check_eq $P/unreached-tarfile "yes" "$(pv unreached_tarfile)" check_eq $P/unreached-zipfile "yes" "$(pv unreached_zipfile)" check_eq $P/unreached-socket "yes" "$(pv unreached_socket)" check_eq $P/unreached-ssl "yes" "$(pv unreached_ssl)" check_eq $P/unreached-tempfile "yes" "$(pv unreached_tempfile)" check_eq $P/unreached-pprint "yes" "$(pv unreached_pprint)" check_eq $P/unreached-cmd "yes" "$(pv unreached_cmd)" check_eq $P/unreached-glob "yes" "$(pv unreached_glob)" check_eq $P/unreached-shlex "yes" "$(pv unreached_shlex)" rm -rf "$W"