. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts: a bare `import', a second one whose # # STDERR is the assertion (`import-quiet'), and the probe, # # which takes ONE start for 117 measurements. # # # # 400s per start is the per-start budget the neighbouring # # python3 suites use, and unlike 99-python3-editor next door # # this suite spawns NOTHING: every fixture is bytes the probe # # writes into a BytesIO, so there is no exec traffic to pay # # for and no file to open outside $TMPDIR. # # # # 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-pyelftools 0.30 (poky meta/recipes-devtools/python, # python3-pyelftools_0.30.bb, `inherit pypi setuptools3', PyPI sdist) on the # wasm32-hwjs glibc image. # # ============================================================================ # THERE IS NO ELF FILE ON THIS IMAGE. THAT SHAPES THE WHOLE SUITE. # ============================================================================ # # The ticket describes two kinds of fixture: one the test writes byte by byte, # and "an ELF object already on the image" to make structural assertions over. # THE SECOND DOES NOT EXIST. This is a wasm32-hwjs target: every executable and # every shared object in the rootfs is a WebAssembly module (`\0asm'), and the # kernel is /boot/vmlinux.wasm. # # MEASURED, not reasoned: the batch-2195 rootfs was scanned in full -- # 83009 regular files, 3367 carrying `\0asm', and ZERO carrying `\x7fELF'. # # So every fixture here is built by the probe, which is what the ticket's own # closing paragraph says anyway ("Every fixture is written by the test itself"). # That is a strictly better position than borrowing a binary: every field the # suite asserts is a field the probe CHOSE, so the expectations below are # literals rather than reflections of whatever happened to be installed, and the # suite cannot go red because some unrelated package changed its layout. # # ============================================================================ # THE MALFORMED-INPUT CONTRACT IS NOT WHAT THE TICKET EXPECTED. MEASURED. # ============================================================================ # # The ticket asks that a file with the wrong magic, one truncated inside the # section header table, and one whose e_shoff points past the end "are each # asserted to raise ELFError rather than to return a half-parsed object". # # ONE OF THE THREE BEHAVES THAT WAY. ELFFile's constructor is EAGER about # identification and LAZY about everything else: # # * wrong magic, bad EI_CLASS, bad EI_DATA -> ELFError FROM THE CONSTRUCTOR. # _identify_file() reads the first six bytes itself and elf_assert()s them. # # * truncated inside the section header table -> THE CONSTRUCTOR SUCCEEDS. # * e_shoff past the end of the file -> THE CONSTRUCTOR SUCCEEDS. # # For those two you get exactly the half-parsed object the ticket wanted # excluded, and num_sections() cheerfully returns e_shnum -- 5 -- read straight # out of the header and never once checked against the file's length. The # ELFError arrives only when a section is actually REACHED, as ELFParseError, # and iter_segments() keeps working the whole time because the program header # table is still intact. # # So the guarantee a caller can actually rely on is "every SECTION ACCESS # raises", not "the constructor rejects it", and the `malformed-*' cases below # assert that line where it really falls. This is a correction to the ticket # derived from running the code, not a relaxation of it: the cases are STRICTER # than the ticket asked for, because they pin down which operations are safe on # a damaged file and which are not. # # DWARFError IS NOT AN ELFError. `class DWARFError(Exception)' -- # common/exceptions.py -- while ELFRelocationError, ELFParseError and # ELFCompressionError all derive from ELFError. A caller who wraps a parse in # `except ELFError' therefore catches every container fault and NO DWARF fault. # `exc-dwarferror-not-elferror' pins that, because it is the kind of hierarchy # detail a version bump changes silently. # # ============================================================================ # WHAT THE PACKAGE IS, AND WHY IT PORTS. # ============================================================================ # # A PURE-PYTHON PARSER WITH NO THIRD-PARTY RUNTIME DEPENDENCY. pyelftools # carries its own vendored copy of `construct' under elftools/construct, so it # needs nothing from PyPI at run time -- `construct-is-ours' asserts the copy # that answers is the vendored one and not some other `construct' on the path. # This image's python3 is ONE STATIC BINARY with an empty lib-dynload and no # dynamic loader (BRINTOS-1476), so a module carrying a compiled extension could # not be imported here at all; pyelftools ships no C, which is why it ports. # # IT DOES PUT A FILE IN /usr/bin, unlike most of its pure-Python neighbours. # setup.py declares `scripts=['scripts/readelf.py']' -- not entry_points -- so # /usr/bin/readelf.py lands on the rootfs. `bin-readelf' and `pkg-has-bin' say # so from the two sides, the interpreter's and the .deb's. # # THE WHEEL IS UNIVERSAL, which its neighbour python3-editor is not. setup.cfg # carries `[bdist_wheel] universal = 1', so the wheel is tagged for BOTH py2 and # py3 and `dist-info-tag-py2' is a PASS here where the identically-named case # next door asserts the absence. The difference is a measurement of the two # upstreams, not an inconsistency between the suites. # # elffile.py COMPUTES PAGESIZE AT IMPORT and has three fallbacks: # resource.getpagesize(), else mmap.PAGESIZE, else the literal 4096. Which one # answers on a static interpreter with an empty lib-dynload is a fact about THIS # ARM, so the suite RECORDS it (`pagesize-recorded') and asserts only that the # result is a positive power of two. Pinning the number would make this suite go # red on a python3 split that moved `resource', which would be a true statement # about the interpreter and a false one about pyelftools. # # ============================================================================ P=python3-pyelftools # The canary is first so that a suite which dies before its own first assertion # is distinguishable from one that never started. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKG=$SITE/elftools DI=$SITE/pyelftools-0.30.dist-info PKGLIST=/var/lib/dpkg/info/python3-pyelftools.list PROBE=$BST_DATA/python3-pyelftools-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-pyelftools-suite.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ---------------------------------------------------------------- layout ----- # A PACKAGE, NOT A FLAT MODULE -- setup.py lists seven packages, and the import # name (`elftools') is not the distribution name (`pyelftools'). check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKG" check $P/package-init test -s "$PKG/__init__.py" check $P/not-a-flat-module test ! -e "$SITE/elftools.py" # The six sub-packages setup.py declares beside `elftools' itself, each of # which the probe also imports. check $P/subpkg-elf test -s "$PKG/elf/elffile.py" check $P/subpkg-common test -s "$PKG/common/exceptions.py" check $P/subpkg-dwarf test -s "$PKG/dwarf/dwarfinfo.py" check $P/subpkg-ehabi test -s "$PKG/ehabi/ehabiinfo.py" check $P/subpkg-construct test -s "$PKG/construct/core.py" check $P/subpkg-construct-lib test -s "$PKG/construct/lib/container.py" check $P/pycache-init test -s "$PKG/__pycache__/__init__.cpython-312.pyc" # Upstream's own test tree and examples are NOT packaged -- setup.py lists # neither -- so an image that grew them would be shipping several megabytes of # fixtures nobody asked for. check $P/no-test-tree test ! -d "$SITE/test" check $P/no-examples test ! -d "$SITE/examples" # ------------------------------------------------------------- the .deb ------ cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # PURE PYTHON, AND NOT MERELY "NO .so OF ITS OWN". This image's python3 is one # static binary with an empty lib-dynload and no dynamic loader (BRINTOS-1476), # so a module carrying a compiled extension could not be imported here at all. # pyelftools ships no C -- no .c, no ext_modules in setup.py -- so this is a # REGRESSION TRIPWIRE on a version bump rather than a statement about today. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # THIS ONE DOES SHIP A SCRIPT. setup.py declares scripts=['scripts/readelf.py'], # so exactly one file lands in /usr/bin -- asserted as a literal, because "some # file in /usr/bin" would pass on a packaging change that added more. check_eq $P/pkg-has-bin "/usr/bin/readelf.py" \ "$(grep -E '^/usr/(s?bin)/' "$W/pkgf" 2>/dev/null)" # ...and nothing but Python in the rest of the payload. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in /usr/bin/*) continue ;; *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # THE DIST-INFO IS NAMED FOR THE DISTRIBUTION, NOT THE IMPORT NAME. The # distribution is `pyelftools' and the top-level package is `elftools'. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: pyelftools" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 0.30" cat "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" check_out $P/dist-info-top-level "elftools" cat "$DI/top_level.txt" # `setuptools3' BUILT THIS, and setup.cfg carries `universal = 1', so the wheel # is tagged for BOTH py2 and py3 -- the opposite of python3-editor next door, # where the same case asserts py2's ABSENCE. Both are measurements of their own # upstream's setup.cfg. check_out $P/dist-info-generator "Generator: bdist_wheel" cat "$DI/WHEEL" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-tag-py3 "Tag: py3-none-any" cat "$DI/WHEEL" check_out $P/dist-info-tag-py2 "Tag: py2-none-any" cat "$DI/WHEEL" sed -n '/^Package: python3-pyelftools$/,/^$/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.30-r0" cat "$W/st" # THE RDEPENDS IS ASSERTED EXACTLY. The recipe adds `python3-debugger' and # `python3-pprint' by hand, and setuptools3-base contributes python3-core. A # lean image that dropped one of the two would say so HERE, once, rather than # presenting as a library that half-imports: elftools/dwarf/descriptions.py and # elftools/elf/descriptions.py both reach pprint, and the readelf.py driver # imports pdb. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" # ---------------------------------------------------------------- import ----- check $P/import python3 -c 'import elftools' check $P/import-elffile python3 -c 'from elftools.elf.elffile import ELFFile' # A DeprecationWarning, or a stray print on import, would be invisible to the # cases above, which score only the exit status. check_eq $P/import-quiet "" \ "$(python3 -c 'import elftools.elf.elffile' 2>&1)" # THE DRIVER SCRIPT IS ON THE ROOTFS AND IS EXECUTABLE. Its --help is not run # here: readelf.py's argparse block runs at import and a fourth python3 start # costs more than the case is worth. `bin-readelf-shebang' is the cheap half. check $P/bin-readelf test -x /usr/bin/readelf.py check_out $P/bin-readelf-shebang "python" head -n 1 /usr/bin/readelf.py # ----------------------------------------------------------------- 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() { 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's own two bookends. `probe-complete' says it reached its last line # and `probe-count' says it emitted every key it meant to: a probe that died # two-thirds of the way through would otherwise leave the missing cases scoring # against an empty string, which `check_eq' would read as a wrong answer rather # than as an absent one. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "117" "$(pv emitted_count)" # -------------------------------------------------------------- identity ----- check_eq $P/version-attr "0.30" "$(pv version_attr)" check_eq $P/is-package "yes" "$(pv is_package)" check_eq $P/elffile-module "$PKG/elf/elffile.py" "$(pv elffile_module)" # THE VENDORED construct IS THE ONE THAT ANSWERED -- see the header. check_eq $P/construct-name "elftools.construct" "$(pv construct_vendored)" check_eq $P/construct-is-ours "yes" "$(pv construct_is_ours)" # PAGESIZE IS RECORDED, NOT PINNED -- see the header. The assertion is the # invariant (a positive power of two); the value and which fallback produced it # are reported so a later reader can see what this arm did. check_eq $P/pagesize-sane "yes" "$(pv pagesize_sane)" bst_ok $P/pagesize-recorded # value: see the probe transcript key `pagesize' # ========================================================== the ELF header === # EVERY LITERAL BELOW IS A FIELD THE PROBE WROTE. A disagreement is a parse # fault in elftools, never a property of some binary that happened to be around. check_eq $P/elfclass "64" "$(pv elfclass)" check_eq $P/little-endian "yes" "$(pv little_endian)" check_eq $P/elftype "ET_REL" "$(pv elftype)" check_eq $P/machine "EM_X86_64" "$(pv machine)" check_eq $P/e-version "EV_CURRENT" "$(pv e_version)" check_eq $P/e-ehsize "64" "$(pv e_ehsize)" check_eq $P/e-shentsize "64" "$(pv e_shentsize)" check_eq $P/e-phentsize "56" "$(pv e_phentsize)" check_eq $P/e-shstrndx "4" "$(pv e_shstrndx)" check_eq $P/ei-class "ELFCLASS64" "$(pv ei_class)" check_eq $P/ei-data "ELFDATA2LSB" "$(pv ei_data)" check_eq $P/ei-osabi "ELFOSABI_SYSV" "$(pv ei_osabi)" # e_ident IS RETURNED RAW AS WELL AS DECODED, and the raw form is the 16 bytes # the probe wrote -- magic, class 2, data 1, version 1, then nine zeroes. check_eq $P/e-ident-raw "7f454c46020101000000000000000000" "$(pv e_ident_raw)" check_eq $P/num-sections "5" "$(pv num_sections)" check_eq $P/num-segments "1" "$(pv num_segments)" # THE PARSER MEASURED THE WHOLE FILE, not a prefix of it. check_eq $P/stream-len-is-file-len "$(pv fixture_len)" "$(pv stream_len)" # ============================================================== sections ===== # EVERY SECTION IS ITERABLE and every one resolves a name in the section-header # string table -- the ticket's structural assertion, over a table the probe # built, so the expectation is the exact list. check_eq $P/section-names \ ",.text,.symtab,.strtab,.shstrtab" "$(pv section_names)" check_eq $P/section-types \ "SHT_NULL,SHT_PROGBITS,SHT_SYMTAB,SHT_STRTAB,SHT_STRTAB" "$(pv section_types)" check_eq $P/sections-iterated "5" "$(pv sections_iterated)" check_eq $P/all-sections-named "yes" "$(pv all_sections_named)" # INDEX 0 IS THE MANDATORY NULL SECTION and it is not skipped -- a parser that # dropped it would renumber every section a caller looked up by index. check_eq $P/null-section-type "SHT_NULL" "$(pv null_section_type)" check_eq $P/by-name-text ".text" "$(pv by_name_text)" check_eq $P/index-text "1" "$(pv index_text)" check_eq $P/text-data "90909090" "$(pv text_data)" check_eq $P/text-size "4" "$(pv text_size)" # A MISSING SECTION IS None, NOT AN EXCEPTION. Both lookups agree, which is the # contract a caller writes `if sec is None' against. check_eq $P/by-name-missing "None" "$(pv by_name_missing)" check_eq $P/index-missing "None" "$(pv index_missing)" # iter_sections(type=...) FILTERS BY THE TYPE'S STRING NAME, not its number. check_eq $P/filter-symtab ".symtab" "$(pv symtab_sections)" check_eq $P/filter-strtab ".strtab,.shstrtab" "$(pv strtab_sections)" # ============================================================== symbols ====== check_eq $P/symtab-class "SymbolTableSection" "$(pv symtab_class)" check_eq $P/symtab-is-symboltable "yes" "$(pv symtab_is_symboltable)" check_eq $P/num-symbols "2" "$(pv num_symbols)" check_eq $P/symbol-names "'','_start'" "$(pv symbol_names)" # EVERY SYMBOL RESOLVES A NAME IN THE STRING TABLE -- the ticket's assertion, # counted rather than reduced to a boolean so a partial failure stays legible. check_eq $P/symbols-resolved "2" "$(pv symbols_resolved)" # The null symbol at index 0, whose name is the empty string and whose value is # zero. Required by the ELF ABI and easy for a parser to elide. check_eq $P/sym0-name "''" "$(pv sym0_name)" check_eq $P/sym0-value "0" "$(pv sym0_value)" # ...and the one real symbol, every field of which the probe chose. check_eq $P/sym1-name "_start" "$(pv sym1_name)" check_eq $P/sym1-value "0x1000" "$(pv sym1_value)" check_eq $P/sym1-size "16" "$(pv sym1_size)" check_eq $P/sym1-shndx "1" "$(pv sym1_shndx)" check_eq $P/sym1-bind "STB_GLOBAL" "$(pv sym1_bind)" check_eq $P/sym1-type "STT_FUNC" "$(pv sym1_type)" check_eq $P/symbol-by-name-value "0x1000" "$(pv symbol_by_name_value)" check_eq $P/symbol-by-name-missing "None" "$(pv symbol_by_name_missing)" check_eq $P/strtab-class "StringTableSection" "$(pv strtab_class)" check_eq $P/strtab-is-stringtable "yes" "$(pv strtab_is_stringtable)" check_eq $P/strtab-get-string "_start" "$(pv strtab_get_string)" # ============================================================== segments ===== check_eq $P/segment-types "PT_LOAD" "$(pv segment_types)" check_eq $P/segment0-vaddr "0x1000" "$(pv segment0_vaddr)" check_eq $P/segment0-data "90909090" "$(pv segment0_data)" # THE SEGMENT TABLE'S FILE OFFSETS STAY INSIDE THE FILE -- the ticket's # structural assertion, checked as arithmetic over p_offset + p_filesz. check_eq $P/segments-in-file "yes" "$(pv segments_in_file)" # ============================================================= malformed ===== # READ THE HEADER FIRST. These cases record WHERE each failure lands, and two of # the three the ticket names do NOT fail at the constructor. # The eager half: identification. Six bytes decide these, and all six raise. check_eq $P/malformed-bad-magic "ELFError" "$(pv malformed_bad_magic)" check_eq $P/malformed-bad-magic-is-elferror "yes" "$(pv malformed_bad_magic_iselferror)" check_eq $P/malformed-empty "ELFError" "$(pv malformed_empty)" check_eq $P/malformed-four-bytes "ELFError" "$(pv malformed_four_bytes)" check_eq $P/malformed-bad-ei-class "ELFError" "$(pv malformed_bad_ei_class)" check_eq $P/malformed-bad-ei-data "ELFError" "$(pv malformed_bad_ei_data)" # A header cut short mid-field is a PARSE fault rather than an identification # one -- ELFParseError, which is an ELFError, from struct_parse. check_eq $P/malformed-short-header "ELFParseError" "$(pv malformed_short_header)" # The lazy half. THE CONSTRUCTOR SUCCEEDS on both of these, which is the # ticket's expectation corrected by measurement. check_eq $P/malformed-truncated-constructs "constructed" "$(pv malformed_truncated_ctor)" check_eq $P/malformed-shoff-constructs "constructed" "$(pv malformed_shoff_ctor)" # ...and num_sections() answers from e_shnum without checking the file, so it # reports 5 sections for a file that physically holds one truncated header. # THIS IS THE TRAP a caller will hit, which is why it is asserted rather than # left as a remark. check_eq $P/malformed-truncated-num-sections "5" "$(pv malformed_truncated_num_sections)" check_eq $P/malformed-shoff-num-sections "5" "$(pv malformed_shoff_num_sections)" # The guarantee that DOES hold: every section access raises ELFError. check_eq $P/malformed-truncated-iter-raises "ELFError" "$(pv malformed_truncated_raises)" check_eq $P/malformed-shoff-iter-raises "ELFError" "$(pv malformed_shoff_raises)" check_eq $P/malformed-shoff-by-name-raises "ELFError" "$(pv malformed_shoff_by_name_raises)" # ...while the PROGRAM header table is untouched, so segments still parse out of # a file whose section table is destroyed. Stated as a case because a caller who # only wants segments can rely on it. check_eq $P/malformed-truncated-segments "PT_LOAD" "$(pv malformed_truncated_segments)" # THE EXCEPTION HIERARCHY IS THE CONTRACT. ELFParseError must stay under # ELFError or every `except ELFError' caller silently stops catching the # deferred faults above -- and DWARFError is NOT under it, which is the one a # caller gets wrong. See the header. check_eq $P/exc-parseerror-is-elferror "yes" "$(pv parseerror_is_elferror)" check_eq $P/exc-elferror-is-exception "yes" "$(pv elferror_is_exception)" check_eq $P/exc-dwarferror-not-elferror "no" "$(pv dwarferror_is_elferror)" # ================================================================= DWARF ===== # A fixture WITHOUT debug sections reports no DWARF -- the negative half, so # that `dwarf-has-dwarf' below is a measurement and not a tautology. check_eq $P/plain-has-no-dwarf "no" "$(pv plain_has_dwarf)" check_eq $P/dwarf-sections \ ".debug_abbrev,.debug_info,.debug_line" "$(pv dwarf_sections)" check_eq $P/dwarf-has-dwarf "yes" "$(pv dwarf_has_dwarf)" # DWARFInfo YIELDS COMPILE UNITS -- the ticket's DWARF assertion. One CU, which # is what the probe's .debug_info describes. check_eq $P/dwarf-num-cus "1" "$(pv num_cus)" check_eq $P/dwarf-cu-version "4" "$(pv cu_version)" check_eq $P/dwarf-cu-addr-size "8" "$(pv cu_address_size)" # The top DIE and its five attributes, each a value the probe encoded by hand # into .debug_abbrev + .debug_info. check_eq $P/dwarf-cu-tag "DW_TAG_compile_unit" "$(pv cu_tag)" check_eq $P/dwarf-cu-children "no" "$(pv cu_has_children)" check_eq $P/dwarf-cu-name "fixture.c" "$(pv cu_name)" check_eq $P/dwarf-cu-comp-dir "/fixture" "$(pv cu_comp_dir)" check_eq $P/dwarf-cu-low-pc "0x1000" "$(pv cu_low_pc)" check_eq $P/dwarf-cu-high-pc "64" "$(pv cu_high_pc)" check_eq $P/dwarf-cu-attr-count "5" "$(pv cu_attr_count)" # THE LINE PROGRAM DECODES TO A MONOTONE ADDRESS SEQUENCE -- the ticket's second # DWARF assertion. The addresses are the exact ones the probe's opcodes encode, # so this asserts the DECODER and not merely that something came back. check_eq $P/dwarf-lp-version "4" "$(pv lp_version)" check_eq $P/dwarf-lp-opcode-base "13" "$(pv lp_opcode_base)" check_eq $P/dwarf-lp-entries "4" "$(pv lp_entry_count)" check_eq $P/dwarf-lp-addresses \ "0x1000,0x1010,0x1020,0x1040" "$(pv lp_addresses)" check_eq $P/dwarf-lp-monotone "yes" "$(pv lp_monotone)" check_eq $P/dwarf-lp-lines "1,2,4,4" "$(pv lp_lines)" check_eq $P/dwarf-lp-end-sequence "yes" "$(pv lp_last_is_end_sequence)" check_eq $P/dwarf-lp-file-entry "fixture.c" "$(pv lp_file_entry)" # ============================================================ sub-modules ==== # The layers the ticket names, each imported inside the probe's single start. # An image that split the package wrongly fails HERE, once per layer, rather # than at some caller's first use. check_eq $P/mod-elffile "yes" "$(pv import_elftools_elf_elffile)" check_eq $P/mod-sections "yes" "$(pv import_elftools_elf_sections)" check_eq $P/mod-segments "yes" "$(pv import_elftools_elf_segments)" check_eq $P/mod-relocation "yes" "$(pv import_elftools_elf_relocation)" check_eq $P/mod-dynamic "yes" "$(pv import_elftools_elf_dynamic)" check_eq $P/mod-descriptions "yes" "$(pv import_elftools_elf_descriptions)" check_eq $P/mod-dwarfinfo "yes" "$(pv import_elftools_dwarf_dwarfinfo)" check_eq $P/mod-compileunit "yes" "$(pv import_elftools_dwarf_compileunit)" check_eq $P/mod-lineprogram "yes" "$(pv import_elftools_dwarf_lineprogram)" check_eq $P/mod-abbrevtable "yes" "$(pv import_elftools_dwarf_abbrevtable)" check_eq $P/mod-die "yes" "$(pv import_elftools_dwarf_die)" check_eq $P/mod-dwarf-descr "yes" "$(pv import_elftools_dwarf_descriptions)" check_eq $P/mod-ehabi "yes" "$(pv import_elftools_ehabi_ehabiinfo)" check_eq $P/mod-common-utils "yes" "$(pv import_elftools_common_utils)" # The probe reads and writes nothing outside its own BytesIO objects, so the # only thing to take away is this suite's own workdir. rm -rf "$W" check $P/workdir-gone test ! -d "$W"