. "$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 twenty-nine measurements. The # # work inside the probe is calendar arithmetic and string # # parsing over inputs under 40 bytes, 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-aniso8601 # # and 99-python3-parse set for their three. # # # # 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-dateutil 2.9.0 (meta-openembedded/meta-python # recipes-devtools/python/python3-dateutil_2.9.0.bb, `inherit pypi # python_setuptools_build_meta', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE DOES. # ============================================================================ # # Four extensions to the standard `datetime' module, each behind its own # submodule and each with a failure mode the standard library does not have: # `relativedelta' (calendar arithmetic, where a month is a month and not 30 # days), `parser' (a permissive reader for date strings nobody specified), # `rrule' (the RFC 5545 recurrence engine), and `tz' (zoneinfo readers). It also # ships `easter' and `utils', which are asserted here so the port is not # recorded as smaller than it is. # # ============================================================================ # THE CLOCK IS AN INPUT, NEVER AN AMBIENT FACT. # ============================================================================ # # Every instant this suite scores is one the probe supplied. Nothing calls # `datetime.now()', `date.today()', `time.time()' or `tz.tzlocal()', and nothing # reads $TZ -- so no case moves with WHEN the suite runs, with the target's local # zone, or with any time source off the machine. # # That is a deliberate constraint rather than a convenience, and it costs # something: the two-digit-year pivot and `utils.today()' are both genuinely # clock-dependent, so they are scored for the property that survives fixing the # clock (`p-2digit-*' pins them against an explicit `default'; `now-type' asserts # the TYPE and never the value) rather than left out or left flapping. # # The one thing the suite does read off the machine is the IANA database, which # is data rather than a clock: `tz-*' resolves named zones through # /usr/share/zoneinfo, which tzdata puts on this image. Those answers are # historical facts about 2024 and do not move with a tzdata release. # # ============================================================================ # THE MONTH-END CLAMP IS THE WHOLE OF `relativedelta', AND IT IS LOSSY. # ============================================================================ # # `date(2024,1,31) + relativedelta(months=1)' is 29 February, not 2 March: the # day is CLAMPED to the target month's length rather than overflowed. In 2023 # the same expression is 28 February, so the answer depends on the year as well # as on the month. # # The clamp does not round-trip. Out one month and back is the 29th of January, # not the 31st -- `rd-roundtrip-lossy' -- which is the bug a caller writes when # it treats `+1 month' as invertible. # # It is also what makes the operation NON-COMMUTATIVE, and the suite measures # both sides of that rather than asserting the interesting one. From the 31st, # months-then-days and days-then-months AGREE (both 1 March), which is the case # a reader expects to differ; from the 30th they do NOT (1 March against 29 # February), which is the case a reader expects to agree. One day apart, the # opposite way round each time. # # SINGULAR SETS, PLURAL ADDS. `relativedelta(day=1)' sets the day of the month; # `relativedelta(days=1)' adds one. The two spellings differ by one character # and by a month of arithmetic, and `day=31' is itself clamped -- which is how # "the last day of this month" is written without computing the month's length. # # ============================================================================ # `rrule' AND `relativedelta' DISAGREE ABOUT 29 FEBRUARY, ON PURPOSE. # ============================================================================ # # `relativedelta(years=1)' on a leap day CLAMPS to 28 February # (`rd-feb29-plus-1y'). A yearly `rrule' pinned to 29 February SKIPS the three # years in four that do not have one, so its first three occurrences are 2024, # 2028 and 2032 (`rr-feb29-skips'). # # Neither is wrong. They answer different questions -- "the date one year on" # against "the dates matching this rule" -- and a caller who reaches for the # wrong one gets a plausible answer rather than an error. Both are scored here # so the pair is on the record together. # # ============================================================================ # `parse' GUESSES, AND THE DEFAULT GUESS IS AMERICAN. # ============================================================================ # # `03/04/2024' is the 4th of March, whatever the target's locale says -- # month-first is the default and there is no locale input to change it. The two # flags select the other readings, and the suite scores all four combinations # over `03/04/05' so the interaction is pinned rather than one flag at a time: # neither is 2005-03-04, `yearfirst' alone is 2003-04-05, `dayfirst' alone is # 2005-04-03 in principle, and BOTH is 2003-05-04. # # `isoparse' IS THE STRICT COUNTERPART and the pair is the point: # `iso-refuses-verbose' and `parse-accepts-verbose' run the same input through # both and score the disagreement, so the choice between them is documented by # measurement rather than by a docstring. # # THE FAILURE PATH IS ASSERTED BY EXCEPTION TYPE, never by message -- the # messages are English prose a release may reword; the TYPE is what a caller's # `except' clause names. Malformed input is `ParserError' at every entry point, # and `p-err-is-valueerror' pins the part a caller depends on without reading # the source: ParserError is a ValueError SUBCLASS, so `except ValueError' # catches it. A NON-STRING argument is a bare `TypeError' raised before any # parsing, which is the other branch. # # `p-err-feb30' and `p-feb29-leap-ok' are a pair: 2023-02-29 is refused and # 2024-02-29 is accepted, so the refusal is shown to be about the calendar # rather than about the syntax. # # ============================================================================ # EVERY `rrule' EXPANSION IS BOUNDED, AND COMPARED ELEMENT BY ELEMENT. # ============================================================================ # # An unbounded rrule is an infinite generator; materialising one would hang the # suite and cost every member of the batch the round. So every rule here carries # `count' or `until', and each expansion is scored against an enumerated list # joined on `|' rather than against a length -- a count alone would pass on a # rule that produced the right NUMBER of wrong instants. # # `until' IS INCLUSIVE of an instant landing exactly on it, and the pair # `rr-until-inclusive' / `rr-until-excludes-later' says so by moving the bound # one second earlier and losing the last occurrence. # # ============================================================================ # `gettz' READS THE SYSTEM DATABASE FIRST, AND FALLS THROUGH TO AN UNGUARDED # IMPORT. # ============================================================================ # # `tz.gettz("America/New_York")' resolves against TZPATHS -- `/usr/share/zoneinfo' # first, which tzdata puts on this image -- and `tz-gettz-reads-system' asserts # the resolved file came from there rather than from anywhere else. # # Only where TZPATHS MISSES does it fall through to # `from dateutil.zoneinfo import get_zonefile_instance', and that import has no # `except ImportError' around it. `dateutil/zoneinfo' is split into a separate # package (`PACKAGES =+ "${PN}-zoneinfo"', taking the whole directory including # `__init__.py'), so on an image carrying the base package ALONE the documented # `gettz() is None' becomes a ModuleNotFoundError -- the one call a # caller wraps in `if tz is None' is the one that raises. # # core-image-brintos-emcraft installs `python3-dateutil-zoneinfo' for that # reason, and `tz-unknown-zone' / `tz-zoneinfo-importable' are what hold it # there: remove the package from the image and both go red, naming the cause. # The bundled snapshot cannot shadow the system database -- it is consulted only # after TZPATHS has missed -- so its staleness is reachable only for zones this # image's tzdata does not carry. # # TWO ZONES, NOT ONE. America/New_York carries a DST transition and a whole-hour # offset; Asia/Kolkata carries a HALF-HOUR offset and no DST at all. Scoring one # country's politics twice would leave the half-hour arithmetic untested. # # THE TRANSITION INSTANTS ARE A FIXTURE TABLE. 2024's US spring-forward is 10 # March at 02:00 local and the fall-back is 3 November at 02:00, so the suite # straddles each with a pair of instants minutes apart and scores the offset on # both sides. It also scores the two cases a UTC offset cannot express: 01:30 on # the fall-back day happens TWICE (`fold' selects which, and the two folds carry # different offsets), and 02:30 on the spring-forward day does not happen at all. # # ============================================================================ # THE CLOSURE: `six' AND NOTHING ELSE. # ============================================================================ # # 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 compiled dependency # would fail at `import' here rather than resolve it. `pulled-nonstdlib' scores # the import delta against `sys.stdlib_module_names' rather than against a # literal list -- what the delta CONTAINS depends on what the interpreter had # already loaded -- and the answer is `six', which is the package's one # third-party runtime dependency and is on this image under BRINTOS-1744. # # UPSTREAM'S TEST SUITE IS NOT PACKAGED, and not by anything this port does: the # sdist keeps `tests/' at the TOP LEVEL, outside `src/dateutil', so there is no # `dateutil.tests' subpackage to ship. That is the opposite of python3-aniso8601 # beside it, whose bare `find_packages()' puts nine unittest files in the runtime # .deb. The absence is asserted rather than left to be discovered. P=python3-dateutil bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKGDIR=$SITE/dateutil DI=$SITE/python_dateutil-2.9.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-dateutil.list ZILIST=/var/lib/dpkg/info/python3-dateutil-zoneinfo.list PROBE=$BST_DATA/python3-dateutil-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-dateutil.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # --------------------------------------------------------------- on disk --- check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKGDIR" check $P/no-module-py test ! -f "$SITE/dateutil.py" check $P/init-file test -s "$PKGDIR/__init__.py" # The four subsystems the ticket names, each as its own file or package. check $P/relativedelta-py test -s "$PKGDIR/relativedelta.py" check $P/rrule-py test -s "$PKGDIR/rrule.py" check $P/parser-dir test -d "$PKGDIR/parser" check $P/parser-isoparser test -s "$PKGDIR/parser/isoparser.py" check $P/tz-dir test -d "$PKGDIR/tz" check $P/tz-tz-py test -s "$PKGDIR/tz/tz.py" check $P/easter-py test -s "$PKGDIR/easter.py" check $P/utils-py test -s "$PKGDIR/utils.py" check $P/pycache test -s "$PKGDIR/__pycache__/__init__.cpython-312.pyc" # The library ships no program. check $P/no-cli test ! -e /usr/bin/dateutil # THE `-zoneinfo' SPLIT, ON DISK. The subpackage carries the whole directory -- # `__init__.py' included -- so its absence is an ImportError rather than a # missing data file. See the header. check $P/zoneinfo-dir test -d "$PKGDIR/zoneinfo" check $P/zoneinfo-init test -s "$PKGDIR/zoneinfo/__init__.py" check $P/zoneinfo-tarball test -s "$PKGDIR/zoneinfo/dateutil-zoneinfo.tar.gz" # The system database, which is what `gettz' answers from first. check $P/system-zoneinfo test -d /usr/share/zoneinfo check $P/system-zone-ny test -s /usr/share/zoneinfo/America/New_York check $P/system-zone-kolkata test -s /usr/share/zoneinfo/Asia/Kolkata # ------------------------------------------------------------- packaging --- cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # PURE PYTHON: no shared object, and nothing that is not a .py, a .pyc or # dist-info metadata. A compiled extension could not load on this arm at all. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # The zoneinfo subpackage IS installed, and its one payload is the tarball -- # the only non-python file this port puts on the image. cat "$ZILIST" > "$W/zif" 2>/dev/null || : > "$W/zif" check $P/zoneinfo-pkg-installed test -s "$W/zif" check_out $P/zoneinfo-pkg-tarball "dateutil-zoneinfo.tar.gz" cat "$W/zif" # UPSTREAM'S TESTS ARE NOT HERE, and the sdist layout is why -- see the header. check $P/no-tests-subpackage test ! -d "$PKGDIR/tests" check_eq $P/pkg-no-test-files "" \ "$(grep -E '/dateutil/tests/' "$W/pkgf" 2>/dev/null)" # --------------------------------------------------------------- metadata --- check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-version "Version: 2.9.0" cat "$DI/METADATA" check_out $P/dist-info-name "Name: python-dateutil" cat "$DI/METADATA" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-top-level "dateutil" cat "$DI/top_level.txt" # ------------------------------------------------------------------ dpkg --- sed -n '/^Package: python3-dateutil$/,/^$/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" # THE DEPENDS LINE, PINNED BY NAME. The four the recipe declares, each a package # this image already carried before the port -- and `setuptools-scm', which is a # NATIVE build dependency and must NOT appear here. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" check_out $P/pkg-depends-core "python3-core" cat "$W/dep" check_out $P/pkg-depends-datetime "python3-datetime" cat "$W/dep" check_out $P/pkg-depends-numbers "python3-numbers" cat "$W/dep" check_out $P/pkg-depends-six "python3-six" cat "$W/dep" check_out $P/pkg-depends-stringold "python3-stringold" cat "$W/dep" check_eq $P/pkg-no-setuptools-scm "" \ "$(grep -E 'setuptools-scm' "$W/dep" 2>/dev/null)" # This distro's DISTRO_FEATURES has no `ptest', so no -ptest package exists. check_eq $P/no-ptest-package "" \ "$(sed -n '/^Package: python3-dateutil-ptest$/p' /var/lib/dpkg/status 2>/dev/null)" # ---------------------------------------------------------------- import --- check $P/import python3 -c 'import dateutil' # THE IMPORT IS SILENT. A DeprecationWarning or a failed optional import would # land on stderr and be invisible to the case above, which scores only status. check_eq $P/import-quiet "" \ "$(python3 -c 'import dateutil' 2>&1)" # The four subsystems import individually -- a package root that imports while a # submodule does not is the shape a bad split produces. check $P/import-relativedelta python3 -c 'import dateutil.relativedelta' check $P/import-parser python3 -c 'import dateutil.parser' check $P/import-rrule python3 -c 'import dateutil.rrule' check $P/import-tz python3 -c 'import dateutil.tz' check $P/import-zoneinfo python3 -c 'import dateutil.zoneinfo' # ----------------------------------------------------------------- probe --- 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" 900 python3 "$PROBE" OUT="$W/probe" # pv : the value the probe emitted for , or empty. 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 # The probe ran to its last line, and emitted every measurement it should have. # A short transcript would otherwise show up as a spray of empty-value reds # whose real cause is one truncation. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "129" "$(pv emitted_count)" # --------------------------------------------------------------- identity --- check_eq $P/module-answered "$PKGDIR/__init__.py" "$(pv module_file)" check_eq $P/is-package "yes" "$(pv is_package)" # The version, from the module and from the installed metadata, and then the two # against each other -- a package whose dist-info disagrees with its own # `__version__' is a mispackaged upgrade. check_eq $P/version-attr "2.9.0" "$(pv version_attr)" check_eq $P/dist-version "2.9.0" "$(pv dist_version)" check_eq $P/version-agrees "$(pv version_attr)" "$(pv dist_version)" # Every submodule reachable from the package root. check_eq $P/api-relativedelta "yes" "$(pv has_relativedelta)" check_eq $P/api-parser "yes" "$(pv has_parser)" check_eq $P/api-rrule "yes" "$(pv has_rrule)" check_eq $P/api-tz "yes" "$(pv has_tz)" check_eq $P/api-easter "yes" "$(pv has_easter)" check_eq $P/api-utils "yes" "$(pv has_utils)" check_eq $P/api-zoneinfo "yes" "$(pv has_zoneinfo)" # ---------------------------------------------------- relativedelta --------- # THE MONTH-END CLAMP, in a leap year and out of one: the same expression, two # different answers, neither of them 2 March. check_eq $P/rd-jan31-plus-1m-leap "2024-02-29" "$(pv rd_jan31_plus_1m_leap)" check_eq $P/rd-jan31-plus-1m-nonleap "2023-02-28" "$(pv rd_jan31_plus_1m_nonleap)" check_eq $P/rd-may31-plus-1m "2024-06-30" "$(pv rd_may31_plus_1m)" # ...and the clamp is LOSSY: out one month and back is the 29th, not the 31st. check_eq $P/rd-roundtrip-lossy "2024-01-29" "$(pv rd_roundtrip_lossy)" # A LEAP DAY clamps the same way on a year step, and lands exactly on the next # leap day at four. check_eq $P/rd-feb29-plus-1y "2025-02-28" "$(pv rd_feb29_plus_1y)" check_eq $P/rd-feb29-plus-4y "2028-02-29" "$(pv rd_feb29_plus_4y)" # ORDER OF APPLICATION. From the 31st the two orders AGREE; from the 30th they # do NOT. Both directions measured -- see the header. check_eq $P/rd-order-m-then-d "2024-03-01" "$(pv rd_order_m_then_d)" check_eq $P/rd-order-d-then-m "2024-03-01" "$(pv rd_order_d_then_m)" check_eq $P/rd-order-agrees-at-31 "$(pv rd_order_m_then_d)" "$(pv rd_order_d_then_m)" check_eq $P/rd-order2-m-then-d "2024-03-01" "$(pv rd_order2_m_then_d)" check_eq $P/rd-order2-d-then-m "2024-02-29" "$(pv rd_order2_d_then_m)" # One relativedelta carrying both applies MONTHS FIRST, so it agrees with the # months-first spelling and not with the days-first one. check_eq $P/rd-combined-m-and-d "2024-03-01" "$(pv rd_combined_m_and_d)" check_eq $P/rd-combined2-m-and-d "2024-03-01" "$(pv rd_combined2_m_and_d)" check_eq $P/rd-combined2-is-months-first "$(pv rd_order2_m_then_d)" "$(pv rd_combined2_m_and_d)" # SINGULAR SETS, PLURAL ADDS -- one character apart, a month of arithmetic apart. check_eq $P/rd-absolute-day "2024-01-01 13:45:30" "$(pv rd_absolute_day)" check_eq $P/rd-relative-days "2024-02-01 13:45:30" "$(pv rd_relative_days)" # `day=31' is itself clamped, which is how "last day of the month" is spelled. check_eq $P/rd-day31-in-feb "2024-02-29" "$(pv rd_day31_in_feb)" # WEEKDAY ARITHMETIC. MO(+1) is the next Monday ON OR AFTER, so on a Monday it # is a NO-OP -- the off-by-one a caller hits first. check_eq $P/rd-next-monday "2024-03-11" "$(pv rd_next_monday)" check_eq $P/rd-monday-is-noop "2024-03-04" "$(pv rd_monday_is_noop)" check_eq $P/rd-last-friday "2024-03-01" "$(pv rd_last_friday)" check_eq $P/rd-second-tuesday "2024-03-12" "$(pv rd_second_tuesday)" # THE DIFFERENCE OF TWO DATES in calendar units -- the question a timedelta # cannot answer -- and its round trip, which is the property that makes the # three fields mean anything together. check_eq $P/rd-diff-years "24" "$(pv rd_diff_years)" check_eq $P/rd-diff-months "7" "$(pv rd_diff_months)" check_eq $P/rd-diff-days "24" "$(pv rd_diff_days)" check_eq $P/rd-diff-roundtrip "2024-03-15" "$(pv rd_diff_roundtrip)" # NORMALIZATION pushes a fractional unit down to the next one. check_eq $P/rd-normalized "relativedelta(days=+1, hours=+12)" "$(pv rd_normalized)" check_eq $P/rd-times-two "2024-03-15" "$(pv rd_times_two)" check_eq $P/rd-negated "2024-02-29" "$(pv rd_negated)" # `weeks' is a VIEW over days, not a seventh field. check_eq $P/rd-weeks-are-days "14" "$(pv rd_weeks_are_days)" # ---------------------------------------------------------------- parser --- check_eq $P/p-iso-basic "2024-03-15T00:00:00" "$(pv p_iso_basic)" check_eq $P/p-iso-datetime "2024-03-15T13:45:30" "$(pv p_iso_datetime)" check_eq $P/p-iso-offset "2024-03-15T13:45:30+02:00" "$(pv p_iso_offset)" # `Z' becomes an explicit +00:00 offset rather than a naive datetime. check_eq $P/p-iso-z "2024-03-15T13:45:30+00:00" "$(pv p_iso_z)" check_eq $P/p-rfc2822 "2024-03-15T13:45:30+00:00" "$(pv p_rfc2822)" check_eq $P/p-verbose "2024-03-15T13:45:00" "$(pv p_verbose)" # AMBIGUITY: four readings of the same digits, all four scored. The default is # MONTH-FIRST whatever the target's locale says -- there is no locale input. check_eq $P/p-ambig-default "2024-03-04T00:00:00" "$(pv p_ambig_default)" check_eq $P/p-ambig-dayfirst "2024-04-03T00:00:00" "$(pv p_ambig_dayfirst)" check_eq $P/p-ambig-neither "2005-03-04T00:00:00" "$(pv p_ambig_neither)" check_eq $P/p-ambig-yearfirst "2003-04-05T00:00:00" "$(pv p_ambig_yearfirst)" check_eq $P/p-ambig-both "2003-05-04T00:00:00" "$(pv p_ambig_both)" # A TWO-DIGIT YEAR RESOLVES AGAINST `default', NOT AGAINST THE CLOCK -- which is # the only reason these two are scorable at all. See the header. check_eq $P/p-2digit-69 "2069" "$(pv p_2digit_69)" check_eq $P/p-2digit-70 "2070" "$(pv p_2digit_70)" # `default' SUPPLIES THE FIELDS THE STRING OMITS, which is what makes a partial # parse deterministic instead of clock-dependent. check_eq $P/p-default-fills "2024-03-15T08:00:00" "$(pv p_default_fills)" check_eq $P/p-default-time-only "2024-03-01T13:45:00" "$(pv p_default_time_only)" # FUZZY parsing pulls a date out of prose, and reports what it discarded. check_eq $P/p-fuzzy "2024-03-15T13:45:00" "$(pv p_fuzzy)" check_eq $P/p-fuzzy-tokens "('Today is ', ' ')" "$(pv p_fuzzy_tokens)" # `isoparse' IS THE STRICT ONE, and the disagreement is the point. check_eq $P/iso-strict-ok "2024-03-15T13:45:30" "$(pv iso_strict_ok)" check_eq $P/iso-week-date "2024-03-15T00:00:00" "$(pv iso_week_date)" check_eq $P/iso-ordinal "2024-03-15T00:00:00" "$(pv iso_ordinal)" check_eq $P/iso-refuses-verbose "ValueError" "$(pv iso_refuses_verbose)" check_eq $P/parse-accepts-verbose "2024-03-15T00:00:00" "$(pv parse_accepts_verbose)" # THE FAILURE PATH, BY EXCEPTION TYPE -- see the header. check_eq $P/p-err-garbage "ParserError" "$(pv p_err_garbage)" check_eq $P/p-err-empty "ParserError" "$(pv p_err_empty)" check_eq $P/p-err-month-13 "ParserError" "$(pv p_err_month_13)" check_eq $P/p-err-day-32 "ParserError" "$(pv p_err_day_32)" check_eq $P/p-err-feb30 "ParserError" "$(pv p_err_feb30)" # A NON-STRING argument is a bare TypeError, raised before any parsing. check_eq $P/p-err-nonstring "TypeError" "$(pv p_err_nonstring)" # ParserError IS a ValueError, which is what a caller's `except' depends on. check_eq $P/p-err-is-valueerror "yes" "$(pv p_err_is_valueerror)" # ...and the refusal above is about the CALENDAR, not the syntax. check_eq $P/p-feb29-leap-ok "2024-02-29T00:00:00" "$(pv p_feb29_leap_ok)" # ----------------------------------------------------------------- rrule --- # Every expansion element by element, every rule bounded -- see the header. check_eq $P/rr-daily-5 \ "2024-01-01T09:00:00|2024-01-02T09:00:00|2024-01-03T09:00:00|2024-01-04T09:00:00|2024-01-05T09:00:00" \ "$(pv rr_daily_5)" check_eq $P/rr-weekly-3 \ "2024-01-01T09:00:00|2024-01-08T09:00:00|2024-01-15T09:00:00" "$(pv rr_weekly_3)" check_eq $P/rr-monthly-4 \ "2024-01-01T09:00:00|2024-02-01T09:00:00|2024-03-01T09:00:00|2024-04-01T09:00:00" \ "$(pv rr_monthly_4)" check_eq $P/rr-yearly-3 \ "2024-01-01T09:00:00|2025-01-01T09:00:00|2026-01-01T09:00:00" "$(pv rr_yearly_3)" check_eq $P/rr-interval-2d \ "2024-01-01T09:00:00|2024-01-03T09:00:00|2024-01-05T09:00:00|2024-01-07T09:00:00" \ "$(pv rr_interval_2d)" check_eq $P/rr-hourly-3 \ "2024-01-01T09:00:00|2024-01-01T10:00:00|2024-01-01T11:00:00" "$(pv rr_hourly_3)" # `until' IS INCLUSIVE, and one second earlier loses the last occurrence. check_eq $P/rr-until-inclusive \ "2024-01-01T09:00:00|2024-01-02T09:00:00|2024-01-03T09:00:00|2024-01-04T09:00:00" \ "$(pv rr_until_inclusive)" check_eq $P/rr-until-excludes-later \ "2024-01-01T09:00:00|2024-01-02T09:00:00|2024-01-03T09:00:00" \ "$(pv rr_until_excludes_later)" check_eq $P/rr-byweekday \ "2024-01-01T09:00:00|2024-01-03T09:00:00|2024-01-05T09:00:00|2024-01-08T09:00:00|2024-01-10T09:00:00|2024-01-12T09:00:00" \ "$(pv rr_byweekday)" check_eq $P/rr-monthly-2nd-tue \ "2024-01-09T09:00:00|2024-02-13T09:00:00|2024-03-12T09:00:00" "$(pv rr_monthly_2nd_tue)" check_eq $P/rr-monthly-last-fri \ "2024-01-26T09:00:00|2024-02-23T09:00:00|2024-03-29T09:00:00" "$(pv rr_monthly_last_fri)" # bymonthday=-1 is "the last day of the month", which is how a month's length is # asked for without computing it -- and February 2024 answers 29. check_eq $P/rr-last-day-of-month \ "2024-01-31T09:00:00|2024-02-29T09:00:00|2024-03-31T09:00:00|2024-04-30T09:00:00" \ "$(pv rr_last_day_of_month)" # A YEARLY RULE ON 29 FEBRUARY SKIPS three years in four rather than clamping -- # the half of the disagreement with relativedelta that the header names. check_eq $P/rr-feb29-skips \ "2024-02-29T00:00:00|2028-02-29T00:00:00|2032-02-29T00:00:00" "$(pv rr_feb29_skips)" # The query API, which answers without materialising the expansion. check_eq $P/rr-count-method "5" "$(pv rr_count_method)" check_eq $P/rr-before "2024-01-04T09:00:00" "$(pv rr_before)" check_eq $P/rr-after "2024-01-06T09:00:00" "$(pv rr_after)" check_eq $P/rr-between \ "2024-01-03T09:00:00|2024-01-04T09:00:00|2024-01-05T09:00:00" "$(pv rr_between)" # An rruleset composes, and `exdate' removes an instant the rule produced -- the # 3rd is missing from an otherwise contiguous five. check_eq $P/rr-set-exdate \ "2024-01-01T09:00:00|2024-01-02T09:00:00|2024-01-04T09:00:00|2024-01-05T09:00:00" \ "$(pv rr_set_exdate)" # The RFC 5545 string form round-trips through `rrulestr'. check_eq $P/rr-str-roundtrip \ "2024-01-01T09:00:00|2024-01-02T09:00:00|2024-01-03T09:00:00" "$(pv rr_str_roundtrip)" # -------------------------------------------------------------------- tz --- check_eq $P/tz-gettz-named "yes" "$(pv tz_gettz_named)" # A `tzfile', which is the class that reads the SYSTEM database -- a different # answer here means the fallback was taken. check_eq $P/tz-gettz-type "tzfile" "$(pv tz_gettz_type)" check_eq $P/tz-gettz-reads-system "yes" "$(pv tz_gettz_reads_system)" check_eq $P/tz-tzpaths \ "/usr/share/zoneinfo,/usr/lib/zoneinfo,/usr/share/lib/zoneinfo,/etc/zoneinfo" \ "$(pv tz_tzpaths)" # EST is UTC-5 and EDT is UTC-4. Python renders a negative offset as a negative # day plus a positive time, so -5h is `-1 day, 19:00:00' -- the representation a # caller misreads first, which is why it is scored as the string it actually is. check_eq $P/tz-utc-offset-winter "-1 day, 19:00:00" "$(pv tz_utc_offset_winter)" check_eq $P/tz-utc-offset-summer "-1 day, 20:00:00" "$(pv tz_utc_offset_summer)" check_eq $P/tz-name-winter "EST" "$(pv tz_name_winter)" check_eq $P/tz-name-summer "EDT" "$(pv tz_name_summer)" check_eq $P/tz-dst-winter "0:00:00" "$(pv tz_dst_winter)" check_eq $P/tz-dst-summer "1:00:00" "$(pv tz_dst_summer)" # THE TRANSITION INSTANTS, straddled minutes apart on each side. check_eq $P/tz-before-spring "-1 day, 19:00:00" "$(pv tz_before_spring)" check_eq $P/tz-after-spring "-1 day, 20:00:00" "$(pv tz_after_spring)" check_eq $P/tz-before-fall "-1 day, 20:00:00" "$(pv tz_before_fall)" check_eq $P/tz-after-fall "-1 day, 19:00:00" "$(pv tz_after_fall)" # THE TWO CASES A UTC OFFSET CANNOT EXPRESS. 01:30 on the fall-back day happens # TWICE and `fold' selects which; 02:30 on the spring-forward day never happens. check_eq $P/tz-ambiguous-yes "True" "$(pv tz_ambiguous_yes)" check_eq $P/tz-fold0-offset "-1 day, 20:00:00" "$(pv tz_fold0_offset)" check_eq $P/tz-fold1-offset "-1 day, 19:00:00" "$(pv tz_fold1_offset)" check_eq $P/tz-imaginary-exists "False" "$(pv tz_imaginary_exists)" # A HALF-HOUR OFFSET AND NO DST, so the arithmetic is not one country's twice. check_eq $P/tz-kolkata-offset "5:30:00" "$(pv tz_kolkata_offset)" check_eq $P/tz-kolkata-summer "5:30:00" "$(pv tz_kolkata_summer)" # The three tzinfos that need no database at all. check_eq $P/tz-utc-offset "0:00:00" "$(pv tz_utc_offset)" check_eq $P/tz-utc-name "UTC" "$(pv tz_utc_name)" check_eq $P/tz-offset-named "-1 day, 21:00:00" "$(pv tz_offset_named)" check_eq $P/tz-str-posix "-1 day, 20:00:00" "$(pv tz_str_posix)" # Conversion between zones, which is what all of the above exists for. check_eq $P/tz-convert "2024-01-15T07:00:00-05:00" "$(pv tz_convert)" # THE UNGUARDED FALLBACK IMPORT. These three are what hold # `python3-dateutil-zoneinfo' on the image: without it `tz-unknown-zone' reports # ModuleNotFoundError where None is documented. See the header. check_eq $P/tz-unknown-zone "None" "$(pv tz_unknown_zone)" check_eq $P/tz-zoneinfo-importable "yes" "$(pv tz_zoneinfo_importable)" check_eq $P/tz-zoneinfo-get "None" "$(pv tz_zoneinfo_get)" # ---------------------------------------------------------------- easter --- # Three calculus modes, one fixture each -- Western, Orthodox and Julian differ # for 2024 by more than a month. check_eq $P/easter-2024-western "2024-03-31" "$(pv easter_2024_western)" check_eq $P/easter-2024-orthodox "2024-05-05" "$(pv easter_2024_orthodox)" check_eq $P/easter-2024-julian "2024-04-22" "$(pv easter_2024_julian)" # ----------------------------------------------------------------- utils --- check_eq $P/utils-within-delta "True" "$(pv utils_within_delta)" check_eq $P/utils-outside-delta "False" "$(pv utils_outside_delta)" # ----------------------------------------------------------- the clock ----- # The two calls that DO read the clock, scored by TYPE and never by value. check_eq $P/now-type "datetime" "$(pv now_type)" check_eq $P/now-has-tz "yes" "$(pv now_has_tz)" # --------------------------------------------------------------- closure --- # `six' AND NOTHING ELSE -- see the header. A release that grew a compiled # dependency would fail at `import' on this arm rather than resolve it. check_eq $P/pulled-nonstdlib "six" "$(pv pulled_nonstdlib)" check_eq $P/needs-six "yes" "$(pv needs_six)" check_eq $P/needs-datetime "yes" "$(pv needs_datetime)" check_eq $P/needs-calendar "yes" "$(pv needs_calendar)" # Nothing named *dateutil*test* is importable: the sdist keeps tests/ outside # the package, so there is no subpackage to reach. check_eq $P/tests-not-imported "" "$(pv tests_not_imported)" rm -rf "$W"