. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts, plus TWO console-script execs. # # # # The three are: a bare `import', a second one whose # # STDERR is the assertion (`import-quiet'), and the probe, # # which takes ONE start for 88 measurements -- the console # # scripts' COMPUTED output included, because the probe runs # # those through runpy rather than through a fourth # # interpreter. # # # # The two execs are separate and deliberate: they run # # `pripngtopam' and `pripamtopng' as real programs, which # # is what covers the `#!' line and the exec itself rather # # than what the scripts compute. Each is another # # interpreter start. # # # # 1200 is 400s per interpreter start -- the per-start # # budget the neighbouring python3 suites use -- times the # # three that dominate. The encode/decode work itself is # # negligible: the largest image any case builds is 4x2 # # pixels. # # # # A suite-timeout red is a batch-wide cost, so if starts # # prove slower than that on target the RIGHT fix is this # # number, not fewer cases. # # # # NOT MEASURED ON TARGET YET -- see the header note below. # # The measured elapsed replaces this in the test plan # # rather than in this comment, which would cost an image # # build to keep the shipped suite byte-identical. # # python3-pypng 0.20220715.0 (meta-openembedded meta-python/recipes-devtools/ # python, python3-pypng_0.20220715.0.bb, `inherit pypi # python_setuptools_build_meta', PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # READ THIS FIRST: WHAT IN THIS FILE IS MEASURED AND WHAT IS PREDICTED. # ============================================================================ # # THE PORT'S FIRST ON-TARGET RUN IS THE BATCH VERIFY. This suite was authored in # a batch RECIPE lane, which builds at recipe scope and never boots an image. # The recipe-scope build DID complete on the lane's second entry -- 1622 of 1622 # tasks, zero ERROR lines, three wasm32 .debs -- after a first attempt in which # every lane of batch-2270 failed in `brintos-glibc-native:do_configure' on a # base fault unrelated to this package. # # So the PACKAGING facts below that a .deb can answer -- the twelve scripts and # their `#!' line -- are measured. Everything else is not: no image was built # and no case here has run on a target. # # The two halves are not equally solid, and the distinction is the point: # # * THE BEHAVIOURAL CASES ARE MEASURED, just not here. Every literal the # probe's values are compared against was produced by running THIS probe # against THE SAME sdist -- sha256 739c433b..., byte-identical to the one # `SRC_URI[sha256sum]' pins -- on the build host's CPython 3.12.3. The # target interpreter is CPython 3.12.13. `png' is pure Python with no C of # its own, so these are expected to carry over; where one does not, THAT IS # THE FINDING and it is what the suite is for. # # * THE PACKAGING CASES ARE PREDICTIONS. Paths, the dist-info directory name, # the dpkg file list and the `#!' line are derived from the recipe and from # the sdist's `setup.cfg', not from an installed image. They are written as # assertions because a weakened assertion is worth nothing as a regression # signal -- but a red among them on the first verify is a fact about this # comment, not necessarily about the port. `pkg-depends-has-core' is # deliberately the weak form for exactly this reason; see its own note. # # ============================================================================ # WHAT THE PACKAGE ACTUALLY IS, AND THEREFORE WHAT HAS TO BE TESTED. # ============================================================================ # # `png' is a PURE-PYTHON PNG CODEC. `png.Writer' encodes pixel rows into a PNG # byte stream; `png.Reader' decodes one back. 2372 lines in ONE flat module, # `png.py' -- no C extension, no binding to libpng, and no compression # dependency beyond the interpreter's own `zlib'. # # So the suite is built out of ROUND TRIPS IT CLOSES ITSELF. The probe writes # the pixel rows, encodes them, decodes them back, and the cases below assert # the decode against the very rows that were written. NOTHING is compared # against a shipped reference image: every fixture is created by the test and # removed by it. That is what makes this port close on its suite -- no device, # no peer, no terminal, and no BRINTOS-780 follow-on. # # ============================================================================ # WHAT COULD ACTUALLY BREAK ON THIS ARM. # ============================================================================ # # A pure-Python module has no relocation, no `dlopen' and no ABI, so most of # the failure modes the neighbouring C ports test for cannot arise. Four can: # # ZLIB. Every IDAT chunk goes through `zlib.compressobj' / # `zlib.decompressobj'. That is a C extension in the INTERPRETER, and # it is the one native dependency the round trip has. `zlib-selftest' # measures it FIRST and on its own, so a broken zlib arrives as one # named fact instead of as six unexplained decode reds. # # INTEGER WIDTH. 16-bit samples are packed big-endian through `struct', and # chunk CRCs come from `zlib.crc32', which is specified to return an # UNSIGNED 32-bit value. An arm that sign-extended either would corrupt # the stream in a way only the gs16 and the CRC cases would show. # # THE `array' MODULE. Decoded rows are `bytearray' up to 8 bits and # `array("H")' at 16 -- so the row TYPE is itself a measurement # (`rows-type-*'), not an incidental detail of how the probe printed # them. # # THE CONSOLE SCRIPTS' `#!' LINE. This is the ONE case here with real # cross-build risk, and it is the classic one: the sdist ships all # twelve scripts with `#!/usr/bin/env python' -- bare `python', not # `python3'. setuptools rewrites that at build time, and a cross build # that rewrote it to the BUILD HOST's interpreter path produces scripts # that cannot run on the target at all. `script-shebang' and # `script-shebang-not-host' are the two halves of that, following # 58-rsnapshot and 57-icon-naming-utils next door. # # MEASURED at build time against the wasm32 .deb: all twelve ship # `#!/usr/bin/env python3', and all twelve are present. So the rewrite # happened and did not capture a build-host path. What is still NOT # observed is whether the target resolves `env python3' at runtime -- # a property of the image, not of this package. # # ============================================================================ # TWELVE CONSOLE SCRIPTS SHIP, NOT TWO. # ============================================================================ # # The ticket names `pripamtopng' and `pripngtopam'. The sdist's `setup.cfg' # lists TWELVE under `scripts =' -- prichunkpng, pricolpng, priditherpng, # priforgepng, prigreypng, pripalpng, pripamtopng, priplan9topng, pripnglsch, # pripngtopam, prirowpng, priweavepng -- and they are plain `scripts', not # `entry_points' console_scripts, so they land in ${bindir} as the sdist's own # files with their shebangs rewritten. `bin-all-twelve' asserts the whole set: # a port that shipped only the two the ticket happened to name would be a # packaging regression nothing else here would catch. # # This also inverts a check the pure-Python suites next door make. 99-python3- # editor asserts its package installs NOTHING under /usr/bin; this one must # assert the opposite, so the shape of that case is `pkg-has-bin' rather than # `pkg-no-bin'. P=python3-pypng # The canary is emitted BEFORE anything can exit: a suite that dies later still # shows up as having started, which is what tells a truncation from an absence. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages MOD=$SITE/png.py DI=$SITE/pypng-0.20220715.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-pypng.list PROBE=$BST_DATA/python3-pypng-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-pypng-suite.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------------ layout --- check $P/site-packages-dir test -d "$SITE" check $P/module-file test -s "$MOD" # ONE FLAT MODULE, not a package: `png' is py_modules, so there must be no # directory of that name shadowing it. check $P/not-a-package test ! -d "$SITE/png" # The sdist's `code/' directory carries a dozen other modules -- pngsuite.py, # test_png.py, iccp.py, prix.py -- and `py_modules = png' installs NONE of # them. A build that installed the directory wholesale would ship the test # suite and its 8 MB of assets onto the target. check $P/no-test-module test ! -e "$SITE/test_png.py" check $P/no-pngsuite test ! -e "$SITE/pngsuite.py" check $P/no-iccp test ! -e "$SITE/iccp.py" check $P/no-prix test ! -e "$SITE/prix.py" check $P/pycache-mod test -s "$SITE/__pycache__/png.cpython-312.pyc" # ------------------------------------------------------ dpkg file inventory --- # Staged into the workdir first: the loop below must run in THIS shell to keep # the variable it sets, and a `done < $PKGLIST' would redirect stdin from the # virtiofs root, where dup2(2) returns 513 and ash EXITS -- ending the suite # silently (brintos/linux#73). $W is tmpfs. cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # NO SHARED OBJECT ANYWHERE. This package is pure Python; a .so in its file # list would mean something built a C extension that upstream does not have. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # ... and, unlike the pure-Python suites next door, it MUST have files under # /usr/bin -- the twelve console scripts. check $P/pkg-has-bin test -n "$(grep -E '^/usr/bin/' "$W/pkgf" 2>/dev/null)" # Every twelve, by name. See the header: shipping only the two the ticket names # would be a packaging regression with no other symptom. _missing="" for _s in prichunkpng pricolpng priditherpng priforgepng prigreypng pripalpng \ pripamtopng priplan9topng pripnglsch pripngtopam prirowpng priweavepng; do [ -f "/usr/bin/$_s" ] || _missing="$_missing $_s" done check_eq $P/bin-all-twelve "" "$_missing" # Nothing but Python and the scripts. Anything else in the file list is the # sdist's `asset/' or `man/' tree having escaped into the package. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; /usr/bin/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python-and-scripts "" "$_bad" # THE SDIST'S ASSETS MUST NOT SHIP. `asset/' is 8 MB of reference PNGs and ICC # profiles used only by upstream's own test suite. check $P/no-asset-dir test ! -d "$SITE/asset" # ---------------------------------------------------------------- dist-info --- check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" # `pypng' is the DISTRIBUTION name and `png' the MODULE name -- they differ, # which is the detail a hand-written RDEPENDS or a `pip show' gets wrong. check_out $P/dist-info-name "Name: pypng" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 0.20220715.0" cat "$DI/METADATA" check_out $P/dist-info-summary \ "Pure Python library for saving and loading PNG images" cat "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" # Root-Is-Purelib true and a py3-none-any tag: this is what "pure Python" means # to the packaging layer, and it is the assertion that would break first if # anything ever compiled a C extension into this wheel. check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag-py3 "Tag: py3-none-any" cat "$DI/WHEEL" check_eq $P/dist-info-no-py2-tag "" \ "$(grep '^Tag: py2' "$DI/WHEEL" 2>/dev/null)" # top_level.txt carries the MODULE name, not the distribution name. check_out $P/dist-info-top-level "png" cat "$DI/top_level.txt" # ------------------------------------------------------------- dpkg status ---- sed -n '/^Package: python3-pypng$/,/^$/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" check_out $P/pkg-version "Version: 0.20220715.0-r0" cat "$W/st" # THE WEAK FORM, DELIBERATELY. The neighbouring suites assert their `Depends:' # line verbatim, and that is the right shape once the line has been SEEN. This # recipe declares no RDEPENDS of its own, so the line is whatever the pypi / # setuptools3 classes compute -- and this suite was authored in a lane whose # build never completed, so nobody has read it. Asserting a guessed exact # string would manufacture a red that says nothing about the port. What IS # certain is that a pure-Python module importable at all must depend on # python3-core; that much is asserted, and the exact line is a follow-up once # the first verify prints it. 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-has-core "python3-core" cat "$W/dep" # ------------------------------------------------------------------ import --- check $P/import python3 -c 'import png' # A CLEAN IMPORT, not merely a successful one. A DeprecationWarning or a # missing-module notice on stderr is invisible to the exit status and is # exactly what a half-finished port emits. check_eq $P/import-quiet "" \ "$(python3 -c 'import png' 2>&1)" # ============================================================================ # THE CONSOLE SCRIPTS AS REAL PROGRAMS. # ============================================================================ # # The probe runs these through runpy, which covers what they COMPUTE. These two # cases cover what runpy cannot: the `#!' line, the executable bit, and the # exec itself. See the header -- this is the one genuine cross-build risk in # the port. BIN=/usr/bin/pripngtopam check $P/script-present test -f "$BIN" check $P/script-executable test -x "$BIN" _sb=$(head -n 1 "$BIN" 2>/dev/null) # The sdist ships `#!/usr/bin/env python' -- BARE `python'. setuptools rewrites # it; MEASURED in the built wasm32 .deb, all twelve land on `#!/usr/bin/env # python3' -- the env form, not an absolute interpreter path. check_eq $P/script-shebang "#!/usr/bin/env python3" "$_sb" # The half that matters even if the line above drifts: whatever the shebang is, # it must not point into a build-host tree. A `.../tmp-glibc/work/...' or # `.../recipe-sysroot-native/...' path here is the classic cross-build failure # and the script would be unrunnable on target. case "$_sb" in *recipe-sysroot*|*/tmp-glibc/*|*/work/*|*hosttools*) bst_notok $P/script-shebang-not-host "build-host path in shebang: $_sb" ;; *) bst_ok $P/script-shebang-not-host ;; esac # END TO END THROUGH BOTH PROGRAMS. The probe writes a 3x2 greyscale PNG; the # two scripts convert it to netpbm and back; the result decodes to the SAME # rows the probe wrote. Run as separate execs, and the pipeline is written with # explicit files rather than a pipe so a failure names which half broke. python3 -c " import png f = open('$W/rt.png', 'wb') png.Writer(width=3, height=2, greyscale=True, bitdepth=8).write( f, [[0, 127, 255], [10, 20, 30]]) f.close()" 2>/dev/null check $P/rt-fixture test -s "$W/rt.png" check $P/rt-pngtopam sh -c "pripngtopam '$W/rt.png' > '$W/rt.pnm'" check $P/rt-pamtopng sh -c "pripamtopng '$W/rt.pnm' > '$W/rt2.png'" check_eq $P/rt-rows "0,127,255|10,20,30" \ "$(python3 -c " import png w, h, rows, info = png.Reader(filename='$W/rt2.png').read() print('|'.join(','.join(str(v) for v in r) for r in rows))" 2>/dev/null)" # ============================================================================ # THE PROBE: ONE INTERPRETER START FOR EVERYTHING ELSE. # ============================================================================ 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() { 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, so a value that is absent below is absent # because the probe chose not to emit it -- not because it died partway. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "88" "$(pv emitted_count)" # ------------------------------------------------ zlib, before any png work --- # The one native dependency the round trip has. Measured first so a broken zlib # is one named fact rather than six unexplained decode reds. check_eq $P/zlib-selftest "yes" "$(pv zlib_selftest)" check_eq $P/crc32-unsigned "yes" "$(pv crc32_unsigned)" # --------------------------------------------------------- module identity --- check_eq $P/module-answered "$MOD" "$(pv module_file)" check_eq $P/is-package "no" "$(pv is_package)" check_eq $P/version-attr "0.20220715.0" "$(pv version_attr)" check_eq $P/has-dunder-all "yes" "$(pv has_dunder_all)" check_eq $P/dunder-all "Image,Reader,Writer,from_array,write_chunks" \ "$(pv dunder_all)" # THE EXCEPTION LATTICE, which is what makes the ticket's "raises # png.FormatError" true of a bad CRC: ChunkError SUBCLASSES FormatError, so # `except png.FormatError' catches it. Asserted as structure rather than left # implicit in the error cases below. check_eq $P/chunkerror-is-formaterror "yes" "$(pv chunkerror_is_formaterror)" check_eq $P/formaterror-is-error "yes" "$(pv formaterror_is_error)" check_eq $P/error-is-exception "yes" "$(pv error_is_exception)" check_eq $P/protocolerror-is-error "yes" "$(pv protocolerror_is_error)" # ============================================================================ # ROUND TRIPS, ONE PER COLOUR MODE AND BIT DEPTH. # ============================================================================ # Each asserts width, height, the row values and the info dict against the same # literals the probe encoded from -- so a mismatch is a codec fault and never a # fixture that drifted. # --- 8-bit greyscale: one plane, one byte per sample. The baseline. check_eq $P/gs8-width "3" "$(pv gs8_width)" check_eq $P/gs8-height "2" "$(pv gs8_height)" check_eq $P/gs8-rows "0,127,255|10,20,30" "$(pv gs8_rows)" check_eq $P/gs8-info \ "greyscale=True alpha=False planes=1 bitdepth=8 interlace=0 size=(3, 2)" \ "$(pv gs8_info)" # --- 16-bit greyscale: samples packed BIG-ENDIAN through struct. The case that # would show an arm mis-handling sample width. check_eq $P/gs16-width "2" "$(pv gs16_width)" check_eq $P/gs16-height "2" "$(pv gs16_height)" check_eq $P/gs16-rows "0,65535|1000,32768" "$(pv gs16_rows)" check_eq $P/gs16-info \ "greyscale=True alpha=False planes=1 bitdepth=16 interlace=0 size=(2, 2)" \ "$(pv gs16_info)" # --- 8-bit RGB: three planes interleaved WITHIN a row, so a 2-pixel row is six # samples long and `planes=3' is what says so. check_eq $P/rgb8-width "2" "$(pv rgb8_width)" check_eq $P/rgb8-height "1" "$(pv rgb8_height)" check_eq $P/rgb8-rows "255,0,0,0,255,0" "$(pv rgb8_rows)" check_eq $P/rgb8-info \ "greyscale=False alpha=False planes=3 bitdepth=8 interlace=0 size=(2, 1)" \ "$(pv rgb8_info)" # --- 8-bit RGBA: four planes. `alpha=True' is the only field distinguishing it # from RGB, since both are greyscale=False. check_eq $P/rgba8-width "2" "$(pv rgba8_width)" check_eq $P/rgba8-height "1" "$(pv rgba8_height)" check_eq $P/rgba8-rows "1,2,3,4,5,6,7,8" "$(pv rgba8_rows)" check_eq $P/rgba8-info \ "greyscale=False alpha=True planes=4 bitdepth=8 interlace=0 size=(2, 1)" \ "$(pv rgba8_info)" # --- 4-bit palette: TWO PIXELS PER BYTE, and the decoded rows are PALETTE # INDICES rather than colours -- the caller resolves them through # info["palette"] itself. That is the behaviour a caller gets wrong, so the # palette comes back as its own assertion. check_eq $P/pal4-width "4" "$(pv pal4_width)" check_eq $P/pal4-height "2" "$(pv pal4_height)" check_eq $P/pal4-rows "0,1,2,3|3,2,1,0" "$(pv pal4_rows)" check_eq $P/pal4-info \ "greyscale=False alpha=False planes=1 bitdepth=4 interlace=0 size=(4, 2)" \ "$(pv pal4_info)" check_eq $P/pal4-palette "0,0,0|255,0,0|0,255,0|0,0,255" "$(pv pal4_palette)" # A palette image is NOT greyscale and has ONE plane -- together the pair that # identifies the mode, and the pair a caller most often assumes wrong. check_eq $P/pal4-greyscale "False" "$(pv pal4_greyscale)" check_eq $P/pal4-planes "1" "$(pv pal4_planes)" # --- 1-bit bitmap: EIGHT PIXELS PER BYTE. check_eq $P/bit1-width "8" "$(pv bit1_width)" check_eq $P/bit1-height "1" "$(pv bit1_height)" check_eq $P/bit1-rows "0,1,1,0,1,0,0,1" "$(pv bit1_rows)" check_eq $P/bit1-info \ "greyscale=True alpha=False planes=1 bitdepth=1 interlace=0 size=(8, 1)" \ "$(pv bit1_info)" # The row above fills exactly one byte, so a decoder that never masked the # trailing bits of a PARTIAL byte would still pass it. This 3-pixel row is the # case that catches that: 3 bits of data, 5 bits of padding the reader must # discard rather than return as samples. check_eq $P/bit1-partial-rows "1,0,1" "$(pv bit1_partial_rows)" check_eq $P/bit1-partial-len "3" "$(pv bit1_partial_len)" # --- ROW TYPE, and it is NOT one type. `bytearray' up to 8 bits, `array("H")' # at 16. A caller that only indexes rows sees no difference; one that calls # .tobytes() does, because bytearray has no such method. check_eq $P/rows-type-1 "bytearray" "$(pv rows_type_1)" check_eq $P/rows-type-4 "bytearray" "$(pv rows_type_4)" check_eq $P/rows-type-8 "bytearray" "$(pv rows_type_8)" check_eq $P/rows-type-16 "array" "$(pv rows_type_16)" check_eq $P/rows-typecode-16 "H" "$(pv rows_typecode_16)" # ============================================================================ # INTERLACING: THE PIXELS SURVIVE AND THE BYTES DO NOT. # ============================================================================ # Adam7 reorders the image into seven passes, so the encoded stream differs # from the non-interlaced one -- and Reader.read() must reassemble the SAME # rows. Both halves are asserted; either alone would pass a writer that # silently ignored `interlace=1'. check_eq $P/interlace-width "3" "$(pv interlace_width)" check_eq $P/interlace-height "2" "$(pv interlace_height)" check_eq $P/interlace-rows "0,127,255|10,20,30" "$(pv interlace_rows)" check_eq $P/interlace-flag "1" "$(pv interlace_flag)" check_eq $P/interlace-bytes-differ "yes" "$(pv interlace_bytes_differ)" check_eq $P/interlace-rows-match "yes" "$(pv interlace_rows_match)" # ============================================================================ # THE CHUNK STREAM, ASSERTED DIRECTLY. # ============================================================================ # Reader.chunks() yields (tag, data) without decoding the image. The signature # is checked separately because chunks() never yields it -- it is consumed # before the first chunk. check_eq $P/signature-hex "89504e470d0a1a0a" "$(pv signature_hex)" check_eq $P/signature-ok "yes" "$(pv plain_signature_ok)" check_eq $P/chunks-plain "IHDR,IDAT,IEND" "$(pv chunks_plain)" check_eq $P/chunks-first "IHDR" "$(pv chunks_first)" check_eq $P/chunks-last "IEND" "$(pv chunks_last)" # tRNS APPEARS EXACTLY WHEN TRANSPARENCY WAS REQUESTED. Both arms, because # "present when asked" alone would pass a writer that emitted it always. check_eq $P/chunks-palette-opaque "IHDR,PLTE,IDAT,IEND" "$(pv chunks_palette_opaque)" check_eq $P/chunks-palette-trns "IHDR,PLTE,tRNS,IDAT,IEND" "$(pv chunks_palette_trns)" check_eq $P/trns-absent-when-opaque "yes" "$(pv trns_absent_when_opaque)" check_eq $P/trns-present-when-asked "yes" "$(pv trns_present_when_asked)" # PLTE must precede tRNS -- the PNG spec's own ordering constraint, and the one # a hand-rolled writer gets wrong. check_eq $P/plte-before-trns "yes" "$(pv plte_before_trns)" # ============================================================================ # ERROR PATHS: EACH MUST RAISE RATHER THAN YIELD PARTIAL ROWS. # ============================================================================ # Reader.read() is lazy -- it returns a row ITERATOR -- so the probe drains the # rows inside the guarded call. Draining is the point: a reader that raised # only on the last row would otherwise pass while having handed the caller # partial data. # # Note the CONCRETE type differs across the three, and that is deliberate # rather than sloppy: a cut mid-chunk is a FormatError, while a missing IEND # and a bad CRC are ChunkError -- which SUBCLASSES FormatError, so the # ticket's "raises png.FormatError" holds for all three. The `-is-formaterror' # case is the one that states the ticket's requirement; the `-type' case pins # which concrete class, so a future release that changed it shows up here. # --- A file cut mid-stream, so the cut lands inside IDAT's declared length. check_eq $P/err-truncated-type "!FormatError" "$(pv err_truncated_type)" check_eq $P/err-truncated-is-formaterror "yes" "$(pv err_truncated_is_formaterror)" # --- IEND removed. NOT the same failure: every chunk present is well-formed, # and what is missing is the terminator, so the reader runs out of input at a # chunk boundary rather than inside one. check_eq $P/err-no-iend-type "!ChunkError" "$(pv err_no_iend_type)" check_eq $P/err-no-iend-is-formaterror "yes" "$(pv err_no_iend_is_formaterror)" # --- A BAD CRC: one byte of IHDR's payload flipped, so the stored CRC no # longer matches. This is the case that proves CRC checking is ON BY DEFAULT -- # a reader that skipped it would decode this file happily. check_eq $P/err-bad-crc-type "!ChunkError" "$(pv err_bad_crc_type)" check_eq $P/err-bad-crc-is-formaterror "yes" "$(pv err_bad_crc_is_formaterror)" # ... and it is refused at CHUNK level, before any pixel work: chunks() alone # raises, with the image never decoded. check_eq $P/err-bad-crc-chunks "!ChunkError" "$(pv err_bad_crc_chunks)" # --- A WRONG SIGNATURE, with every chunk after it intact, so the refusal is # the eight-byte magic check alone. check_eq $P/err-bad-sig-type "!FormatError" "$(pv err_bad_sig_type)" check_eq $P/err-bad-sig-is-formaterror "yes" "$(pv err_bad_sig_is_formaterror)" # --- AN EMPTY FILE, AND IT ESCAPES THE LIBRARY'S OWN HIERARCHY. Zero bytes # raise a bare EOFError, NOT a png.FormatError: png.py reads the signature with # a helper that raises EOFError on short input and converts to FormatError only # once it has bytes to complain about. So `except png.FormatError' does NOT # catch the empty-file case. # # THIS IS UPSTREAM BEHAVIOUR, NOT A PLATFORM GAP -- it reproduces identically # on the build host's CPython 3.12. It is pinned rather than reported so that a # future release which tidies the hierarchy shows up as a red here instead of # passing unnoticed. check_eq $P/err-empty-type "!EOFError" "$(pv err_empty_type)" check_eq $P/err-empty-not-formaterror "no:EOFError" "$(pv err_empty_is_formaterror)" # --- A WRITER MISUSE, which is the OTHER exception family. A row shorter than # the declared width is the CALLER's protocol error, not a malformed file, and # png.py keeps the two apart: this must NOT come back as a FormatError. check_eq $P/err-short-row-type "!ProtocolError" "$(pv err_short_row_type)" # ============================================================================ # WHAT THE CONSOLE SCRIPTS COMPUTE. # ============================================================================ # Run through runpy inside the probe's single interpreter; the exec-level cases # are above. check_eq $P/scriptdir "/usr/bin" "$(pv scriptdir)" check_eq $P/fixture-written "yes" "$(pv fixture_written)" # pripngtopam picks the netpbm FORMAT from the colour mode -- P5 (PGM) for # greyscale, P6 (PPM) for RGB. It does NOT emit a literal PAM (P7) for these, # which is what a caller reading only the program's name expects, the ticket's # own wording included. check_eq $P/pngtopam-magic "P5" "$(pv pngtopam_magic)" check_eq $P/pngtopam-header "P5 3 2 255" "$(pv pngtopam_header)" check_eq $P/pngtopam-body-hex "007fff0a141e" "$(pv pngtopam_body_hex)" check_eq $P/pngtopam-rgb-magic "P6" "$(pv pngtopam_rgb_magic)" # pripamtopng, fed the netpbm the previous case produced: the two together # close a round trip through BOTH console scripts, and the result is decoded # back to the same rows every other greyscale case uses. check_eq $P/pamtopng-width "3" "$(pv pamtopng_width)" check_eq $P/pamtopng-height "2" "$(pv pamtopng_height)" check_eq $P/pamtopng-rows "0,127,255|10,20,30" "$(pv pamtopng_rows)" check_eq $P/pamtopng-info \ "greyscale=True alpha=False planes=1 bitdepth=8 interlace=0 size=(3, 2)" \ "$(pv pamtopng_info)" # --------------------------------------------------------------- hygiene ---- # The probe left nothing behind and did not move the interpreter's cwd out from # under anything that follows. check_eq $P/workdir-gone "yes" "$(pv workdir_gone)" check_eq $P/cwd-restored "yes" "$(pv cwd_restored)" rm -rf "$W"