. "$BST_LIB" # BST-TIMEOUT: 1800 # ELEVEN python3 starts, and they are the whole cost. # # Everything else here is `test', `grep' and `printf' in # # the shell. # # # # The eleven: a bare `import'; a second whose STDERR is the # # assertion; the measurement probe (ONE start, 245 # # measurements); the upstream unittest probe (ONE start, # # 120 of the package's own assertions); and seven # # invocations of the installed console scripts, which are # # separate processes by definition -- running them in- # # process would test importing them, which is not what the # # ticket asks for. # # # # The work inside each start is dictionary and string # # manipulation over fixtures under 200 bytes, except # # hex2bin's, which writes 17 bytes. Nothing here is I/O # # bound and nothing is quadratic; the interpreter start is # # the only term this number has to cover. # # # # 1800 is ~160s per start, against the ~10-30s a python3 # # start has measured at on this image across the ports that # # went before (99-python3-appdirs' three starts came in at # # 86s of a 1200s bound). # # # # A suite-timeout red costs the whole batch a round, so if # # the 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 goes in # # the test plan rather than in this comment, which would # # cost an image build to keep the shipped suite # # byte-identical. # # python3-intelhex 2.3.0 (meta-openembedded meta-python/recipes-devtools/python, # python3-intelhex_2.3.0.bb, `inherit pypi setuptools3', PyPI sdist) on the # wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE ACTUALLY IS, AND THEREFORE WHAT HAS TO BE TESTED. # ============================================================================ # # intelhex READS AND WRITES INTEL HEX FILES. The subject is a TEXT FORMAT and a # SPARSE ADDRESS SPACE: a HEX file is a sequence of `:'-prefixed records, each # carrying a length byte, a 16-bit offset, a type, its data and a checksum, and # the object it parses into is a dict from address to byte with HOLES IN IT. # Six record types matter and each one is a case below. # # It is a PACKAGE, not a flat module -- `intelhex/', with __init__.py, # compat.py, getsizeof.py, __main__.py, and the package's OWN unittest suite # shipped as test.py. It also installs SIX console scripts into ${bindir}, with # a `.py' suffix, because setup.py declares `scripts=glob.glob("scripts/*")' # rather than entry_points. The ticket names four of them; there are six, and # all six are covered. # # PURE PYTHON, WHICH IS WHY IT PORTS. 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. intelhex has # none: its imports are array, binascii, bisect, os, sys and (inside one # function) difflib, and the built package carries no .so. # # NO PATCH, NO BBAPPEND AND NO SITE ANSWER. The recipe is meta-openembedded's, # taken exactly as written -- the port is the IMAGE_INSTALL line, this suite and # two probes. There is no configure step, no compile step and no C in the # package, so there is nothing on this arm for an override to reach. # # ============================================================================ # EVERY FIXTURE IS WRITTEN BY THIS SUITE OR BY ITS PROBE. # ============================================================================ # # Nothing here reads a HEX file that shipped with anything. The probe writes its # records as literal strings and parses them through a StringIO; the console- # script cases write theirs with `printf' into a workdir under ${TMPDIR:-/tmp}. # So the suite reaches no device, no peer and no terminal, and it is # deterministic: every expectation is a literal, and every literal was MEASURED # against intelhex 2.3.0 under CPython 3.12 on the build host -- first against # the unpacked sdist and then again against the package extracted from the built # .deb, which agreed on all 245. # # ============================================================================ # THE PACKAGE'S OWN TEST SUITE IS SHIPPED, AND IS RUN HERE. # ============================================================================ # # `intelhex/test.py' is installed on the image because setup.py packages the # whole directory. The second probe loads it and runs 120 of its 126 cases # against the INSTALLED intelhex. That is 120 assertions written by the people # who wrote the library, executed on this arm, for one interpreter start. # # The six it does not run are TestInSubprocess, and they are excluded for a # reason that is not "subprocess is unavailable": they invoke `setup.py' and # `scripts/bin2hex.py' RELATIVE TO THE CURRENT DIRECTORY, and neither file is # installed by the package. They test an unpacked sdist, not an image. The # installed console scripts are covered below instead, from their real paths. # # ============================================================================ # SEVEN BEHAVIOURS A CALLER WILL GET WRONG. THESE ARE THE POINT OF THE SUITE. # ============================================================================ # # A FILE WITH NO END-OF-FILE RECORD PARSES CLEANLY. # The ticket predicted an IntelHexError subclass here. There # is none. loadhex's loop simply runs out of lines; nothing # checks that the internal _EndOfFile was ever raised. So a # TRUNCATED HEX FILE IS INDISTINGUISHABLE FROM A COMPLETE # ONE, which is the single most useful thing this port has to # say to a caller who is validating input. `no-eof-record-*'. # # THERE IS NO frombinstr. # The ticket names `tobinstr/frombinstr' as a pair. tobinstr # exists; frombinstr does not, in this release or any other. # The ways back in are frombytes, puts and loadbin, and all # three are asserted so that a release which adds frombinstr # shows up as one new red rather than as a silent change of # shape. `has-frombinstr', `has-frombytes'. # # THE DECODER'S CHECKS ARE ORDERED length, type, checksum. # A record that is wrong in two ways reports the EARLIER # check. So a caller diagnosing a corrupt file by exception # class is told the wrong thing about exactly the files that # are most corrupt. `err-type-beats-checksum', # `err-length-beats-type'. # # dump() ON A HALF-SET start_addr RAISES NameError. # The branch handling "neither {CS,IP} nor {EIP}" formats a # bare `start_addr', which is not a name in that scope. One # assignment from public API reaches it. The WRITER rejects # the same object properly, with the documented exception, so # reader and writer disagree about it and both are asserted # side by side. `quirk-dump-partial-start', # `quirk-write-partial-start'. # # THE DEPRECATED `pad' ARGUMENT PRINTS TO STDOUT. # It still works. Passing pad= is honoured and emits # one notice line; passing pad=None emits THREE and then # ignores the argument. On STDOUT, not stderr -- so a script # piping tobinstr's caller has its output corrupted by a # deprecation notice. `deprecated-pad', `deprecated-pad-none'. # # THERE IS NO ADDRESS WRAP AFTER A TYPE-02 RECORD. # The decoder carries its own FIXME saying so. A data record # running past the 64 KiB boundary keeps counting linearly, so # bytes land at 0x100000 where a conforming reader puts them # at 0xF0000. `nowrap-addresses'. # # EmptyIntelHexError IS DEFINED AND UNREACHABLE. # Every caller of the one site that raises it is guarded by an # earlier early-return, so the empty object whose docstring # promises the exception answers with an empty array. # `empty-tobinarray-returns'. # # ============================================================================ # WHAT IS NOT ASSERTED, AND WHY. # ============================================================================ # # Nothing here asserts elapsed time, and nothing asserts the ABSENCE of a shared # object by name (bst_lib_shape carries that, per BRINTOS-1740). The `bench' # submodule is imported but not run: it is timing code the package's own setup.py # calls "out of date", and a benchmark on a shared build host measures the host. P=python3-intelhex # suite-canary is the first line of output: it says the file was reached and # $BST_LIB sourced. Without it a suite that dies on its next line is # indistinguishable from one that never ran. bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKG=$SITE/intelhex DI=$SITE/intelhex-2.3.0.dist-info PKGLIST=/var/lib/dpkg/info/python3-intelhex.list PROBE=$BST_DATA/python3-intelhex-probe.py UPROBE=$BST_DATA/python3-intelhex-upstream.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-intelhex.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # --------------------------------------------------------------- what shipped check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKG" check $P/not-a-flat-module test ! -e "$SITE/intelhex.py" # The five modules the package installs. test.py is one of them and is the whole # reason the upstream probe below has anything to run. for f in __init__ compat getsizeof __main__ test bench; do check "$P/module-$f" test -s "$PKG/$f.py" done check $P/module-version test -s "$PKG/__version__.py" # The .pyc are built at do_install, not on first import: site-packages is 0644 # under a root the guest cannot write, so a missing cache would mean every # invocation recompiles, on every start, forever. check $P/pyc-init test -s "$PKG/__pycache__/__init__.cpython-312.pyc" check $P/pyc-compat test -s "$PKG/__pycache__/compat.cpython-312.pyc" check $P/pyc-test test -s "$PKG/__pycache__/test.cpython-312.pyc" cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # NO SHARED OBJECT. This image's python3 has an empty lib-dynload and no dynamic # loader, so a .so in this package would be an unimportable file rather than a # slow one. bst_lib_shape is the shape assertion BRINTOS-1740 left standing; # this package ships nothing for it to find, and that is the answer. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" bst_lib_shape $P/lib-shape "$PKG/*.so" "$SITE/intelhex*.so" # Everything the package installs is a .py, a .pyc, dist-info metadata, or one # of the six console scripts in ${bindir}. Anything else is a packaging change # nobody meant. _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" 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.3.0" cat "$DI/METADATA" check_out $P/dist-info-name "Name: intelhex" cat "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check_out $P/dist-info-top-level "intelhex" cat "$DI/top_level.txt" sed -n '/^Package: python3-intelhex$/,/^$/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: 2.3.0-r0" cat "$W/st" # The RDEPENDS the recipe declares, checked against the built package rather # than read off the recipe. intelhex imports array, binascii, bisect, os, sys # and difflib; if any of those left python3-core into a split package this line # is where it shows. grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" # The modules the package imports, one case each. A red here is a python3 # packaging split, not an intelhex defect, and naming them separately is what # makes that legible. # ONE start for all four: they are asserted together because they fail # together -- a python3 split that dropped one of them dropped the .py stdlib. check $P/stdlib-imports python3 -c 'import array, binascii, bisect, difflib' # ------------------------------------------------------------------ it imports # IntelHex, IntelHex16bit and the exception classes are imported by the probe # below, in the start it was going to make anyway; this is the bare package. check $P/import python3 -c 'import intelhex' # A clean import prints NOTHING. A DeprecationWarning or a SyntaxWarning out of # python3.12 would land on every start of every caller. check_eq $P/import-quiet "" \ "$(python3 -c 'import intelhex' 2>&1)" # ================================================================== the probe # ONE python3 start, 245 measurements. It writes every fixture itself, parses # through StringIO, and prints `key=value' as each measurement is taken rather # than buffering to the end -- a probe that died at measurement 150 must still # hand this file the 149 before it. 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 # probe-complete and probe-count are the two cases that say whether the 242 # below mean anything: the first that the probe reached its last line, the # second that it took every measurement on the way. A partial transcript would # otherwise turn into a run of `got:'''' reds naming symptoms. check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "245" "$(pv emitted_count)" check_eq $P/probe-tmpdir "yes" "$(pv probe_tmpdir_exists)" # The package answered from the image's site-packages, not from a copy the probe # dragged in. Everything below is a statement about THAT file. check_eq $P/module-answered "$PKG/__init__.py" "$(pv module_file)" check_eq $P/package-answered "$PKG" "$(pv package_dir)" # ---------------------------------------------------------- what was imported # `import intelhex' binds NO version. __init__.py imports neither __version__ # nor anything out of it, so `intelhex.__version__' is an AttributeError until # the SUBMODULE of that name is imported by hand -- and once it is, the # attribute is a MODULE, never the '2.3.0' a caller reaching for it expects. check_eq $P/is-package 'yes' "$(pv is_package)" check_eq $P/bare-version-attr 'no' "$(pv bare_version_attr)" check_eq $P/version-attr-is-module 'yes' "$(pv version_attr_is_module)" check_eq $P/version-str '2.3.0' "$(pv version_str)" check_eq $P/version-info '2.3.0' "$(pv version_info)" check_eq $P/version-info-is-tuple 'yes' "$(pv version_info_is_tuple)" check_eq $P/docformat 'javadoc' "$(pv docformat)" check_eq $P/has-dunder-all 'no' "$(pv has_dunder_all)" check_eq $P/compat-python '3' "$(pv compat_python)" check_eq $P/compat-strtype 'str' "$(pv compat_strtype)" check_eq $P/compat-inttypes 'int' "$(pv compat_inttypes)" check_eq $P/submodule-compat 'yes' "$(pv submodule_compat)" check_eq $P/submodule-getsizeof 'intelhex.getsizeof' "$(pv submodule_getsizeof)" check_eq $P/submodule-test-import 'intelhex.test' "$(pv submodule_test_import)" check_eq $P/submodule-bench-import 'intelhex.bench' "$(pv submodule_bench_import)" # ------------------------------------------------- one case per record type # Every fixture below is HEX TEXT the probe writes itself and parses through a # StringIO, so each expectation is a statement about the decoder rather than # about this image's filesystem. The records were built with intelhex's own # Record helpers and are pasted in as literals, checksums included. # # type 00 data # type 01 end of file # type 02 extended SEGMENT address -- a x16 offset, NOT x65536 # type 03 start segment address -> start_addr {'CS','IP'} # type 04 extended LINEAR address -- x65536, what moves the base over 64 KiB # type 05 start linear address -> start_addr {'EIP'} check_eq $P/data-len '4' "$(pv data_len)" check_eq $P/data-minaddr '0x1000' "$(pv data_minaddr)" check_eq $P/data-maxaddr '0x1003' "$(pv data_maxaddr)" check_eq $P/data-bytes 'ABCD' "$(pv data_bytes)" check_eq $P/data-first '0x41' "$(pv data_first)" check_eq $P/data-addresses '1000,1001,1002,1003' "$(pv data_addresses)" check_eq $P/data-start-addr 'None' "$(pv data_start_addr)" check_eq $P/ela-minaddr '0x00010000' "$(pv ela_minaddr)" check_eq $P/ela-maxaddr '0x00010003' "$(pv ela_maxaddr)" check_eq $P/ela-byte '0x41' "$(pv ela_byte)" check_eq $P/ela-len '4' "$(pv ela_len)" check_eq $P/ela-above-64k 'yes' "$(pv ela_above_64k)" check_eq $P/esa-minaddr '0x00010000' "$(pv esa_minaddr)" check_eq $P/esa-offset-is-x16 'yes' "$(pv esa_offset_is_x16)" check_eq $P/ssa-keys 'CS,IP' "$(pv ssa_keys)" check_eq $P/ssa-cs '0x0010' "$(pv ssa_cs)" check_eq $P/ssa-ip '0x0020' "$(pv ssa_ip)" check_eq $P/sla-keys 'EIP' "$(pv sla_keys)" check_eq $P/sla-eip '0x08000000' "$(pv sla_eip)" check_eq $P/sla-present 'yes' "$(pv sla_present)" # The END-OF-FILE RECORD STOPS THE READER, and it stops it MID-FILE: the data # record after it is never seen. loadhex catches the internal _EndOfFile and # returns, so trailing records are silently discarded rather than rejected. check_eq $P/eof-stops-reader '4' "$(pv eof_stops_reader)" check_eq $P/eof-stops-addrs '1000,1001,1002,1003' "$(pv eof_stops_addrs)" # ------------------------------------------------------- the round trip # Build an IntelHex in memory, write_hex_file it, read it back, and assert the # address-to-byte mapping is identical INCLUDING THE GAPS. The fixture has two # holes and data above 64 KiB, so the writer has to emit an extended-linear # record and the reader has to consume it for the two dicts to agree. check_eq $P/rt-text-lines '7' "$(pv rt_text_lines)" check_eq $P/rt-dict-identical 'yes' "$(pv rt_dict_identical)" check_eq $P/rt-len '7' "$(pv rt_len)" check_eq $P/rt-minaddr '0x00000000' "$(pv rt_minaddr)" check_eq $P/rt-maxaddr '0x00012346' "$(pv rt_maxaddr)" check_eq $P/rt-addresses '0,1,2,10,11,12345,12346' "$(pv rt_addresses)" check_eq $P/rt-start-addr '{'\''EIP'\'': 134217728}' "$(pv rt_start_addr)" check_eq $P/rt-holes-preserved 'yes' "$(pv rt_holes_preserved)" check_eq $P/rt-hole-reads-padding '0xFF' "$(pv rt_hole_reads_padding)" check_eq $P/rt-first-record ':0400000508000000EF' "$(pv rt_first_record)" check_eq $P/rt-last-record ':00000001FF' "$(pv rt_last_record)" check_eq $P/rt-has-ela 'yes' "$(pv rt_has_ela)" check_eq $P/rt-segments '[(0, 3), (16, 18), (74565, 74567)]' "$(pv rt_segments)" check_eq $P/rt-segments-min-gap-32 '[(0, 18), (74565, 74567)]' "$(pv rt_segments_min_gap_32)" check_eq $P/rt-text-sha-len '114' "$(pv rt_text_sha_len)" check_eq $P/rt-eol-native 'LF' "$(pv rt_eol_native)" # eolstyle='CRLF' gives \r\n on every platform that is not win32 -- inverted, # because on win32 the file is opened in text mode and the runtime adds the CR. # This image is not win32, so CRLF here means literal CRLF. check_eq $P/rt-crlf-has-cr 'yes' "$(pv rt_crlf_has_cr)" check_eq $P/rt-crlf-same-records 'yes' "$(pv rt_crlf_same_records)" check_eq $P/rt-eolstyle-bogus 'ValueError' "$(pv rt_eolstyle_bogus)" # byte_count is the data field width and is bounded to 1..255. Both extremes # round-trip to the same dict; only the record count changes. check_eq $P/rt-bc1-dict-identical 'yes' "$(pv rt_bc1_dict_identical)" check_eq $P/rt-bc1-lines '11' "$(pv rt_bc1_lines)" check_eq $P/rt-bc255-dict-identical 'yes' "$(pv rt_bc255_dict_identical)" check_eq $P/rt-bc-zero 'ValueError' "$(pv rt_bc_zero)" check_eq $P/rt-bc-256 'ValueError' "$(pv rt_bc_256)" check_eq $P/rt-bc-zero-msg 'ValueError|wrong byte_count value: 0' "$(pv rt_bc_zero_msg)" # write_start_addr=False drops the start-address record and nothing else. check_eq $P/rt-nostart-start-addr 'None' "$(pv rt_nostart_start_addr)" check_eq $P/rt-nostart-dict-data 'yes' "$(pv rt_nostart_dict_data)" check_eq $P/rt-nostart-lines '6' "$(pv rt_nostart_lines)" # The same round trip through a real file rather than a StringIO -- this is the # only place the port's data path touches the filesystem, and it is under # TMPDIR, which is tmpfs on this image. check_eq $P/file-rt-dict-identical 'yes' "$(pv file_rt_dict_identical)" check_eq $P/file-rt-exists 'yes' "$(pv file_rt_exists)" # ------------------------------------ tobinstr / tobinarray over a hole # The fixture is DE AD -- -- BE EF: two bytes, a two-byte hole, two bytes. The # hole is what `padding' fills, and the default is 0xFF. check_eq $P/bin-default-pad '0xFF' "$(pv bin_default_pad)" check_eq $P/bin-str-default 'DEADFFFFBEEF' "$(pv bin_str_default)" check_eq $P/bin-arr-default 'DE,AD,FF,FF,BE,EF' "$(pv bin_arr_default)" check_eq $P/bin-arr-typecode 'B' "$(pv bin_arr_typecode)" check_eq $P/bin-len '6' "$(pv bin_len)" check_eq $P/bin-pad-00 'DEAD0000BEEF' "$(pv bin_pad_00)" check_eq $P/bin-pad-5A 'DEAD5A5ABEEF' "$(pv bin_pad_5A)" check_eq $P/bin-str-range 'ADFFFFBE' "$(pv bin_str_range)" check_eq $P/bin-str-size 'DEADFF' "$(pv bin_str_size)" check_eq $P/bin-str-size-from-end 'BEEF' "$(pv bin_str_size_from_end)" check_eq $P/bin-size-and-both 'ValueError' "$(pv bin_size_and_both)" check_eq $P/bin-size-zero 'ValueError' "$(pv bin_size_zero)" check_eq $P/bin-size-negative 'ValueError' "$(pv bin_size_negative)" check_eq $P/bin-reversed-range 'DEADFFFFBEEF' "$(pv bin_reversed_range)" check_eq $P/bin-empty-object 'b'\'''\''' "$(pv bin_empty_object)" check_eq $P/bin-empty-with-range 'FFFFFF' "$(pv bin_empty_with_range)" # THERE IS NO frombinstr. tobinstr has no inverse of that name in 2.3.0; the # ways back in are frombytes, puts and loadbin, and all three are asserted here # so a release that finally adds frombinstr is visible as one new case rather # than as a silent change of shape. check_eq $P/has-frombinstr 'no' "$(pv has_frombinstr)" check_eq $P/has-frombytes 'yes' "$(pv has_frombytes)" check_eq $P/has-loadbin 'yes' "$(pv has_loadbin)" check_eq $P/has-puts 'yes' "$(pv has_puts)" check_eq $P/frombytes-rt 'DEADBEEF@10' "$(pv frombytes_rt)" check_eq $P/loadbin-rt '01020304@20' "$(pv loadbin_rt)" # THE DEPRECATED `pad' ARGUMENT STILL WORKS, AND ITS NOTICE GOES TO STDOUT. # Passing pad= is honoured and prints one line; passing pad=None prints # THREE and then ignores the argument, using self.padding. Not passing it prints # nothing. A caller who pipes a tobinstr-driven script has its stdout polluted # by a deprecation notice, which is the failure worth stating. check_eq $P/deprecated-pad \ 'DEAD1111BEEF|lines=2|first=IntelHex.tobinstr: '\''pad'\'' parameter is deprecated.' \ "$(pv deprecated_pad)" check_eq $P/deprecated-pad-none 'DEADFFFFBEEF|lines=3' "$(pv deprecated_pad_none)" check_eq $P/undeprecated-quiet 'lines=0' "$(pv undeprecated_quiet)" # ========================================================================= # THE ERROR PATHS. Each is a case of its own, and each names its class. # ========================================================================= check_eq $P/err-bad-checksum 'RecordChecksumError' "$(pv err_bad_checksum)" check_eq $P/err-bad-checksum-msg \ 'RecordChecksumError|Record at line 1 has invalid checksum' \ "$(pv err_bad_checksum_msg)" check_eq $P/err-bad-length 'RecordLengthError' "$(pv err_bad_length)" check_eq $P/err-bad-length-msg \ 'RecordLengthError|Record at line 1 has invalid length' \ "$(pv err_bad_length_msg)" check_eq $P/err-truncated 'HexRecordError' "$(pv err_truncated)" check_eq $P/err-truncated-msg \ 'HexRecordError|Hex file contains invalid record at line 1' \ "$(pv err_truncated_msg)" check_eq $P/err-too-short 'HexRecordError' "$(pv err_too_short)" check_eq $P/err-odd-digits 'HexRecordError' "$(pv err_odd_digits)" check_eq $P/err-no-colon 'HexRecordError' "$(pv err_no_colon)" check_eq $P/err-garbage-line 'HexRecordError' "$(pv err_garbage_line)" check_eq $P/err-bad-rectype 'RecordTypeError' "$(pv err_bad_rectype)" check_eq $P/err-bad-rectype-msg \ 'RecordTypeError|Record at line 1 has invalid record type' \ "$(pv err_bad_rectype_msg)" check_eq $P/err-eof-with-data 'EOFRecordError' "$(pv err_eof_with_data)" check_eq $P/err-esa-bad-len 'ExtendedSegmentAddressRecordError' "$(pv err_esa_bad_len)" check_eq $P/err-esa-bad-offset 'ExtendedSegmentAddressRecordError' "$(pv err_esa_bad_offset)" check_eq $P/err-ela-bad-len 'ExtendedLinearAddressRecordError' "$(pv err_ela_bad_len)" check_eq $P/err-ssa-bad-len 'StartSegmentAddressRecordError' "$(pv err_ssa_bad_len)" check_eq $P/err-sla-bad-len 'StartLinearAddressRecordError' "$(pv err_sla_bad_len)" check_eq $P/err-overlap 'AddressOverlapError' "$(pv err_overlap)" check_eq $P/err-overlap-msg \ 'AddressOverlapError|Hex file has data overlap at address 0x1000 on line 2' \ "$(pv err_overlap_msg)" check_eq $P/err-dup-start-sla 'DuplicateStartAddressRecordError' "$(pv err_dup_start_sla)" check_eq $P/err-dup-start-ssa 'DuplicateStartAddressRecordError' "$(pv err_dup_start_ssa)" # THE CHECKS ARE ORDERED, AND THE ORDER IS NOT THE ONE A READER ASSUMES: # length, then record type, then checksum. A record that is wrong in two ways # reports the EARLIER check, so diagnosing a corrupt file by exception class # gives the wrong answer for exactly the files that are most corrupt. check_eq $P/err-type-beats-checksum 'RecordTypeError' "$(pv err_type_beats_checksum)" check_eq $P/err-length-beats-type 'RecordLengthError' "$(pv err_length_beats_type)" # A HEX FILE WITH NO END-OF-FILE RECORD PARSES CLEANLY, AND THAT IS NOT WHAT # THE TICKET PREDICTED. loadhex's loop simply runs out of lines; nothing checks # that the internal _EndOfFile was ever raised. So the terminator the format # requires is optional in practice, and a truncated file is indistinguishable # from a complete one. MEASURED, not read off the source. check_eq $P/no-eof-record-len '4' "$(pv no_eof_record_len)" check_eq $P/no-eof-record-bytes 'ABCD' "$(pv no_eof_record_bytes)" check_eq $P/no-eof-record-raises 'no' "$(pv no_eof_record_raises)" check_eq $P/empty-file-len '0' "$(pv empty_file_len)" check_eq $P/blank-lines-ignored '4' "$(pv blank_lines_ignored)" check_eq $P/crlf-input-accepted '4' "$(pv crlf_input_accepted)" # gets / getsz refuse to read across a hole -- padding is NOT used for these, # unlike __getitem__ and tobinstr. check_eq $P/err-gets-hole 'NotEnoughDataError' "$(pv err_gets_hole)" check_eq $P/err-gets-hole-msg \ 'NotEnoughDataError|Bad access at 0x0: not enough data to read 6 contiguous bytes' \ "$(pv err_gets_hole_msg)" check_eq $P/gets-ok 'DEAD' "$(pv gets_ok)" check_eq $P/putsz-getsz 'hi|3|00' "$(pv putsz_getsz)" check_eq $P/err-getsz-no-zero 'NotEnoughDataError' "$(pv err_getsz_no_zero)" # Indexing. A negative address is a TypeError (not an IndexError or a # ValueError), and an absent address reads back as `padding' rather than # raising, which is what makes __getitem__ unusable for asking whether an # address is occupied. check_eq $P/err-getitem-negative 'TypeError' "$(pv err_getitem_negative)" check_eq $P/err-getitem-negative-msg \ 'TypeError|Address should be >= 0.' \ "$(pv err_getitem_negative_msg)" check_eq $P/err-getitem-str 'TypeError' "$(pv err_getitem_str)" check_eq $P/err-setitem-negative 'TypeError' "$(pv err_setitem_negative)" check_eq $P/getitem-absent-is-padding '0xFF' "$(pv getitem_absent_is_padding)" # EmptyIntelHexError IS DEFINED AND, IN 2.3.0, UNREACHABLE. _get_start_end is # the only site that raises it, and every caller is guarded by an earlier # `if self._buf == {} and None in (start, end): return bin'. So the empty object # whose documentation promises the exception answers with an empty array # instead. The class and its format string are asserted so the day it becomes # reachable is one red case rather than a silent behaviour change. check_eq $P/empty-tobinarray-returns 'array('\''B'\'')' "$(pv empty_tobinarray_returns)" check_eq $P/empty-tobinarray-len '0' "$(pv empty_tobinarray_len)" check_eq $P/empty-tobinarray-with-size 'array('\''B'\'')' "$(pv empty_tobinarray_with_size)" check_eq $P/empty-error-class-exists 'yes' "$(pv empty_error_class_exists)" check_eq $P/empty-error-fmt \ 'Requested operation cannot be executed with empty object' \ "$(pv empty_error_fmt)" check_eq $P/empty-minaddr 'None' "$(pv empty_minaddr)" check_eq $P/empty-maxaddr 'None' "$(pv empty_maxaddr)" check_eq $P/empty-len '0' "$(pv empty_len)" check_eq $P/empty-segments '[]' "$(pv empty_segments)" check_eq $P/empty-addresses '[]' "$(pv empty_addresses)" # ---------------------------------------- the exception hierarchy as documented # The module's own comment block draws this tree. Each line asserts one edge of # it, so a release that re-parents an exception breaks the one case that names # that edge rather than every case that catches through it. check_eq $P/hier-HexReaderError 'yes' "$(pv hier_HexReaderError)" check_eq $P/hier-AddressOverlapError 'yes' "$(pv hier_AddressOverlapError)" check_eq $P/hier-HexRecordError 'yes' "$(pv hier_HexRecordError)" check_eq $P/hier-RecordLengthError 'yes' "$(pv hier_RecordLengthError)" check_eq $P/hier-RecordTypeError 'yes' "$(pv hier_RecordTypeError)" check_eq $P/hier-RecordChecksumError 'yes' "$(pv hier_RecordChecksumError)" check_eq $P/hier-EOFRecordError 'yes' "$(pv hier_EOFRecordError)" check_eq $P/hier-ExtendedAddressRecordError 'yes' "$(pv hier_ExtendedAddressRecordError)" check_eq $P/hier-ExtendedSegmentAddressRecordError \ 'yes' \ "$(pv hier_ExtendedSegmentAddressRecordError)" check_eq $P/hier-ExtendedLinearAddressRecordError \ 'yes' \ "$(pv hier_ExtendedLinearAddressRecordError)" check_eq $P/hier-StartAddressRecordError 'yes' "$(pv hier_StartAddressRecordError)" check_eq $P/hier-StartSegmentAddressRecordError 'yes' "$(pv hier_StartSegmentAddressRecordError)" check_eq $P/hier-StartLinearAddressRecordError 'yes' "$(pv hier_StartLinearAddressRecordError)" check_eq $P/hier-DuplicateStartAddressRecordError \ 'yes' \ "$(pv hier_DuplicateStartAddressRecordError)" check_eq $P/hier-InvalidStartAddressValueError 'yes' "$(pv hier_InvalidStartAddressValueError)" check_eq $P/hier-NotEnoughDataError 'yes' "$(pv hier_NotEnoughDataError)" check_eq $P/hier-BadAccess16bit 'yes' "$(pv hier_BadAccess16bit)" check_eq $P/hier-EmptyIntelHexError 'yes' "$(pv hier_EmptyIntelHexError)" check_eq $P/hier-base-is-exception 'yes' "$(pv hier_base_is_exception)" check_eq $P/hier-all-from-base 'yes' "$(pv hier_all_from_base)" check_eq $P/hier-endoffile-is-error 'yes' "$(pv hier_endoffile_is_error)" # IntelHexError's constructor takes a message OR keyword arguments, and every # keyword becomes an ATTRIBUTE. With neither, __str__ renders _fmt against # __dict__ -- and a subclass whose _fmt needs `line' when no line was passed # answers the literal text 'Unprintable exception ...' rather than raising. check_eq $P/err-base-fmt 'IntelHex base error' "$(pv err_base_fmt)" check_eq $P/err-base-str 'IntelHex base error' "$(pv err_base_str)" check_eq $P/err-msg-wins 'a message' "$(pv err_msg_wins)" check_eq $P/err-kw-becomes-attr '7' "$(pv err_kw_becomes_attr)" check_eq $P/err-unprintable 'Unprintable exception KeyE' "$(pv err_unprintable)" # ========================================================================= # THE QUIRKS. THESE ARE THE POINT OF THE SUITE. # ========================================================================= # # dump() ON A HALF-SET start_addr RAISES NameError. The branch that handles # "neither {CS,IP} nor {EIP}" formats a bare `start_addr', which is not a name # in that scope -- self.start_addr is. It is reachable from public API in one # assignment, and the exception a caller gets tells them nothing about what # they did wrong. check_eq $P/quirk-dump-partial-start 'NameError' "$(pv quirk_dump_partial_start)" # The two start_addr shapes dump() DOES handle, and the body format: `--' for an # unset byte, the padding byte with withpadding=True. check_eq $P/dump-eip-header 'EIP = 0x08000000' "$(pv dump_eip_header)" check_eq $P/dump-csip-header 'CS = 0x0010, IP = 0x0020' "$(pv dump_csip_header)" check_eq $P/dump-body-line \ '0000 DE AD -- -- BE EF -- -- -- -- -- -- -- -- -- -- |.. .. |' \ "$(pv dump_body_line)" check_eq $P/dump-body-withpadding \ '0000 DE AD FF FF BE EF FF FF FF FF FF FF FF FF FF FF |................|' \ "$(pv dump_body_withpadding)" check_eq $P/dump-bad-width 'ValueError' "$(pv dump_bad_width)" # The same half-set start_addr is REJECTED PROPERLY by the writer, with the # documented exception. Reader and writer disagree about the same object, which # is why both are asserted side by side. check_eq $P/quirk-write-partial-start \ 'InvalidStartAddressValueError' \ "$(pv quirk_write_partial_start)" check_eq $P/quirk-write-partial-start-msg \ 'InvalidStartAddressValueError|Invalid start address value: {'\''CS'\'': 16}' \ "$(pv quirk_write_partial_start_msg)" # NO ADDRESS WRAP AFTER A TYPE-02 RECORD -- the decoder's own FIXME, asserted. # A data record that runs past the 64 KiB boundary keeps counting linearly # instead of wrapping to the segment base, so bytes land at 0x100000 and # 0x100001 where a conforming reader would put them at 0xF0000 and 0xF0001. check_eq $P/nowrap-addresses 'FFFFE,FFFFF,100000,100001' "$(pv nowrap_addresses)" check_eq $P/nowrap-maxaddr '0x00100001' "$(pv nowrap_maxaddr)" check_eq $P/nowrap-is-linear 'yes' "$(pv nowrap_is_linear)" # merge(): the three overlap modes, self-merge, and the separate rule the # start_addr follows -- adopted from the other object when this one has none, # and a conflict under 'error' that reports as an ADDRESS overlap. check_eq $P/merge-replace '0:01,1:FF,2:03' "$(pv merge_replace)" check_eq $P/merge-ignore '0:01,1:02,2:03' "$(pv merge_ignore)" check_eq $P/merge-error 'AddressOverlapError' "$(pv merge_error)" check_eq $P/merge-bad-mode 'ValueError' "$(pv merge_bad_mode)" check_eq $P/merge-self 'ValueError' "$(pv merge_self)" check_eq $P/merge-not-intelhex 'TypeError' "$(pv merge_not_intelhex)" check_eq $P/merge-start-adopted '{'\''EIP'\'': 4660}' "$(pv merge_start_adopted)" check_eq $P/merge-start-conflict-error 'AddressOverlapError' "$(pv merge_start_conflict_error)" check_eq $P/merge-start-conflict-replace '{'\''EIP'\'': 2}' "$(pv merge_start_conflict_replace)" check_eq $P/merge-start-conflict-ignore '{'\''EIP'\'': 1}' "$(pv merge_start_conflict_ignore)" # find() searches segment by segment, so it CANNOT match across a hole even # when the bytes either side of it are the ones asked for. check_eq $P/find-hit '0x04' "$(pv find_hit)" check_eq $P/find-miss '-1' "$(pv find_miss)" check_eq $P/find-across-hole '-1' "$(pv find_across_hole)" # Slice get / set / delete, and the three ways a slice assignment is refused. check_eq $P/slice-get '0:DE,1:AD' "$(pv slice_get)" check_eq $P/slice-set '10:01,11:02,12:03' "$(pv slice_set)" check_eq $P/slice-set-bad-len 'ValueError' "$(pv slice_set_bad_len)" check_eq $P/slice-set-not-seq 'ValueError' "$(pv slice_set_not_seq)" check_eq $P/slice-set-open 'TypeError' "$(pv slice_set_open)" check_eq $P/slice-del '4,5' "$(pv slice_del)" check_eq $P/item-del '1,4,5' "$(pv item_del)" check_eq $P/item-del-absent 'KeyError' "$(pv item_del_absent)" # IntelHex16bit reads pairs little-endian and SIGN-EXTENDS THE PADDING, but # only when it was exactly 0xFF: a padding a caller set to 0x00 is left alone, # so the 8-bit and 16-bit views of the same object disagree about what an # unset word reads as. A pair with only one byte set raises rather than padding. check_eq $P/h16-word '0x1234' "$(pv h16_word)" check_eq $P/h16-padding-extended '0xFFFF' "$(pv h16_padding_extended)" check_eq $P/h16-minaddr '0' "$(pv h16_minaddr)" check_eq $P/h16-maxaddr '0' "$(pv h16_maxaddr)" check_eq $P/h16-absent-is-padding '0xFFFF' "$(pv h16_absent_is_padding)" check_eq $P/h16-typecode 'H' "$(pv h16_typecode)" check_eq $P/h16-tobinarray '1234' "$(pv h16_tobinarray)" check_eq $P/h16-half-word-raises 'BadAccess16bit' "$(pv h16_half_word_raises)" check_eq $P/h16-setitem-little-endian 'EFBE' "$(pv h16_setitem_little_endian)" check_eq $P/h16-from-dict 'IntelHexError' "$(pv h16_from_dict)" check_eq $P/h16-padding-not-extended '0x0000' "$(pv h16_padding_not_extended)" # The Record helpers build the same literals this suite pastes in by hand, which # is how the fixtures above were derived. Their argument checks are `assert', # so they vanish under python3 -O. check_eq $P/rec-eof ':00000001FF' "$(pv rec_eof)" check_eq $P/rec-data ':0410000041424344E2' "$(pv rec_data)" check_eq $P/rec-esa ':020000021000EC' "$(pv rec_esa)" check_eq $P/rec-ela ':020000040001F9' "$(pv rec_ela)" check_eq $P/rec-ssa ':0400000300100020C9' "$(pv rec_ssa)" check_eq $P/rec-sla ':0400000508000000EF' "$(pv rec_sla)" check_eq $P/rec-data-roundtrips 'yes' "$(pv rec_data_roundtrips)" check_eq $P/rec-data-empty 'AssertionError' "$(pv rec_data_empty)" check_eq $P/rec-data-offset-high 'AssertionError' "$(pv rec_data_offset_high)" # The private file:start:end notation hexmerge.py is built on. check_eq $P/far-plain '('\''a.hex'\'', None, None)' "$(pv far_plain)" check_eq $P/far-range '('\''a.hex'\'', 0, 255)' "$(pv far_range)" check_eq $P/far-open-start '('\''a.hex'\'', None, 255)' "$(pv far_open_start)" check_eq $P/far-open-end '('\''a.hex'\'', 16, None)' "$(pv far_open_end)" check_eq $P/far-two-parts '_BadFileNotation' "$(pv far_two_parts)" check_eq $P/far-bad-hex '_BadFileNotation' "$(pv far_bad_hex)" check_eq $P/far-drive-letter '('\''C:\\a.hex'\'', 1, 10)' "$(pv far_drive_letter)" # The module-level convertors RETURN a status and PRINT the error rather than # raising, so a caller who ignores the return value gets a silently truncated # output file and a line on stdout. check_eq $P/hex2bin-ok 'rc=0 size=74567' "$(pv hex2bin_ok)" check_eq $P/hex2bin-bad-returns \ 'rc=1 out=ERROR: bad HEX file: Record at line 1 has invalid checksum' \ "$(pv hex2bin_bad_returns)" check_eq $P/bin2hex-ok 'rc=0 lines=2 first=:0401000041424344F1' "$(pv bin2hex_ok)" check_eq $P/diff-dumps 'n=5 head=--- a' "$(pv diff_dumps)" # The other accepted initialisers, and the two formats tofile/loadfile take. check_eq $P/from-dict '0:01,1:02' "$(pv from_dict)" check_eq $P/from-dict-start '{'\''EIP'\'': 9}' "$(pv from_dict_start)" check_eq $P/from-dict-bad-key 'ValueError' "$(pv from_dict_bad_key)" check_eq $P/from-dict-negative-key 'ValueError' "$(pv from_dict_negative_key)" check_eq $P/from-intelhex '0,1,4,5' "$(pv from_intelhex)" check_eq $P/from-bad-type 'ValueError' "$(pv from_bad_type)" check_eq $P/todict-roundtrip 'yes' "$(pv todict_roundtrip)" check_eq $P/fromfile-hex '7' "$(pv fromfile_hex)" check_eq $P/loadfile-bad-format 'ValueError' "$(pv loadfile_bad_format)" check_eq $P/tofile-bad-format 'ValueError' "$(pv tofile_bad_format)" check_eq $P/get-memory-size-positive 'yes' "$(pv get_memory_size_positive)" # ============================================== the package's OWN test suite # intelhex/test.py is INSTALLED on this image, so 120 assertions written by the # library's own authors can be run against the library as this image built it, # for one interpreter start. A separate probe from the measurement one on # purpose: this is the only thing in the file that runs somebody else's code in # bulk, and a hang inside it must not take the 242 cases above down with it. # # TestInSubprocess is excluded and that is not a workaround for a missing # subprocess: its cases run `setup.py' and `scripts/bin2hex.py' relative to the # CURRENT DIRECTORY, and the package installs neither. They test an unpacked # sdist. The installed scripts are covered below, from their real paths. if [ ! -f "$UPROBE" ]; then bst_notok $P/upstream-present "$UPROBE did not ship" else bst_ok $P/upstream-present bst_probe $P/upstream-run "$W/upstream" 600 python3 "$UPROBE" UOUT="$W/upstream" uv() { grep "^$1=" "$UOUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } check_eq $P/upstream-complete "yes" "$(uv upstream_done)" check_eq $P/upstream-classes "17" "$(uv upstream_classes)" check_eq $P/upstream-declared "120" "$(uv upstream_declared)" check_eq $P/upstream-run-count "120" "$(uv upstream_run)" check_eq $P/upstream-failures "0" "$(uv upstream_failures)" check_eq $P/upstream-errors "0" "$(uv upstream_errors)" check_eq $P/upstream-skipped "0" "$(uv upstream_skipped)" check_eq $P/upstream-ok "yes" "$(uv upstream_ok)" # Which cases failed, if any -- one line naming them, so a red above is # readable without the transcript. Empty is the pass. check_eq $P/upstream-no-named-failures "" \ "$(grep -E '^upstream_(fail|error)_case=' "$UOUT" 2>/dev/null | cut -d= -f2- | paste -sd, -)" fi # ========================================================= the console scripts # setup.py declares `scripts=glob.glob("scripts/*")', NOT entry_points, so all # SIX install into ${bindir} WITH THEIR .py SUFFIX and with the shebang rewritten # from the sdist's `#!/usr/bin/python'. The ticket names four; there are six, # and all six are covered. # # They are run as PROCESSES, which is the point of covering them at all: # importing them would test the import machinery, while what has to hold on this # image is that a shebang'd python3 script installed by a wheel-built package can # be exec'd. Each is invoked ONCE -- a python3 start is the whole cost of this # file, so nothing below runs a script twice to score two things about it. # # The fixtures are written HERE, by printf, into the tmpfs workdir: literal HEX # records with their checksums, of the same shape the probe parses. a.hex holds # a start-linear-address record, four bytes at 0x0000 and one at 0x0010; b.hex # holds four bytes at 0x0000 and one at 0x0004, which OVERLAPS a.hex; bad.hex # is a.hex's first data record with its checksum off by one. B2H=/usr/bin/bin2hex.py H2B=/usr/bin/hex2bin.py H2D=/usr/bin/hex2dump.py HDF=/usr/bin/hexdiff.py HIN=/usr/bin/hexinfo.py HMG=/usr/bin/hexmerge.py for _s in "$B2H" "$H2B" "$H2D" "$HDF" "$HIN" "$HMG"; do _n=${_s##*/} check "$P/script-$_n" test -x "$_s" # The shebang the wheel build rewrote. The sdist ships `#!/usr/bin/python', # naming an interpreter this image does not have at all -- so a script that # reached the target with its shebang unrewritten would be an ENOENT at exec # rather than a wrong python. check_out "$P/shebang-$_n" "#!/usr/bin/env python3" head -1 "$_s" done # And NOT under a suffix-less name: these are `scripts', so nothing strips the # .py the way an entry_points console_script would have. check_eq $P/no-entry-point-names "" \ "$(ls /usr/bin/bin2hex /usr/bin/hex2bin /usr/bin/hex2dump /usr/bin/hexdiff \ /usr/bin/hexinfo /usr/bin/hexmerge 2>/dev/null)" printf ':0400000508000000EF\n:0400000041424344F2\n:010010005A95\n:00000001FF\n' > "$W/a.hex" printf ':0400000041424344F2\n:010004009962\n:00000001FF\n' > "$W/b.hex" printf ':0400000041424344F3\n:00000001FF\n' > "$W/bad.hex" check $P/fixture-a test -s "$W/a.hex" check $P/fixture-b test -s "$W/b.hex" check $P/fixture-bad test -s "$W/bad.hex" # --- hex2dump.py: the library's dump(), from a process. ( cd "$W" && python3 "$H2D" a.hex ) > "$W/dump.out" 2>&1 check_eq $P/hex2dump-lines "3" "$(wc -l < "$W/dump.out" | tr -d ' ')" check_eq $P/hex2dump-eip "EIP = 0x08000000" "$(sed -n 1p "$W/dump.out")" check_eq $P/hex2dump-row0 \ '0000 41 42 43 44 -- -- -- -- -- -- -- -- -- -- -- -- |ABCD |' \ "$(sed -n 2p "$W/dump.out")" check_eq $P/hex2dump-row1 \ '0010 5A -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- |Z |' \ "$(sed -n 3p "$W/dump.out")" # --- hexinfo.py: the same object as YAML. ( cd "$W" && python3 "$HIN" a.hex ) > "$W/info.out" 2>&1 check_eq $P/hexinfo-file "- file: 'a.hex'" "$(sed -n 1p "$W/info.out")" check_eq $P/hexinfo-entry " entry: 0x08000000" "$(sed -n 2p "$W/info.out")" check_eq $P/hexinfo-data-hdr " data:" "$(sed -n 3p "$W/info.out")" check_eq $P/hexinfo-seg0 \ ' - { first: 0x00000000, last: 0x00000003, length: 0x00000004 }' \ "$(sed -n 4p "$W/info.out")" check_eq $P/hexinfo-seg1 \ ' - { first: 0x00000010, last: 0x00000010, length: 0x00000001 }' \ "$(sed -n 5p "$W/info.out")" # --- hex2bin.py: the sparse image flattened, the hole filled with the padding. ( cd "$W" && python3 "$H2B" a.hex a.bin ) > "$W/h2b.out" 2>&1 check_eq $P/hex2bin-quiet "" "$(cat "$W/h2b.out")" check $P/hex2bin-wrote test -s "$W/a.bin" check_eq $P/hex2bin-size "17" "$(wc -c < "$W/a.bin" | tr -d ' ')" check_eq $P/hex2bin-bytes "41424344ffffffffffffffffffffffff5a" \ "$(od -An -tx1 -v "$W/a.bin" | tr -d ' \n')" # --- THE SCRIPTS AGREE WITH THE LIBRARY OVER THE SAME INPUT, and this is the # case that says so rather than assuming it. ONE python3 start drives the # LIBRARY over the very file hex2dump.py and hex2bin.py were just given, and its # dump is compared BYTE FOR BYTE against what hex2dump printed, its tobinstr # against what hex2bin wrote. Anything that made the tools and the library # disagree -- a script pinned to a different intelhex, a stale .pyc, a wrapper # rewriting output -- shows up here and nowhere else. ( cd "$W" && python3 -c ' import sys from intelhex import IntelHex ih = IntelHex("a.hex") ih.dump() sys.stdout.write(ih.tobinstr().hex() + "\n") sys.stdout.write("%d %d %d\n" % (len(ih), ih.minaddr(), ih.maxaddr())) ' ) > "$W/lib.out" 2>&1 check_eq $P/library-agrees-dump "$(cat "$W/dump.out")" "$(sed -n '1,3p' "$W/lib.out")" check_eq $P/library-agrees-bin \ "$(od -An -tx1 -v "$W/a.bin" | tr -d ' \n')" "$(sed -n 4p "$W/lib.out")" check_eq $P/library-shape "5 0 16" "$(sed -n 5p "$W/lib.out")" # --- bin2hex.py: back the other way, and the round trip is LOSSY BY DESIGN. # The hole hex2bin filled with 0xFF comes back as REAL DATA, so # a.hex -> a.bin -> c.hex is not the identity: a four-byte record and a # one-byte record become a SIXTEEN-byte record and a one-byte record. A caller # who uses the pair to normalise a file has silently written 12 bytes of # padding into the image. ( cd "$W" && python3 "$B2H" a.bin c.hex ) > "$W/b2h.out" 2>&1 check_eq $P/bin2hex-quiet "" "$(cat "$W/b2h.out")" check_eq $P/bin2hex-lines "3" "$(wc -l < "$W/c.hex" | tr -d ' ')" check_eq $P/bin2hex-rec0 ':1000000041424344FFFFFFFFFFFFFFFFFFFFFFFFF2' "$(sed -n 1p "$W/c.hex")" check_eq $P/bin2hex-rec1 ':010010005A95' "$(sed -n 2p "$W/c.hex")" check_eq $P/bin2hex-eof ':00000001FF' "$(sed -n 3p "$W/c.hex")" check_eq $P/roundtrip-not-identity "differs" \ "$(if cmp -s "$W/a.hex" "$W/c.hex"; then echo same; else echo differs; fi)" # and the start-address record is GONE: a bin carries no entry point, so the # round trip loses it without saying so. check_eq $P/bin2hex-drops-start "" "$(grep ':04000005' "$W/c.hex")" # --- hexmerge.py: the default REFUSES an overlap and writes nothing. ( cd "$W" && python3 "$HMG" -o m.hex a.hex b.hex ) > "$W/merge.out" 2>&1 check_eq $P/hexmerge-refuses "Data overlapped at address 0x0" \ "$(grep '^Data overlapped' "$W/merge.out")" check_eq $P/hexmerge-progress "Merging: b.hex" "$(grep '^Merging' "$W/merge.out")" check $P/hexmerge-no-output test ! -e "$W/m.hex" # --overlap=replace takes it, and the result is one merged record: b.hex's 0x99 # lands at 0x0004 and extends a.hex's four-byte record to five. ( cd "$W" && python3 "$HMG" -o m2.hex --overlap=replace a.hex b.hex ) > "$W/merge2.out" 2>&1 check $P/hexmerge-wrote test -s "$W/m2.hex" check_eq $P/hexmerge-start ':0400000508000000EF' "$(sed -n 1p "$W/m2.hex")" check_eq $P/hexmerge-data ':05000000414243449958' "$(sed -n 2p "$W/m2.hex")" check_eq $P/hexmerge-tail ':010010005A95' "$(sed -n 3p "$W/m2.hex")" check_eq $P/hexmerge-eof ':00000001FF' "$(sed -n 4p "$W/m2.hex")" # --- hexdiff.py: a unified diff of the two dumps -- AND IT EXITS 0 WHETHER OR # NOT THE FILES DIFFER, so it cannot be used as a test the way diff(1) can. ( cd "$W" && python3 "$HDF" a.hex b.hex ) > "$W/diff.out" 2>&1 echo $? > "$W/diff.rc" check_eq $P/hexdiff-rc-on-difference "0" "$(cat "$W/diff.rc")" check_eq $P/hexdiff-header-a "--- a.hex" "$(sed -n 1p "$W/diff.out")" check_eq $P/hexdiff-header-b "+++ b.hex" "$(sed -n 2p "$W/diff.out")" check_eq $P/hexdiff-hunk "@@ -1,3 +1 @@" "$(sed -n 3p "$W/diff.out")" check_eq $P/hexdiff-removed-eip "-EIP = 0x08000000" "$(sed -n 4p "$W/diff.out")" # --- a bad file is a nonzero exit and a MESSAGE, not a traceback. A traceback # here would mean the script had stopped catching HexReaderError, which is the # difference between a tool and a crash. ( cd "$W" && python3 "$H2B" bad.hex bad.bin ) > "$W/badout" 2>&1 echo $? > "$W/bad.rc" check_eq $P/hex2bin-bad-rc "1" "$(cat "$W/bad.rc")" check_eq $P/hex2bin-bad-message \ "ERROR: bad HEX file: Record at line 1 has invalid checksum" \ "$(sed -n 1p "$W/badout")" check_eq $P/hex2bin-bad-no-traceback "" "$(grep '^Traceback' "$W/badout")" # --- the version every script agrees on, taken from ONE of them. The string is # intelhex.__version__.version_str, which the probe asserts separately, so this # case is about the SCRIPT reaching the installed package rather than about the # number. check_out $P/script-version "2.3.0" python3 "$B2H" --version # --- python3 -m intelhex is a signpost, not a tool, and says so. check_out $P/dash-m-welcome "Welcome to IntelHex Python library." python3 -m intelhex rm -rf "$W"