. "$BST_LIB" # BST-TIMEOUT: 1200 # THREE python3 starts and nothing else. # # # # They are: a bare `import', a second one whose STDERR is # # the assertion (`import-quiet'), and the probe, which # # takes ONE start for 157 measurements. # # # # 400s per start is the budget the neighbouring python3 # # suites use on this arm. NOTHING here forks or execs: # # every case is integer arithmetic over a 256-element # # table, so the interpreter start IS the whole cost and # # there is no exec traffic to budget for. The heaviest # # single measurement is `gf-all-inverses-ok', which is 255 # # multiplications and 255 table lookups. # # # # A suite-timeout red is a batch-wide cost, 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 # # 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-reedsolo 2.0.13 (meta-openembedded meta-python/recipes-devtools/ # python3-reedsolo, python3-reedsolo_2.0.13.bb, `inherit pypi setuptools3', # PyPI sdist) on the wasm32-hwjs glibc image. # # ============================================================================ # WHAT THE PACKAGE ACTUALLY IS, AND THEREFORE WHAT HAS TO BE TESTED. # ============================================================================ # # `reedsolo' is a Reed-Solomon error-correcting encoder and decoder over # GF(2^c_exp). `RSCodec.encode' appends parity symbols; `RSCodec.decode' # repairs corrupted symbols up to the code's distance and reports the positions # it corrected; the module also exposes the raw Galois-field and polynomial # primitives the codec is built out of. # # It is PURE ARITHMETIC: no socket, no subprocess, no device, no terminal and # no file. That is what makes this port close on its suite with no BRINTOS-780 # follow-on -- and it is also what makes the assertions unusually sharp. An # error-correcting code has an EXACT answer: it either returns the original # message or it does not, and the set of positions it says it repaired either # is the set that was corrupted or it is not. There is no tolerance to hide in. # # ============================================================================ # THE EXPECTED VALUES ARE MEASURED, NOT PREDICTED -- BUT NOT ON TARGET. # ============================================================================ # # Every literal below was taken from running data/python3-reedsolo-probe.py # under CPython 3.12.3 against THIS PORT'S OWN BUILT PACKAGE -- the # python3-reedsolo_2.0.13-r0_wasm32.deb this recipe produced, unpacked and put # on PYTHONPATH -- rather than from reading the source and guessing. # # So they are facts about the shipped bytes, not predictions about them. What # is NOT yet measured is this arm: the first on-target run of this suite is the # batch verify. The port is pure Python integer arithmetic over `bytearray', # and the interpreter is the same CPython 3.12, so the expectation is that # wasm32 changes none of it. That is an expectation, and the verify is what # settles it. # # ============================================================================ # WHY THE CORRUPTION IS DETERMINISTIC. # ============================================================================ # # Nothing here is random. Every corrupted position is a literal in the probe, # and every corrupting byte is applied as `^ 0xFF' so it CANNOT accidentally # equal the byte it replaced. A random corruption that happened to be a no-op # would turn a decoder failure into a green case, which is the one way a suite # like this lies to you. # # ============================================================================ # FIVE THINGS A CALLER WILL GET WRONG. THESE ARE THE POINT OF THE SUITE. # ============================================================================ # # `decode' RETURNS THREE VALUES, NOT TWO. Its own docstring says # `Usage: rmes, rmesecc = RSCodec.decode(data)'. The code # returns `(msg, msg+ecc, errata_positions)'. A caller who # believes the docstring gets a ValueError on unpacking. # `decode-arity' is that fact. # # `encode(str)' RAISES IN THE DEFAULT FIELD. The docstring and the README # both take a str. `encode' converts one with the module # global `_bytearray', and `init_tables' binds that name to # the BUILTIN `bytearray' whenever c_exp <= 8 -- and # `bytearray("hello")' with no encoding is a TypeError. Only # a c_exp > 8 field, where init_tables installs the # array.array shim that latin-1 encodes, takes a str. # `encode-str-default-field' and `encode-str-c-exp-12' are # the pair. # # THERE IS NO `reedsolo.__version__'. The version exists only in the # dist-info METADATA, which is where this suite reads it. # # CONSTRUCTING AN `RSCodec' MUTATES MODULE-LEVEL STATE. `__init__' calls # `init_tables', which assigns the module globals `gf_exp', # `gf_log' and `field_charac'. So the bare `gf_mul' family # operates on whichever codec was built LAST, not on any # field the caller names. Two codecs over different fields # are NOT independent if you also use the bare functions. # # THE ERRATA POSITIONS ARE NEITHER SORTED NOR ABSOLUTE. `decode' returns # `erase_pos + err_pos': the erasures the caller declared, in # the caller's order, then the errors the Chien search found, # which `rs_find_errors' appends in DESCENDING order. And # under chunking they are CHUNK-RELATIVE -- `long-positions' # below reports corruptions at codeword offsets 3, 25 and 41 # as 3, 5 and 1. The `*-positions' cases sort before # comparing, which is the property a caller depends on; # `positions-raw-order' asserts the real order, so a future # version that starts sorting is a visible change here. # # ============================================================================ # THE PACKAGE SHIPS A TOP-LEVEL `tests' MODULE. NOT A DEFECT WE INTRODUCED. # ============================================================================ # # `setup.py' declares only `py_modules=["reedsolo"]', but the sdist carries a # stale `reedsolo.egg-info/top_level.txt' listing `build', `reedsolo' and # `tests', and that is what the wheel was built from. So this package installs # `/usr/lib/python3.12/site-packages/tests/' -- three test files and no # `__init__.py' -- and `import tests' on this image resolves to a namespace # package rooted there. # # It is asserted rather than removed, for two reasons. The recipe is # meta-openembedded's unmodified and this port does not diverge from it; and no # other package in the image owns that path -- checked against all 4670 runtime # entries in the batch image's pkgdata -- so there is no dpkg file conflict and # nothing that can break the shared build. What it IS is a namespace hazard: # any later package or script that expects `import tests' to find its own is # now wrong on this image. The cases below pin the current state so that a # change in either direction is visible. # # ============================================================================ # WHAT IS NOT HERE. # ============================================================================ # # The Cython accelerator. `setup.py' builds `creedsolo' only under an explicit # `--cythonize' argument, which this recipe does not pass and for which it # declares no Cython dependency, so the pure-Python implementation is what # installed and what every case below measures. This image's python3 is ONE # STATIC BINARY with an empty lib-dynload and no dynamic loader # (BRINTOS-1476), so a compiled creedsolo could not have been imported here # anyway. `creedsolo-absent' and `pkg-no-so' are what say it is not there. P=python3-reedsolo # --------------------------------------------------------------------------- bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages MOD=$SITE/reedsolo.py DI=$SITE/reedsolo-2.0.13.dist-info PKGLIST=/var/lib/dpkg/info/python3-reedsolo.list PROBE=$BST_DATA/python3-reedsolo-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-reedsolo-suite.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # --- the module, as installed ---------------------------------------------- # setup.py declares py_modules=["reedsolo"], so what lands is a FLAT MODULE, # not a package directory. `not-a-package' is the assertion that says so. check $P/site-packages-dir test -d "$SITE" check $P/module-file test -s "$MOD" check $P/not-a-package test ! -d "$SITE/reedsolo" check $P/pycache-module test -s "$SITE/__pycache__/reedsolo.cpython-312.pyc" # The Cython accelerator was not built and must not be present in any form. check $P/no-creedsolo-so test ! -e "$SITE/creedsolo.so" check $P/no-creedsolo-py test ! -e "$SITE/creedsolo.py" check $P/no-creedsolo-c test ! -e "$SITE/creedsolo.c" # The `tests' tree the stale egg-info dragged in. Asserted PRESENT, because # that is what shipped -- see the header block. A namespace package: three # files and no __init__.py. check $P/tests-dir-shipped test -d "$SITE/tests" check $P/tests-no-init test ! -e "$SITE/tests/__init__.py" check $P/tests-test-reedsolo test -s "$SITE/tests/test_reedsolo.py" check $P/tests-test-creedsolo test -s "$SITE/tests/test_creedsolo.py" check $P/tests-perf test -s "$SITE/tests/perf.py" # --- the package, as dpkg has it ------------------------------------------- cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # This image's python3 has no dynamic loader at all (BRINTOS-1476), so a .so in # this list would be a file that could never be imported. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # setup.py declares no entry_points and no scripts, so nothing lands in a bindir. check_eq $P/pkg-no-bin "" \ "$(grep -E '^/usr/(s?bin)/' "$W/pkgf" 2>/dev/null)" _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # --- the wheel metadata ---------------------------------------------------- check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: reedsolo" cat "$DI/METADATA" check_out $P/dist-info-version "Version: 2.0.13" cat "$DI/METADATA" check $P/dist-info-record test -s "$DI/RECORD" # Purelib and a py3-none-any tag are the machine-readable form of "there is # nothing arch-specific in here", which is the whole reason this port is cheap # -- and, given the Cython accelerator upstream ships, the thing worth checking # rather than assuming. 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)" # The stale top_level.txt itself, which is WHY tests/ shipped. `build' is in it # too and no build/ directory shipped, which is the other half of the evidence # that this file is a leftover rather than a description of the wheel. check_out $P/dist-info-top-level-reedsolo "reedsolo" cat "$DI/top_level.txt" check_out $P/dist-info-top-level-tests "tests" cat "$DI/top_level.txt" check_out $P/dist-info-top-level-build "build" cat "$DI/top_level.txt" check $P/no-build-dir test ! -d "$SITE/build" # creedsolo is NOT in it -- the accelerator was never part of this wheel. check_eq $P/dist-info-no-creedsolo "" \ "$(grep '^creedsolo$' "$DI/top_level.txt" 2>/dev/null)" sed -n '/^Package: python3-reedsolo$/,/^$/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.0.13-r0" cat "$W/st" # --- import ---------------------------------------------------------------- check $P/import python3 -c 'import reedsolo' # An import that WORKS but complains is a port that is not finished. Asserted # on its own start so that the noise, if any, is the whole of the evidence. check_eq $P/import-quiet "" \ "$(python3 -c 'import reedsolo' 2>&1)" # --- the 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 check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "157" "$(pv emitted_count)" # --- the module, as the interpreter sees it -------------------------------- check_eq $P/module-answered "$MOD" "$(pv module_file)" check_eq $P/is-package "no" "$(pv is_package)" # The version is in the dist-info and NOWHERE ELSE. A caller reaching for # reedsolo.__version__ gets an AttributeError. check_eq $P/has-version-attr "no" "$(pv has_version_attr)" # --- the public names ------------------------------------------------------ check_eq $P/has-rscodec "yes" "$(pv has_rscodec)" check_eq $P/has-error "yes" "$(pv has_reedsolomonerror)" check_eq $P/error-is-exception "yes" "$(pv error_is_exception)" check_eq $P/has-init-tables "yes" "$(pv has_init_tables)" check_eq $P/has-rs-encode-msg "yes" "$(pv has_rs_encode_msg)" check_eq $P/has-rs-correct-msg "yes" "$(pv has_rs_correct_msg)" check_eq $P/has-rs-check "yes" "$(pv has_rs_check)" check_eq $P/has-rs-generator-poly "yes" "$(pv has_rs_generator_poly)" check_eq $P/has-gf-add "yes" "$(pv has_gf_add)" check_eq $P/has-gf-sub "yes" "$(pv has_gf_sub)" check_eq $P/has-gf-mul "yes" "$(pv has_gf_mul)" check_eq $P/has-gf-div "yes" "$(pv has_gf_div)" check_eq $P/has-gf-pow "yes" "$(pv has_gf_pow)" check_eq $P/has-gf-inverse "yes" "$(pv has_gf_inverse)" check_eq $P/has-gf-poly-mul "yes" "$(pv has_gf_poly_mul)" check_eq $P/has-gf-poly-add "yes" "$(pv has_gf_poly_add)" check_eq $P/has-gf-poly-div "yes" "$(pv has_gf_poly_div)" check_eq $P/has-gf-poly-eval "yes" "$(pv has_gf_poly_eval)" check_eq $P/has-find-prime-polys "yes" "$(pv has_find_prime_polys)" # --- the module state BEFORE any field exists ------------------------------ # reedsolo defines gf_exp = bytearray([1]*512) and gf_log = bytearray(256) at # import time. Those are PLACEHOLDERS, not a field -- so the bare gf_mul # answers, and answers wrongly, rather than raising. 2*2 is 4 in GF(2^8) and # the placeholder table says 1. Asserted because a caller who uses the bare # functions without calling init_tables first gets silent nonsense, and this is # the case that documents it. check_eq $P/gf-field-charac-before-init "255" "$(pv gf_field_charac_before_init)" check_eq $P/gf-exp-len-before-init "512" "$(pv gf_exp_len_before_init)" check_eq $P/gf-log-len-before-init "256" "$(pv gf_log_len_before_init)" check_eq $P/gf-mul-before-init "1" "$(pv gf_mul_before_init)" # --- the Galois field, against the primitive polynomial -------------------- # GF(2^8) with prim=0x11d and generator 2 -- the conventional QR-code field. # init_tables doubles the anti-log table to 2*field_charac == 510 so that a # multiply needs no modulo; the log table is field_charac+1 == 256 because # log[0] is unused. check_eq $P/init-tables-field-charac "255" "$(pv init_tables_field_charac)" check_eq $P/init-tables-exp-len "510" "$(pv init_tables_exp_len)" check_eq $P/init-tables-log-len "256" "$(pv init_tables_log_len)" # alpha^0..alpha^7 are plain doubling: 1, 2, 4, ..., 128. No reduction yet. check_eq $P/gf-exp-0 "1" "$(pv gf_exp_0)" check_eq $P/gf-exp-1 "2" "$(pv gf_exp_1)" check_eq $P/gf-exp-7 "128" "$(pv gf_exp_7)" # alpha^8 is where the polynomial finally does something: 2^8 == 256 is outside # the field and is reduced by 0x11d -- 0x100 ^ 0x11d == 0x1d == 29. This single # case is what distinguishes THIS field from any other GF(2^8), and a port that # got the reduction wrong fails here and nearly everywhere below. check_eq $P/gf-exp-8 "29" "$(pv gf_exp_8)" # alpha^9 == alpha^8 * 2 == 29 * 2 == 58, no reduction needed. check_eq $P/gf-exp-9 "58" "$(pv gf_exp_9)" # The log table is the inverse of the same walk. check_eq $P/gf-log-1 "0" "$(pv gf_log_1)" check_eq $P/gf-log-2 "1" "$(pv gf_log_2)" check_eq $P/gf-log-29 "8" "$(pv gf_log_29)" # In a characteristic-2 field addition IS xor and subtraction is the SAME # operation. 5 ^ 3 == 6 both ways, and negation is the identity. check_eq $P/gf-add-5-3 "6" "$(pv gf_add_5_3)" check_eq $P/gf-sub-5-3 "6" "$(pv gf_sub_5_3)" check_eq $P/gf-add-is-sub "yes" "$(pv gf_add_is_sub)" check_eq $P/gf-neg-7 "7" "$(pv gf_neg_7)" # Multiplication against the same definition, from the other side: 128*2 is the # alpha^8 reduction again. check_eq $P/gf-mul-2-2 "4" "$(pv gf_mul_2_2)" check_eq $P/gf-mul-2-4 "8" "$(pv gf_mul_2_4)" check_eq $P/gf-mul-128-2 "29" "$(pv gf_mul_128_2)" check_eq $P/gf-mul-1-37 "37" "$(pv gf_mul_1_37)" # Zero is absorbing in both argument orders -- gf_mul special-cases it before # touching the log table, where log[0] is undefined. check_eq $P/gf-mul-0-5 "0" "$(pv gf_mul_0_5)" check_eq $P/gf-mul-5-0 "0" "$(pv gf_mul_5_0)" check_eq $P/gf-div-8-2 "4" "$(pv gf_div_8_2)" check_eq $P/gf-div-0-7 "0" "$(pv gf_div_0_7)" # Division by zero RAISES rather than returning a field element. check_eq $P/gf-div-by-zero "!ZeroDivisionError" "$(pv gf_div_by_zero)" check_eq $P/gf-pow-2-8 "29" "$(pv gf_pow_2_8)" check_eq $P/gf-pow-2-0 "1" "$(pv gf_pow_2_0)" check_eq $P/gf-inverse-2 "142" "$(pv gf_inverse_2)" # x * x^-1 == 1 over a value that is not a power of two, and then over EVERY # non-zero element of the field: 255 multiplications reported as one number. # This is the case that says the tables are a field and not just a table. check_eq $P/gf-inverse-roundtrip "1" "$(pv gf_inverse_roundtrip)" check_eq $P/gf-all-inverses-ok "1" "$(pv gf_all_inverses_ok)" # --- polynomials over the field -------------------------------------------- # [1,2] + [3] aligns right: [1, 2^3] == [1, 1]. check_eq $P/gf-poly-add "0101" "$(pv gf_poly_add)" # (x+2)(x+3) == x^2 + (2^3)x + (2*3) == [1, 1, 6] over GF(2^8). check_eq $P/gf-poly-mul "010106" "$(pv gf_poly_mul)" check_eq $P/gf-poly-scale "020406" "$(pv gf_poly_scale)" # Horner at x==1 is the xor of the coefficients: 1^2^3 == 0. At x==0 only the # constant term survives, so it is the last coefficient, 3. check_eq $P/gf-poly-eval-at-1 "0" "$(pv gf_poly_eval_at_1)" check_eq $P/gf-poly-eval-at-0 "3" "$(pv gf_poly_eval_at_0)" check_eq $P/gf-poly-eval-at-4 "27" "$(pv gf_poly_eval_at_4)" # The generator polynomial of an nsym=2 code is (x-alpha^0)(x-alpha^1), which # over this field is x^2 + 3x + 2 -- [1, 3, 2]. An nsym code has nsym+1 # coefficients. check_eq $P/rs-generator-poly-2 "010302" "$(pv rs_generator_poly_2)" check_eq $P/rs-generator-poly-2-len "3" "$(pv rs_generator_poly_2_len)" check_eq $P/rs-generator-poly-10-len "11" "$(pv rs_generator_poly_10_len)" # --- the codec's parameters, against what it was constructed with ---------- check_eq $P/ctor-nsym "10" "$(pv ctor_nsym)" check_eq $P/ctor-nsize "255" "$(pv ctor_nsize)" check_eq $P/ctor-fcr "0" "$(pv ctor_fcr)" check_eq $P/ctor-prim "0x11d" "$(pv ctor_prim)" check_eq $P/ctor-generator "2" "$(pv ctor_generator)" check_eq $P/ctor-c-exp "8" "$(pv ctor_c_exp)" check_eq $P/ctor-field-charac "255" "$(pv ctor_field_charac)" # The constructor ran init_tables, which assigned the MODULE globals. This is # the side effect a caller holding two codecs over two fields has to know # about: the bare gf_* functions follow the codec built last. check_eq $P/ctor-sets-module-globals "yes" "$(pv ctor_sets_module_globals)" # A smaller field, to show c_exp is real and not decoration: GF(2^6) has # field_charac 63, and nsize is resized to match. check_eq $P/ctor6-c-exp "6" "$(pv ctor6_c_exp)" check_eq $P/ctor6-field-charac "63" "$(pv ctor6_field_charac)" check_eq $P/ctor6-nsize "63" "$(pv ctor6_nsize)" # nsym must be strictly less than nsize. The boundary RAISES -- it is not # clamped, and it does not become a wrong answer at decode time. check_eq $P/ctor-nsym-eq-nsize "!ValueError" "$(pv ctor_nsym_eq_nsize)" check_eq $P/ctor-nsym-gt-nsize "!ValueError" "$(pv ctor_nsym_gt_nsize)" # The Singleton bound the codec reports: nsym/2 errors OR nsym erasures, and # the simultaneous trade between them. Past the bound it raises. check_eq $P/maxerrata "(5,10)" "$(pv maxerrata_10)" check_eq $P/maxerrata-4-erasures "(3,4)" "$(pv maxerrata_10_given_4_erasures)" check_eq $P/maxerrata-2-errors "(2,6)" "$(pv maxerrata_10_given_2_errors)" check_eq $P/maxerrata-over-bound "!ReedSolomonError" "$(pv maxerrata_over_bound)" # =========================================================================== # THE ROUND TRIP. The backbone of the suite. # # The 50-byte fixture is encoded, exactly nsym//2 symbols are corrupted at # positions chosen in the probe, and the decode is asserted to return the # ORIGINAL message AND to name the EXACT set of positions that were corrupted. # The encoded length is nsym longer than the message, and the message is still # a prefix of it -- this is a systematic code. # =========================================================================== # nsym=4 corrects 2 errors. Corrupted at 0 and 7. check_eq $P/rt4-enc-len "54" "$(pv rt4_enc_len)" check_eq $P/rt4-enc-prefix "yes" "$(pv rt4_enc_prefix)" check_eq $P/rt4-check-clean "[True]" "$(pv rt4_check_clean)" check_eq $P/rt4-check-corrupt "[False]" "$(pv rt4_check_corrupt)" check_eq $P/rt4-msg "yes" "$(pv rt4_msg)" check_eq $P/rt4-full "yes" "$(pv rt4_full_is_msg_plus_ecc)" check_eq $P/rt4-positions "0,7" "$(pv rt4_positions)" # nsym=10 corrects 5. Corrupted at 0, 3, 17, 40 and 52 -- note 52 is in the # PARITY block, not the message, and is repaired just the same. check_eq $P/rt10-enc-len "60" "$(pv rt10_enc_len)" check_eq $P/rt10-enc-prefix "yes" "$(pv rt10_enc_prefix)" check_eq $P/rt10-check-clean "[True]" "$(pv rt10_check_clean)" check_eq $P/rt10-check-corrupt "[False]" "$(pv rt10_check_corrupt)" check_eq $P/rt10-msg "yes" "$(pv rt10_msg)" check_eq $P/rt10-full "yes" "$(pv rt10_full_is_msg_plus_ecc)" check_eq $P/rt10-positions "0,3,17,40,52" "$(pv rt10_positions)" # nsym=20 corrects 10. Ten consecutive corruptions -- a burst, which is the # case Reed-Solomon is actually deployed for. check_eq $P/rt20-enc-len "70" "$(pv rt20_enc_len)" check_eq $P/rt20-enc-prefix "yes" "$(pv rt20_enc_prefix)" check_eq $P/rt20-check-clean "[True]" "$(pv rt20_check_clean)" check_eq $P/rt20-check-corrupt "[False]" "$(pv rt20_check_corrupt)" check_eq $P/rt20-msg "yes" "$(pv rt20_msg)" check_eq $P/rt20-full "yes" "$(pv rt20_full_is_msg_plus_ecc)" check_eq $P/rt20-positions "1,2,3,4,5,6,7,8,9,10" "$(pv rt20_positions)" # The raw order, asserted once. No erasures were declared, so this is # rs_find_errors' own output: DESCENDING, not sorted, not corruption order. check_eq $P/positions-raw-order "52,40,17,3,0" "$(pv positions_raw_order)" check_eq $P/positions-raw-is-sorted "no" "$(pv positions_raw_is_sorted)" # decode returns THREE values. Its docstring says two. check_eq $P/decode-arity "3" "$(pv decode_arity)" check_eq $P/decode-msg-type "bytearray" "$(pv decode_msg_type)" check_eq $P/decode-errata-type "bytearray" "$(pv decode_errata_type)" # An UNcorrupted codeword decodes to the message with an EMPTY errata list -- # the decoder does not invent corrections it did not make. check_eq $P/clean-decode-msg "yes" "$(pv clean_decode_msg)" check_eq $P/clean-decode-positions "" "$(pv clean_decode_positions)" # =========================================================================== # ONE CORRUPTION TOO MANY. # # The case that separates a working decoder from one that only appears to work. # Beyond the bound the library must RAISE, not hand back a wrong message # quietly. The probe reports `returned-wrong' or `returned-correct' if it does # not raise, so a silent-wrong-answer regression reads as exactly that rather # than as a bare mismatch. # =========================================================================== # nsym=4 corrects 2; three errors is one too many. check_eq $P/over-capacity-nsym4 "!ReedSolomonError" "$(pv over4_outcome)" # nsym=10 corrects 5; six is one too many. check_eq $P/over-capacity-nsym10 "!ReedSolomonError" "$(pv over10_outcome)" # And check() calls the over-capacity codeword corrupt, which is the weaker # question the caller is meant to ask first. check_eq $P/check-over-capacity "[False]" "$(pv check_over_capacity)" # =========================================================================== # ERASURES. # # A position the caller KNOWS is bad costs one parity symbol instead of two, so # an nsym=10 code repairs 10 erasures where it repairs only 5 unlocalized # errors. Both halves are asserted -- the doubled capacity works, and one past # it does not -- and `era6' against `era6-as-errors' is the pair that shows the # extra capacity comes from DECLARING the positions rather than from luck. # =========================================================================== # Ten erasures under nsym=10: exactly the Singleton bound. check_eq $P/era10-msg "yes" "$(pv era10_msg)" check_eq $P/era10-positions "0,1,2,3,4,5,6,7,8,9" "$(pv era10_positions)" # Six erasures, comfortably inside the bound. check_eq $P/era6-msg "yes" "$(pv era6_msg)" check_eq $P/era6-positions "2,9,20,31,44,57" "$(pv era6_positions)" # The SAME six positions as unlocalized errors is past the error bound of five. check_eq $P/era6-as-errors "!ReedSolomonError" "$(pv era6_as_errors_outcome)" # The same six with only_erasures=False: the Forney-syndrome path, which hides # the erasures from Berlekamp-Massey. Same answer, different route through the # decoder. check_eq $P/era6-forney-msg "yes" "$(pv era6_forney_msg)" check_eq $P/era6-forney-positions "2,9,20,31,44,57" "$(pv era6_forney_positions)" # Eleven erasures is past the bound and raises. check_eq $P/era11-msg "!ReedSolomonError" "$(pv era11_msg)" check_eq $P/era11-positions "!ReedSolomonError" "$(pv era11_positions)" # =========================================================================== # THE EDGE CASES THE TICKET NAMES. # =========================================================================== # --- a single-symbol code -------------------------------------------------- # nsym=1 corrects int(1/2) == ZERO errors, but ONE erasure -- a located symbol # costs one parity symbol, not two. The pair is the whole point: the same # single corruption is repairable when declared and raises when it is not. check_eq $P/nsym1-enc-len "51" "$(pv nsym1_enc_len)" check_eq $P/nsym1-maxerrata "(0,1)" "$(pv nsym1_maxerrata)" check_eq $P/nsym1-clean-msg "yes" "$(pv nsym1_clean_msg)" check_eq $P/nsym1-one-erasure "yes" "$(pv nsym1_one_erasure)" check_eq $P/nsym1-one-error "!ReedSolomonError" "$(pv nsym1_one_error)" # --- an empty message ------------------------------------------------------ # ceil(0/chunk) is ZERO chunks, so encode returns an EMPTY bytearray -- no # parity at all, not a bare parity block. A caller who expects nsym bytes back # from encoding nothing is wrong, and this is where that shows. decode and # check agree: three empty arrays and an empty per-chunk list. check_eq $P/empty-enc-len "0" "$(pv empty_enc_len)" check_eq $P/empty-enc-hex "" "$(pv empty_enc_hex)" check_eq $P/empty-decode "arity-3-lens-0,0,0" "$(pv empty_decode)" check_eq $P/empty-check "[]" "$(pv empty_check)" # --- a message longer than one block --------------------------------------- # nsize=20, nsym=6 gives a 14-byte payload per chunk, so 35 bytes is THREE # chunks and the chunking loop is genuinely exercised. # # The encoded length is 53, not 60. encode preallocates total_chunks*nsize == # 60 and slice-assigns each encoded chunk in; the last chunk carries only 7 # message bytes, encodes to 13, and the slice assignment RESIZES the bytearray # down to 40+13. A caller who sized the output as total_chunks*nsize is wrong. check_eq $P/long-enc-len "53" "$(pv long_enc_len)" check_eq $P/long-last-chunk-len "13" "$(pv long_last_chunk_len)" check_eq $P/long-check-len "3" "$(pv long_check_len)" check_eq $P/long-check-clean "[True,True,True]" "$(pv long_check_clean)" # One error in EACH of the three chunks -- codeword offsets 3, 25 and 41. check_eq $P/long-check-corrupt "[False,False,False]" "$(pv long_check_corrupt)" check_eq $P/long-msg "yes" "$(pv long_msg)" # The same resize runs on the way back, so decode returns exactly the 35 # message bytes -- NOT the total_chunks*(nsize-nsym) == 42 it preallocated. check_eq $P/long-dec-len "35" "$(pv long_dec_len)" # And the positions are CHUNK-RELATIVE: offsets 3, 25 and 41 come back as # 3, 5 and 1. A caller who reads them as absolute offsets into the codeword is # wrong, and under chunking they are not even unique. check_eq $P/long-positions "1,3,5" "$(pv long_positions)" # =========================================================================== # INPUT TYPES. # =========================================================================== check_eq $P/encode-bytes-type "bytearray" "$(pv encode_bytes_type)" check_eq $P/encode-bytearray-type "bytearray" "$(pv encode_bytearray_type)" # A byte-exact golden codeword: b'hello' under the default nsym=10 codec. This # one literal pins the entire arithmetic chain -- field tables, generator # polynomial and the encoding remainder -- to 15 bytes a reader can diff. check_eq $P/encode-hello-hex "68656c6c6fa9751035862474524d52" "$(pv encode_hello_hex)" # The documented str support DOES NOT WORK in the default field -- see the # header block. TypeError out of bytearray(str) with no encoding. check_eq $P/encode-str-default-field "!TypeError" "$(pv encode_str_default_field)" check_eq $P/decode-str-default-field "!TypeError" "$(pv decode_str_default_field)" # The same call in a c_exp=12 field, where init_tables installs the array.array # shim that latin-1 encodes, works. The pair is what makes this a # field-dependent trap rather than a module that never accepted str. check_eq $P/encode-str-c-exp-12 "ok-len-15" "$(pv encode_str_c_exp_12)" # A codeword shorter than its own parity block is not a codeword, and a message # longer than the field cannot have its errors located uniquely. Both refuse. check_eq $P/decode-too-short "!ValueError" "$(pv decode_too_short)" check_eq $P/decode-too-long "!ValueError" "$(pv decode_too_long)" # =========================================================================== # THE BARE FUNCTIONAL API, used without an RSCodec. # # Same arithmetic, different entry point -- and a different return shape: # rs_check returns a BOOL where RSCodec.check returns a list per chunk. # =========================================================================== check_eq $P/bare-encode-len "15" "$(pv bare_encode_len)" check_eq $P/bare-encode-matches-codec "yes" "$(pv bare_encode_matches_codec)" check_eq $P/bare-check-clean "True" "$(pv bare_check_clean)" check_eq $P/bare-check-corrupt "False" "$(pv bare_check_corrupt)" check_eq $P/bare-correct-msg "68656c6c6f" "$(pv bare_correct_msg)" check_eq $P/bare-correct-positions "0,2" "$(pv bare_correct_positions)" # =========================================================================== # THE ACCELERATOR THAT IS NOT HERE, AND THE MODULE THAT IS. # =========================================================================== # Pure Python is what shipped and what every case above measured. check_eq $P/creedsolo-absent "no" "$(pv creedsolo_importable)" # The top-level `tests' name this package took over. Asserted as it IS, so a # change in either direction is visible. See the header block for why it is # recorded rather than removed. check_eq $P/tests-shadow-importable "yes" "$(pv tests_shadow_importable)" check_eq $P/tests-shadow-file "None" "$(pv tests_shadow_file)" check_eq $P/tests-shadow-path "$SITE/tests" "$(pv tests_shadow_path)" check_eq $P/tests-shadow-submodule "yes" "$(pv tests_shadow_submodule)" rm -rf "$W"