. "$BST_LIB" # BST-TIMEOUT: 150 # MEASURED. The batch-1997 image reported # `==BST-SUITE libmms elapsed=43s bound=300s cases=123==', # so the 300 this file first carried was the unchecked # bound its own comment asked to be trimmed. 150 is that # 43 s with room for a loaded host: all but one case is # shell arithmetic over a transcript, and the single exec # is the probe, which opens no descriptor but /dev/null # and returns at once. # libmms 0.6.4 -- the MMS/MMSH streaming-protocol library (meta-openembedded # meta-oe recipes-multimedia/libmms), with one meta-brintos patch to its # byte-order macros. What ships is /usr/lib/libmms.a, five headers under # /usr/include/libmms, /usr/lib/pkgconfig/libmms.pc, and # /usr/libexec/libmms-tests/libmms-probe from this layer's bbappend. # # --------------------------------------------------------------------------- # THIS SUITE OPENS NO SOCKET, AND THAT IS NOT A GAP IT IS WORKING AROUND # # mms_io_t (mmsio.h) is a CALLER-SUPPLIED transport: connect, read, write, # select. Everything above it -- the URL handling, the HTTP/MMSH request # composition, the chunk framing, the ASF header parser and the # bandwidth-driven stream selection, which between them are most of libmms' # code -- runs unchanged over whatever bytes the caller's read() hands back. So # libmms-probe IS a server: it replays a scripted MMSH conversation out of # memory and this file scores what the library made of it. # # WHAT IS THEREFORE NOT COVERED, said plainly rather than left to be inferred: # the transport. Nothing here opens a TCP connection and nothing on this image # can reach an MMS server. Whether sockets work on this arm is a question about # the platform, not about libmms, and no case below claims to answer it. The # E group asserts the other half of that boundary -- that a transport which # REFUSES is reported as a NULL rather than a block or an abort, which is what # a caller actually meets. # # --------------------------------------------------------------------------- # THE ORACLE IS A NATIVE x86-64 RUN OF THE SAME SOURCE # # Every literal below was pinned against libmms 0.6.4 built natively on x86-64 # WITH THIS LAYER'S bswap patch applied, driven by this same probe and the same # synthesised ASF header. These are wire formats fixed by ASF 1.0 and by # HTTP/1.0, so the two architectures MUST agree byte for byte: a mismatch here # is a real defect and not an expected divergence. The one case that differs is # S03, the large-file macro, which the oracle answers and a cross build cannot; # the S group says why, and why the values either side of it still agree. # # It is also the assertion a target-only run cannot make. Handing the library a # header and reading the fields back proves the writer and the reader agree; it # does not prove they are RIGHT. A parser reading the Play Duration from the # wrong offset would round-trip through its own mistake and report every value # consistent. # # --------------------------------------------------------------------------- # THE PATCH THIS SUITE IS THE REGRESSION TEST FOR # # libmms' LE_64_TO_NATIVE() ORs eight byte terms, four of them cast to uint64_t # and four of them plain `uint8_t << N', which promotes to int. So `val[3] << 24' # with val[3] >= 0x80 sets the sign bit of a 32-bit int, the surrounding 64-bit # expression sign-extends it to 0xFFFFFFFF........, and the four high bytes are # OR-ed into a word that is already all ones where they live. They are lost. # # A11 is that case: an ASF Creation Date of 130000000000000000 (0x01CDDA4F # ACCD0000, byte 3 = 0xAC), read back through mmsx_get_file_time(). # # A11 = 130000000000000000 the patch is in # A11 = 18446744072313700352 the patch is NOT in (0xFFFFFFFFACCD0000) # # A09 is its control: the Play Duration is 300000000 = 0x11E1A300, byte 3 is # 0x11, and it reads correctly with or without the patch. Without A09 a red A11 # could be any parser fault; with it, the pair names the arithmetic. # # --------------------------------------------------------------------------- # THE U GROUP DRIVES AN UNINSTALLED HEADER, DELIBERATELY # # gnet_uri_new() and friends live in src/uri.h, which libmms does NOT install -- # so no consumer outside this tree can call them. They are asserted anyway # because mms_connect() and mmsh_connect() are built on them and every libmms # entry point starts there: a URL parser that mis-splits a host from a port # produces a connect to the wrong place, and the A/C groups can only see the # result through a Host header. The bbappend compiles the probe with -I${S}/src # for this reason, and it is the one thing in this suite that is testing an # internal rather than the installed surface. # # --------------------------------------------------------------------------- # TWO DOCUMENTED CONTRACTS THIS LIBRARY DOES NOT HONOUR, NOT EXERCISED # # * mms_set_default_io_impl(NULL) is documented in mmsio.h as reverting to the # built-in POSIX implementation. It dereferences io->select on its first # line. The D group passes a saved copy of the real struct instead; a case # that segfaulted would end the suite rather than report anything. # * every mms*_get_* accessor dereferences its handle with no NULL check, so # "the accessors are safe on a failed handle" is not true here. A failed # connect returns NULL and the caller must stop; the E group asserts the # NULL, which is the whole of the error contract that exists. # # Both are recorded rather than tested, because the only way to test them is to # take the machine down with the suite on it. PROBE=${BST_LIBMMS_PROBE:-/usr/libexec/libmms-tests/libmms-probe} ARCHIVE=/usr/lib/libmms.a PCFILE=/usr/lib/pkgconfig/libmms.pc INC=/usr/include/libmms # The version the recipe pins. Asserted rather than read back from the library, # so a recipe bumped without updating this suite is a red here instead of a # silent change of subject -- the pinned values above are the oracle for THIS # version only. WANT_VERSION=0.6.4 if [ ! -f "$ARCHIVE" ]; then bst_skip libmms/present "libmms not installed on this image" exit 0 fi bst_ok libmms/present # --- the package is whole ----------------------------------------------------- check libmms/staticlib test -s "$ARCHIVE" check libmms/pkgconfig test -s "$PCFILE" check libmms/hdr-mms test -s "$INC/mms.h" check libmms/hdr-mmsx test -s "$INC/mmsx.h" check libmms/hdr-mmsh test -s "$INC/mmsh.h" check libmms/hdr-mmsio test -s "$INC/mmsio.h" check libmms/hdr-config test -s "$INC/mms_config.h" # uri.h is INTERNAL and must stay uninstalled: the U group drives it out of the # archive, which is a different thing from it being part of the API. A day it # appears here is a packaging change somebody should have to explain. check libmms/hdr-uri-absent test ! -e "$INC/uri.h" # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here would be a conversion and not a policy breach. bst_lib_shape libmms/no-shared "/usr/lib/libmms.so" "/usr/lib/libmms.so.0" # The .pc must name the version this suite pinned its literals against. pcver=$(sed -n 's/^Version: *//p' "$PCFILE" 2>/dev/null | head -n 1) check_eq libmms/pkgconfig-version "$WANT_VERSION" "$pcver" # meta-oe's recipe strips `Requires: glib-2.0' from the .pc, which upstream left # behind when the GLib dependency went away in 0.6.3. If it came back, every # consumer of this library would drag in a GLib that is not on this image. if grep -q '^Requires:' "$PCFILE" 2>/dev/null; then bst_notok libmms/pkgconfig-no-requires "the .pc carries $(sed -n 's/^Requires:/Requires:/p' "$PCFILE" | head -n 1)" else bst_ok libmms/pkgconfig-no-requires fi # Cflags names ${includedir} and not ${includedir}/libmms, so the include is # . Pinned because a consumer that got this wrong would fail to # compile for a reason nobody would look for in the .pc. pccflags=$(sed -n 's/^Cflags: *//p' "$PCFILE" 2>/dev/null | head -n 1) check_eq libmms/pkgconfig-cflags "-I\${includedir}" "$pccflags" # LARGE FILE SUPPORT. configure.in decides LIBMMS_HAVE_64BIT_OFF_T with an # AC_TRY_RUN, which a cross build cannot run, so the macro is left undefined. # That answer is FALSE here -- off_t is 64 bits on this arm -- and it costs # nothing, because mms_off_t then resolves to off_t and lands on the same width # the macro would have selected. Asserted from the INSTALLED header rather than # from the probe, so this case and S03 below are two independent readings of the # same fact and a packaging change that moved one would not move the other. # # THE PAIR HAS ALREADY EARNED ITS KEEP. On the batch-1997 image they disagreed: # this case passed and S03 went red with `want:'undefined' got:'defined''. The # 0.6.4 tarball ships a pre-generated src/mms_config.h saying `#define # LIBMMS_HAVE_64BIT_OFF_T 1' beside the mms_config.h.in it is generated from, # and mmsio.h's QUOTED `#include "mms_config.h"' resolves relative to ${S}/src # before any -I -- so the library's own sources and the probe read the shipped # copy while `make install' shipped config.status's. The bbappend now removes # the stale copy before configure runs and asserts the macro's state at build # time with the probe's own include flags. Two readings of one fact are only # worth having if they can disagree, and this is the disagreement they caught. if grep -q '^/\* *#undef LIBMMS_HAVE_64BIT_OFF_T \*/' "$INC/mms_config.h" 2>/dev/null; then bst_ok libmms/lfs-undefined else bst_notok libmms/lfs-undefined "installed mms_config.h does not carry '#undef LIBMMS_HAVE_64BIT_OFF_T': $(_bst_1line "$(grep LIBMMS_HAVE_64BIT_OFF_T "$INC/mms_config.h" 2>/dev/null)")" fi # The archive carries the API. grep rather than nm: the image ships no binutils # nm, and the names are present as plain bytes. -a where the grep supports it -- # on this arm the archive members are LLVM IR bitcode (ThinLTO), so a grep that # suppresses matches in a file containing NULs reports nothing; GNU grep needs # -a, busybox grep as shipped does not. Probing keeps this measuring libmms # rather than measuring the image's grep. if echo x | grep -aq x 2>/dev/null; then _grepq="grep -aq"; else _grepq="grep -q"; fi for sym in mms_connect mms_read mms_seek mms_time_seek mms_close \ mms_peek_header mms_get_length mms_get_time_length \ mms_get_raw_time_length mms_get_file_time mms_get_current_pos \ mms_get_asf_header_len mms_get_asf_packet_len mms_get_seekable \ mms_set_default_io_impl mms_get_default_io_impl \ mmsh_connect mmsh_read mmsh_close \ mmsx_connect mmsx_read mmsx_close mmsx_peek_header \ mmsx_get_length mmsx_get_seekable \ gnet_uri_new gnet_uri_delete gnet_uri_escape gnet_uri_unescape \ gnet_mms_helper; do if $_grepq "$sym" "$ARCHIVE" 2>/dev/null; then bst_ok "libmms/sym-$sym" else bst_notok "libmms/sym-$sym" "$sym absent from $ARCHIVE" fi done # --- the probe ---------------------------------------------------------------- # Not `test -x' on the shipped payload: do_install writes it 0755 into its own # package, but the rootfs is read-only at browser runtime and asserting the bit # where it sits is the inversion README.md section 4b names. Assert the file, # and let the run carry the executable question. check libmms/probe-shipped test -f "$PROBE" W=${TMPDIR:-/tmp}/bst-libmms.$$ mkdir -p "$W" 2>/dev/null if [ ! -f "$PROBE" ]; then bst_skip libmms/probe-runs "$PROBE absent from the image" rm -rf "$W" exit 0 fi pout=$W/probe.out bst_probe libmms/probe-runs "$pout" 120 "$PROBE" prc=$? if [ "$prc" -ne 0 ] && bst_execfail "$(head -c 200 "$pout.err" 2>/dev/null)"; then bst_unrun libmms/probe-complete "$(head -c 200 "$pout.err" 2>/dev/null)" rm -rf "$W" exit 0 fi if grep -q '^PROBE-END$' "$pout" 2>/dev/null; then bst_ok libmms/probe-complete else bst_notok libmms/probe-complete "no PROBE-END terminator (last line: $(_bst_1line "$(tail -n 1 "$pout" 2>/dev/null)"))" fi # val -- the probe's value for one case, empty when the probe stopped # first. Matched on the ID in field 1 rather than taken by position, so a line # libmms itself wrote to stderr cannot shift the answers. val() { awk -v k="$1" '$1==k { $1=""; sub(/^ /,""); print; exit }' "$pout"; } # want -- score one probe value against a pinned answer. # A missing value is reported as the probe having stopped early rather than as a # mismatch against '', which is a different fault and localises to a different # line. want() { _got=$(val "$1") if [ -z "$_got" ]; then bst_notok "libmms/$2" "no value on target (probe stopped before $1); want='$3'" elif [ "$_got" = "$3" ]; then bst_ok "libmms/$2" else bst_notok "libmms/$2" "want:'$3' got:'$_got'" fi } # --- the fixture the probe built ---------------------------------------------- # Not a libmms result: it is the probe asserting its own arithmetic before it # asserts anything about the library. A wrong F01 makes every A and B value # meaningless, and this is the case that says so first. want F01 fixture-len "412" # --- arch facts --------------------------------------------------------------- # These four are the one place in this file where the x86-64 oracle and the # target agree on the VALUES for different reasons, so the reasons are written # down rather than the agreement being taken as confirmation. # # On x86-64 configure's AC_TRY_RUN runs, LIBMMS_HAVE_64BIT_OFF_T is defined, and # mms_off_t is int64_t. Here the AC_TRY_RUN cannot run and the macro is left # undefined -- a FALSE answer, because bits/typesizes.h takes the # `__TIMESIZE == 64 && __WORDSIZE == 32' branch on this arm and off_t is # __SQUAD_TYPE unconditionally. mms_off_t is then off_t, which is 64 bits # anyway. Both spellings land on the same width, which is why the wrong macro # costs nothing here; S03 is the case that records that it IS wrong. # # MEASURED, not predicted: the width comes from a _Static_assert the bbappend # compiles with the target CC during do_compile, and the macro from a second # build-time compile that uses the probe's own include flags. An earlier draft # of this file predicted 4 from a misread of typesizes.h and the build's own # probe corrected it; S03 below then went red on the first target run, for the # shipped-header reason written up beside lfs-undefined above. want S01 mms-off-t-width "8" want S02 off-t-width "8" want S03 lfs-macro "undefined" # The property that has to hold whatever the two widths are: the library # compiles mmsio.h with __MMS_C__ set and a consumer does not, so if the two # spellings of mms_off_t ever differ in width the mms_io_t callbacks are called # through the wrong prototype. want S04 lfs-consistent "consistent" # --- U: gnet_uri_new(), where every entry point starts ------------------------ # A plain mms:// url. The port is 0 out of the parser -- the DEFAULT is applied # later, by whichever protocol arm takes the url, and C01 below is where that is # asserted against the library rather than against gnet. want U01 uri-plain-scheme "mms" want U02 uri-plain-host "media.example.com" want U03 uri-plain-port "0" want U04 uri-plain-path "/clip.wmv" want U05 uri-port-scheme "mmsh" want U06 uri-port-host "media.example.com" want U07 uri-port-port "8080" want U08 uri-port-path "/dir/clip.wmv" # An IPv6 literal host: the brackets are the delimiter and must not survive into # the hostname, and the colon after them must still be read as the port. want U09 uri-v6-scheme "mms" want U10 uri-v6-host "2001:db8::1" want U11 uri-v6-port "1755" want U12 uri-v6-path "/v6.wmv" # userinfo is split off and does not leak into the hostname. want U13 uri-user-scheme "mmsh" want U14 uri-user-host "host.example" want U15 uri-user-port "0" want U16 uri-user-path "/a/b.wmv" # THE ONE SURPRISE IN THIS GROUP, PINNED RATHER THAN LEFT TO EXPECTATION. The # parser is documented as liberal ("does not validate the URI -- it will accept # some malformed URI"), and a non-numeric port is one of those: the port stays 0 # and the path begins where the digits stopped, WITHOUT the leading slash. So # the ticket's "a malformed URL is refused rather than accepted with defaults" # holds for the structural cases (U21-U23) and NOT for this one. Asserting what # the library does is the only way that fact reaches a reader. want U17 uri-badport-scheme "mms" want U18 uri-badport-host "host.example" want U19 uri-badport-port "0" want U20 uri-badport-path "notaport/x.wmv" # Structurally impossible, and these ARE refused: an authority with no host, an # empty IPv6 literal, and a url that is only whitespace. want U21 uri-nohost-refused "REFUSED" want U22 uri-emptyv6-refused "REFUSED" want U23 uri-blank-refused "REFUSED" # Query and fragment are split off the path rather than left in it, and the # query keeps its percent-coding at this stage. want U24 uri-frag-path "/p/c.wmv" want U25 uri-frag-query "tok=a%20b" want U26 uri-frag-fragment "f1" # ESCAPING IS NOT AN INVOLUTION, AND THAT IS THE MEASURED ANSWER. mms_connect() # unescapes the url before it uses the fields, so a percent-coded path has to # survive that. %20 round-trips; %2F does NOT -- unescape turns it into a real # '/' and escape does not put it back, because '/' is a path delimiter rather # than a character needing coding. U27/U28 pin both halves so the asymmetry is # a recorded property of the library instead of a surprise to the next reader. want U27 uri-unescaped "/a b/c.wmv" want U28 uri-reescaped "/a%20b/c.wmv" # gnet_mms_helper() is what becomes the HTTP request target: LEADING slashes # stripped, one slash prepended, query re-attached -- and interior double # slashes left alone, which is why the pinned answer has one. want U29 uri-helper-abs "/dir//clip.wmv?x=1" want U30 uri-helper-rel "dir//clip.wmv?x=1" # --- D: the default-io registry ----------------------------------------------- want D01 default-io-present "non-NULL" want D02 default-io-connect "installed" want D03 default-io-read "installed" want D04 default-io-restore "restored" # --- E: the error contract ---------------------------------------------------- # A transport that refuses must come back as NULL from every entry point, # without blocking and without aborting. want E01 err-mmsx-null "NULL" # ONE connect, not two, and that is a fact about the scheme lists rather than # about the transport: mmsx tries mmsh (whose list is mms, mmsh) and then mms # (whose list is mms, mmst), so an mmsh:// url reaches the transport from the # mmsh arm only. A 2 here would mean mms.c had started accepting mmsh. want E02 err-mmsx-connects "1" want E03 err-mms-null "NULL" want E04 err-mms-connects "1" # An unsupported scheme is refused BEFORE the transport is touched. The zero is # the assertion; the NULL beside it would be satisfied by a library that dialled # out first and failed afterwards. want E05 err-scheme-null "NULL" want E06 err-scheme-connects "0" # mmst:// is refused by the mmsh arm without connecting and reaches the # transport once, from the mms arm -- the mirror image of E01/E02. want E07 err-mmst-null "NULL" want E08 err-mmst-connects "1" want E09 err-nullurl-null "NULL" want E10 err-nullurl-connects "0" want E11 err-mms-nullurl "NULL" # --- A: the scripted MMSH conversation ---------------------------------------- want A01 asf-connected "connected" # What the library ASKED FOR. This is the port's URL handling measured through # the library's own path rather than through gnet: the request target comes out # of gnet_mms_helper(), the Host header out of the parsed hostname and port. want A02 http-request-line "GET /dir/clip.wmv?token=abc HTTP/1.0" want A03 http-host "media.example.com:8080" # Two connects for one mmsh_connect(): the protocol's own first/second request # exchange, the second carrying the stream selection made from the first. want A04 http-connects "2" # The selection made from the parsed bitrates, going back on the wire. 0 selects # a stream and 2 disables it, so this line says the parser found three streams, # gave them the right ids, and the selector kept the audio and the 400 kbps # video at 600000 bps. want A05 http-stream-entry "ffff:1:0 ffff:2:0 ffff:3:2" want A06 http-stream-count "3" # What the parser made of the fixture. want A07 asf-header-len "412" want A08 asf-packet-len "2888" want A09 asf-raw-time-len "300000000" want A10 asf-time-len "30.000" # THE bswap PATCH'S CASE -- see the header of this file. 18446744072313700352 # here means the patch is absent, not that the parser is lost. want A11 asf-file-time "130000000000000000" # mms_get_length() deliberately does NOT return the File Size: it returns # header_len + packets * packet_len, because an ASF file's index object is not # reachable over MMS. 412 + 2000 * 2888. want A12 asf-length "5776412" # From `Pragma: features="seekable"' in the HTTP answer, not from the ASF # header -- so this is the answer parser rather than the object parser. want A13 asf-seekable "1" want A14 asf-current-pos "0" # The header the library hands back is the fixture with the dropped stream's # bitrate zeroed and NOTHING ELSE touched. want A15 peek-len "412" # 2 rather than 4 because 96000 = 0x00017700 has two zero bytes of its own. want A16 peek-diff-count "2" # The count alone would be satisfied by a parser that scribbled anywhere; this # is the case that says WHERE. want A17 peek-diff-located "in-record" # --- B: the bandwidth-driven selector ----------------------------------------- # 600000 bps: audio (32 kbps) and the 400 kbps video both fit, so stream 3 is # dropped and its bitrate is zeroed in the header the demuxer will see. want B01 sel-hi-stream1 "32000" want B02 sel-hi-stream2 "400000" want B03 sel-hi-stream3 "0" # 200000 bps: the 400 kbps video no longer fits, so the selector takes the # 96 kbps one and it is stream 2 that gets zeroed. Same fixture, same parse, # different answer -- which is what makes this an assertion about the SELECTOR # rather than about the byte offsets. want B04 sel-lo-stream-entry "ffff:1:0 ffff:2:2 ffff:3:0" want B05 sel-lo-stream1 "32000" want B06 sel-lo-stream2 "0" want B07 sel-lo-stream3 "96000" # --- C: the default port ------------------------------------------------------ # No port in the url: gnet_uri_new() leaves 0 (U03) and mmsh_connect() # substitutes DEFAULT_PORT, which for the HTTP-framed arm is 80. That default is # observable from outside only through the Host header, which is why it is # asserted here and not in the U group. want C01 default-port-host "media.example.com:80" want C02 default-port-line "GET /clip.wmv HTTP/1.0" rm -rf "$W"