. "$BST_LIB" # BST-TIMEOUT: 300 # ~12 wayland-scanner spawns over fixtures the suite writes, # plus five bounded probe phases. Nothing here is large. # wayland 1.22.0 -- the Wayland core: libwayland-client, libwayland-server, # libwayland-cursor and wayland-scanner. # # THE PORT SPLITS THREE WAYS ON THIS IMAGE and the suite is organised by that # split rather than by library, because two thirds of it is decided by the # platform and not by wayland: # # 1. wayland-scanner RUNS. It is a pure XML-to-C transformation over expat and # libxml2 and touches neither epoll nor libffi, so every assertion about it # is an assertion about this build. It gets the bulk of the cases. # 2. libwayland-client MARSHALS and DISPATCHES. Every inbound event reaches # its listener through wl_closure_invoke -> ffi_call, which on this arm # goes through libffi's compiled trampoline table. Every Wayland argument # lowers to i32, so every listener signature is inside it. Both directions # are compared byte for byte against the native build of the same probe. # 3. libwayland-SERVER cannot start at all. wl_display_create() builds a # wl_event_loop, wl_event_loop_create() is epoll_create1() with no # alternative backend, and epoll is ENOSYS here (brintos/linux#53). # # The server gap is asserted TWICE, and the pair is deliberate. The first case is # a WITNESS: it asserts the exact failure the platform produces today, so a # regression in either direction -- a different errno, a call that silently # starts returning garbage -- is caught. The second is an attributed `not ok' # naming the capability that is missing, so the gap stays visible in the report. # A witness alone would read as full coverage of a library that cannot run. # # EVERY FIXTURE IS WRITTEN BY THIS FILE. Nothing is read from a compositor, a # peer or a terminal, and no protocol XML is taken from wayland-protocols -- # the point of the scanner cases is to vary one construct at a time, which a # shipped protocol cannot do. tmp=${TMPDIR:-/tmp}/bst-wayland.$$ mkdir -p "$tmp" # The first result line of a suite is dropped in transport # (brintos/linux#96), so the first case is one whose loss costs nothing. bst_ok wayland/suite-canary # NOT `require': that ends the file, and the probe half below does not depend on # the scanner at all. A missing scanner should cost the scanner cases, not the # only coverage libwayland-client has. if command -v wayland-scanner > /dev/null 2>&1; then bst_ok wayland/scanner-present have_scanner=yes else bst_skip wayland/scanner-present "wayland-scanner not on this image" have_scanner=no fi if [ "$have_scanner" = yes ]; then # =========================================================================== # 1. wayland-scanner -- the code generator, which is the half that runs. # =========================================================================== check_out wayland/scanner-version "wayland-scanner 1.22.0" wayland-scanner --version # --- 1a. The basic fixture: requests, events, an enum, new_id, array, fd. --- cat > "$tmp/basic.xml" <<'XEOF' BRINTOS-2226 fixture A fixture interface. XEOF check wayland/scanner-client-header \ wayland-scanner client-header "$tmp/basic.xml" "$tmp/basic-client.h" check wayland/scanner-server-header \ wayland-scanner server-header "$tmp/basic.xml" "$tmp/basic-server.h" check wayland/scanner-private-code \ wayland-scanner private-code "$tmp/basic.xml" "$tmp/basic-code.c" # THE OPCODES ARE THE ORDER OF THE REQUESTS IN THE FILE, and that is the whole # of the wire contract a client and a compositor have to agree on. A generator # that renumbered them would produce headers that compile, code that links, and # a connection in which every request means a different request. check_out wayland/scanner-opcode-0 "#define BST_THING_POKE 0" cat "$tmp/basic-client.h" check_out wayland/scanner-opcode-1 "#define BST_THING_SPAWN 1" cat "$tmp/basic-client.h" check_out wayland/scanner-opcode-2 "#define BST_THING_STASH 2" cat "$tmp/basic-client.h" # `since' reaches the header as the version floor, per event. check_out wayland/scanner-since-1 "#define BST_THING_POKED_SINCE_VERSION 1" \ cat "$tmp/basic-client.h" check_out wayland/scanner-since-2 "#define BST_THING_GONE_SINCE_VERSION 2" \ cat "$tmp/basic-client.h" # The enum: the entry VALUES, not a renumbering of them. 7 is in the fixture # precisely because a generator that emitted a bare C enum would turn it into 2. check_out wayland/scanner-enum-plain "BST_THING_FLAVOUR_PLAIN = 0" cat "$tmp/basic-client.h" check_out wayland/scanner-enum-salted "BST_THING_FLAVOUR_SALTED = 1" cat "$tmp/basic-client.h" check_out wayland/scanner-enum-sweet "BST_THING_FLAVOUR_SWEET = 7" cat "$tmp/basic-client.h" check_out wayland/scanner-enum-guard "#ifndef BST_THING_FLAVOUR_ENUM" \ cat "$tmp/basic-client.h" # THE TWO HEADERS NUMBER DIFFERENT THINGS UNDER ONE NAME SPACE, and that is the # trap worth pinning. The client header's bare opcode defines are the REQUESTS # (poke=0); the server header's are the EVENTS (poked=0, gone=1). So # BST_THING_POKE exists only on the client side and BST_THING_POKED only on the # server side, both numbered from zero, and a header included on the wrong side # fails to compile rather than silently meaning something else -- which is the # good outcome, and is only true because the names differ. check_out wayland/scanner-server-event-0 "#define BST_THING_POKED 0" \ cat "$tmp/basic-server.h" check_out wayland/scanner-server-event-1 "#define BST_THING_GONE 1" \ cat "$tmp/basic-server.h" check_out wayland/scanner-server-iface \ "extern const struct wl_interface bst_thing_interface" cat "$tmp/basic-server.h" # The event senders are the server side's entire generated API surface. check_out wayland/scanner-server-send \ "bst_thing_send_poked(struct wl_resource *resource_, uint32_t serial, const char *what)" \ cat "$tmp/basic-server.h" # THE SIGNATURE STRING IS THE MARSHALLING CONTRACT. Each letter is one wire # argument: u uint, n new_id, a array, h fd, s string, and a leading `?' makes # the argument nullable. `2' on the `gone' event is the since-version, which the # signature carries as a leading digit rather than as a separate field. check_out wayland/scanner-sig-poke '{ "poke", "u",' cat "$tmp/basic-code.c" check_out wayland/scanner-sig-spawn '{ "spawn", "n",' cat "$tmp/basic-code.c" check_out wayland/scanner-sig-stash '{ "stash", "ah",' cat "$tmp/basic-code.c" check_out wayland/scanner-sig-poked '{ "poked", "u?s",' cat "$tmp/basic-code.c" check_out wayland/scanner-sig-gone '{ "gone", "2",' cat "$tmp/basic-code.c" check_out wayland/scanner-sig-destroy '{ "destroy", "",' cat "$tmp/basic-code.c" # The interface object the two headers declare and this file defines, with its # request and event counts. check_out wayland/scanner-iface-def \ "WL_PRIVATE const struct wl_interface bst_thing_interface" cat "$tmp/basic-code.c" check_out wayland/scanner-iface-counts "3, bst_thing_requests," cat "$tmp/basic-code.c" # --- 1b. Every argument type the DTD allows, in one request. --- cat > "$tmp/alltypes.xml" <<'XEOF' XEOF check wayland/scanner-alltypes \ wayland-scanner private-code "$tmp/alltypes.xml" "$tmp/alltypes.c" # Eight declared arguments, eight signature letters, in declaration order. check_out wayland/scanner-sig-alltypes '{ "all", "iufsonah",' cat "$tmp/alltypes.c" check_out wayland/scanner-sig-nullable '{ "nullable", "?s?oa",' cat "$tmp/alltypes.c" # AN UNTYPED new_id IS THREE WIRE ARGUMENTS, NOT ONE -- interface name, version, # then the id -- which is why wl_registry.bind carries a string a client has to # encode correctly. `sun' from three characters of XML is the whole rule, and # the probe measures the same rule from the other end, as bytes on a socket. check_out wayland/scanner-sig-untyped '{ "untyped", "sun",' cat "$tmp/alltypes.c" # The types table is where an interface-typed argument is resolved; a NULL entry # is an argument with no interface. Two of the three requests contribute one # each, which is what pins the offsets the wl_message rows point into. check_out wayland/scanner-types-table "&bst_every_interface," cat "$tmp/alltypes.c" # --- 1c. What the scanner REFUSES, and how. --- # Not well-formed XML. MEASURED: the scanner does not exit, it ABORTS -- # src/scanner.c:132 answers a failed xmlCtxtReadFd with abort() rather than # exit(EXIT_FAILURE), so the caller sees SIGABRT (128+6=134) and a core, not a # status. That is upstream's shape in 1.22.0 and is asserted at its measured # value; a release that turns it into a clean exit turns this case red, which is # the correct way to find out. printf '\n\n \n' \ > "$tmp/malformed.xml" mal=$(wayland-scanner client-header "$tmp/malformed.xml" "$tmp/mal.h" 2>&1) mal_rc=$? check_eq wayland/scanner-malformed-rc 134 "$mal_rc" case "$mal" in *"Failed to read XML"*) bst_ok wayland/scanner-malformed-diag ;; *) bst_notok wayland/scanner-malformed-diag "$(_bst_1line "$mal")" ;; esac # The output file is CREATED AND TRUNCATED before the parse is known to have # succeeded, so a refusal leaves a zero-length header behind rather than no file # -- which a build system reading "did the header appear" would take for # success. check wayland/scanner-malformed-output-empty test ! -s "$tmp/mal.h" check wayland/scanner-malformed-output-exists test -e "$tmp/mal.h" # DTD validation. MEASURED, AND NOT WHAT THE NAME SUGGESTS: a protocol that # fails the built-in DTD is a WARNING and the scanner exits 0, emitting the code # anyway. Only --strict turns it into a refusal. Both halves are asserted, # because the default is the one a build system gets by accident. cat > "$tmp/invalid.xml" <<'XEOF' XEOF inv=$(wayland-scanner client-header "$tmp/invalid.xml" "$tmp/inv.h" 2>&1) inv_rc=$? check_eq wayland/scanner-dtd-lax-rc 0 "$inv_rc" case "$inv" in *"WARNING: XML failed validation against built-in DTD"*) bst_ok wayland/scanner-dtd-lax-warns ;; *) bst_notok wayland/scanner-dtd-lax-warns "$(_bst_1line "$inv")" ;; esac case "$inv" in *"No declaration for element banana"*) bst_ok wayland/scanner-dtd-names-element ;; *) bst_notok wayland/scanner-dtd-names-element "$(_bst_1line "$inv")" ;; esac wayland-scanner --strict client-header "$tmp/invalid.xml" "$tmp/inv2.h" > "$tmp/strict.out" 2>&1 check_eq wayland/scanner-dtd-strict-rc 1 "$?" # allow-null ON AN ARRAY IS REFUSED BY A MESSAGE THAT SAYS ARRAYS ARE VALID. # src/scanner.c:411 is_nullable_type() returns true for STRING and OBJECT and # falls through to false for everything else, while the diagnostic at line 836 # names "objects, strings, and arrays". The wire format has a nullable array -- # a `?a' signature is representable -- so this is a generator that cannot emit # something the protocol allows, and the error message is the reason nobody # notices. UPSTREAM DEFECT, asserted at its wrong value: a wayland release that # repairs either half turns this case red. cat > "$tmp/arraynull.xml" <<'XEOF' XEOF an=$(wayland-scanner private-code "$tmp/arraynull.xml" "$tmp/an.c" 2>&1) an_rc=$? check_eq wayland/scanner-array-null-rc 1 "$an_rc" case "$an" in *"allow-null is only valid for objects, strings, and arrays"*) bst_ok wayland/scanner-array-null-diag ;; *) bst_notok wayland/scanner-array-null-diag "$(_bst_1line "$an")" ;; esac # The scanner with no arguments is a usage message and a non-zero exit, not a # read of stdin that blocks -- which on a target with no terminal would hang the # suite rather than fail it. usg=$(wayland-scanner 2>&1) usg_rc=$? check_eq wayland/scanner-usage-rc 1 "$usg_rc" case "$usg" in *"usage: wayland-scanner"*) bst_ok wayland/scanner-usage-diag ;; *) bst_notok wayland/scanner-usage-diag "$(_bst_1line "$usg")" ;; esac # --- 1d. The SHIPPED protocol, through the shipped scanner. --- # # Everything above this point is a fixture. wayland.xml is the real thing -- # the core protocol every Wayland client and compositor is generated from, and # the same file libwayland-client's own marshalling code was generated from at # build time. Running the target's scanner over it and getting back the # signatures the library uses is what ties the two halves of this port together. REALXML=/usr/share/wayland/wayland.xml if [ -f "$REALXML" ] && wayland-scanner private-code "$REALXML" "$tmp/real.c" 2>"$tmp/real.err"; then bst_ok wayland/scanner-real-protocol check_out wayland/real-sig-sync '{ "sync", "n",' cat "$tmp/real.c" check_out wayland/real-sig-get-registry '{ "get_registry", "n",' cat "$tmp/real.c" check_out wayland/real-sig-create-surface '{ "create_surface", "n",' cat "$tmp/real.c" # THE ONE THAT MATTERS, and the reason the untyped new_id case above is in # this suite at all: wl_registry.bind is `usun' -- name, then the interface # STRING and version the untyped new_id expands into, then the id. The probe # measures the same four arguments from the other end, as bytes a client put # on a socket. Two derivations of one rule, sharing no code. check_out wayland/real-sig-bind '{ "bind", "usun",' cat "$tmp/real.c" check_out wayland/real-iface-display \ "WL_PRIVATE const struct wl_interface wl_display_interface" cat "$tmp/real.c" check_out wayland/real-iface-compositor \ "WL_PRIVATE const struct wl_interface wl_compositor_interface" cat "$tmp/real.c" check_out wayland/real-display-request-count "2, wl_display_requests," cat "$tmp/real.c" else bst_notok wayland/scanner-real-protocol \ "$(_bst_1line "$(cat "$tmp/real.err" 2>/dev/null)")" fi fi # have_scanner # =========================================================================== # 2. The shipped tree. # # This image installs no -dev and no -staticdev package, so the archives, the # headers and the .pc files are NOT on the target and are deliberately not # asserted here -- a `test -f /usr/lib/libwayland-client.a' would be red on # every image that ever built this recipe. That the archives are LINKABLE is # established by the probe existing at all: it is cross-linked in do_compile # against those very archives, and an undefined symbol in a static archive is # not an error until something links it. # =========================================================================== check wayland/scanner-binary test -f /usr/bin/wayland-scanner check wayland/protocol-xml test -f /usr/share/wayland/wayland.xml check wayland/protocol-dtd test -f /usr/share/wayland/wayland.dtd # =========================================================================== # 3. The probe -- libwayland-client driven against a server that is not # libwayland-server. # =========================================================================== ORACLE=$BST_DATA/wayland-native.txt check wayland/probe-shipped test -f "$BST_DATA/wayland-probe" check wayland/oracle-shipped test -f "$ORACLE" probe=$tmp/wayland-probe if cp "$BST_DATA/wayland-probe" "$probe" 2>/dev/null && chmod +x "$probe" 2>/dev/null; then bst_ok wayland/probe-stage else bst_notok wayland/probe-stage "cannot stage $BST_DATA/wayland-probe" rm -rf "$tmp" exit 0 fi # --- 3a. Version, from the headers the probe compiled against. --- bst_bound 20 "$probe" version > "$tmp/version.out" 2>&1 check_eq wayland/probe-version-rc 0 "$?" check_out wayland/probe-version "WAYLAND_VERSION=1.22.0" cat "$tmp/version.out" check_out wayland/probe-version-pv "PROBE_BUILT_AGAINST=1.22.0" cat "$tmp/version.out" check_out wayland/probe-iface-display "WL_DISPLAY_INTERFACE=wl_display" cat "$tmp/version.out" check_out wayland/probe-iface-registry "WL_REGISTRY_INTERFACE=wl_registry" cat "$tmp/version.out" # --- 3b. wl_display_connect's negative paths, which must FAIL rather than block. # # A client asked for a compositor that is not there has to come back, and come # back with an errno the caller can print. bst_bound 20 "$probe" connect > "$tmp/connect.out" 2>&1 check_eq wayland/probe-connect-rc 0 "$?" # ENOENT (2), from connect(2) on a socket path that does not exist. check_out wayland/connect-missing-socket "CONNECT_MISSING=null errno=2" cat "$tmp/connect.out" # No XDG_RUNTIME_DIR: the library substitutes ENOENT ITSELF, deliberately, so # that a caller does not report "failed to create display: Success". check_out wayland/connect-no-xdg "CONNECT_NOXDG=null errno=2" cat "$tmp/connect.out" check_out wayland/connect-no-xdg-diag \ "XDG_RUNTIME_DIR is invalid or not set" cat "$tmp/connect.out" # WAYLAND_SOCKET naming a descriptor that is not open. check_out wayland/connect-bad-fd "CONNECT_BADFD=null" cat "$tmp/connect.out" # --- 3c. Marshalling: the client's bytes, read back by a hand-written server. # # This is the half of libwayland-client this image can run, and it is compared # against the SAME probe built natively at do_compile, phase for phase. Two # builds of one source agreeing on the flushed byte count, the ids the client # allocated and the length-prefixed interface string is the strongest statement # available about an encoder whose failure mode is bytes nobody looks at. bst_bound 20 "$probe" marshal > "$tmp/marshal.out" 2>&1 check_eq wayland/probe-marshal-rc 0 "$?" check_out wayland/marshal-fork "M_FORK=0" cat "$tmp/marshal.out" check_out wayland/marshal-connect "M_CONNECT=ok" cat "$tmp/marshal.out" check_out wayland/marshal-registry "M_GET_REGISTRY=1" cat "$tmp/marshal.out" check_out wayland/marshal-bind "M_BIND=1" cat "$tmp/marshal.out" check_out wayland/marshal-surface "M_CREATE_SURFACE=1" cat "$tmp/marshal.out" check_out wayland/marshal-sync "M_SYNC=1" cat "$tmp/marshal.out" check_out wayland/marshal-no-error "M_DISPLAY_ERROR=0" cat "$tmp/marshal.out" # 76 bytes: get_registry 12, bind 40 (8 header + 4 name + 4+14+2 pad for # "wl_compositor" + 4 version + 4 new_id), create_surface 12, sync 12. The count # is asserted rather than merely non-zero because it is the one number that goes # wrong when a string is padded to the wrong boundary. check_out wayland/marshal-flush-bytes "M_FLUSH=76" cat "$tmp/marshal.out" # What the server SAW -- one request of each shape, and nothing it could not # parse. SRV_UNEXPECTED counts messages that matched no rule; SRV_TAIL counts # bytes left over at EOF, so a short or over-long size field shows up as a # remainder rather than as a silent truncation. check_out wayland/srv-getregistry "SRV_GETREGISTRY=1" cat "$tmp/marshal.out" check_out wayland/srv-bind "SRV_BIND=1" cat "$tmp/marshal.out" check_out wayland/srv-bind-name "SRV_BIND_NAME=1" cat "$tmp/marshal.out" check_out wayland/srv-bind-iface "SRV_BIND_IFACE=wl_compositor" cat "$tmp/marshal.out" # 14 = strlen("wl_compositor") + 1. The NUL is part of the wire length; the two # bytes of padding after it are not. check_out wayland/srv-bind-iface-len "SRV_BIND_IFACE_LEN=14" cat "$tmp/marshal.out" check_out wayland/srv-bind-version "SRV_BIND_VERSION=4" cat "$tmp/marshal.out" # The client allocates ids from 2 upward: registry 2, the bound compositor 3, # the surface 4. That sequence is the client's own id map, asserted from the # other end of the socket. check_out wayland/srv-bind-newid "SRV_BIND_NEWID=3" cat "$tmp/marshal.out" check_out wayland/srv-create-surface "SRV_CREATE_SURFACE=1" cat "$tmp/marshal.out" check_out wayland/srv-surface-id "SRV_SURFACE_ID=4" cat "$tmp/marshal.out" check_out wayland/srv-unexpected "SRV_UNEXPECTED=0" cat "$tmp/marshal.out" check_out wayland/srv-tail "SRV_TAIL=0" cat "$tmp/marshal.out" # And the whole block against the native build of the same source, byte for # byte. The wire is host byte order and both arms are little-endian and # 32-bit-clean for these fields, so anything that differs here is a real # divergence rather than an arch fact. sed -n '/^== marshal$/,/^== session$/p' "$ORACLE" | grep -v '^== ' > "$tmp/marshal.want" grep -v '^== ' "$tmp/marshal.out" > "$tmp/marshal.got" if [ -s "$tmp/marshal.want" ]; then if diff "$tmp/marshal.want" "$tmp/marshal.got" > "$tmp/marshal.diff" 2>&1; then bst_ok wayland/marshal-matches-native else bst_notok wayland/marshal-matches-native "$(_bst_1line "$(cat "$tmp/marshal.diff")")" fi else bst_notok wayland/marshal-matches-native "no marshal block in $ORACLE" fi # --- 3d. libwayland-server: the witness, and the capability it costs. --- bst_bound 20 "$probe" server > "$tmp/server.out" 2>&1 check_eq wayland/probe-server-rc 0 "$?" # ENOSYS is 38. wl_event_loop_create() returns NULL because epoll_create1() # does, and wl_display_create() returns NULL because it builds an event loop # first -- so the errno the second reports is still epoll's. check_out wayland/server-event-loop-null "EVENT_LOOP_CREATE=null errno=38" cat "$tmp/server.out" check_out wayland/server-display-null "DISPLAY_CREATE=null errno=38" cat "$tmp/server.out" bst_notok wayland/server-usable \ "libwayland-server cannot start: wl_event_loop_create is epoll_create1, ENOSYS on this kernel -- brintos/linux#53" # --- 3e. libwayland-client dispatch. # # The session phase is the marshal phase plus two round trips against a peer the # probe forks, so every event it receives reaches its listener through # wl_closure_invoke -> ffi_call. The steps before the first inbound event are # asserted one by one, so a phase that dies later keeps the facts it established. bst_bound 20 "$probe" session > "$tmp/session.out" 2>&1 check_eq wayland/session-rc 0 "$?" check_out wayland/session-socketpair "SOCKETPAIR=0" cat "$tmp/session.out" check_out wayland/session-fork "FORK=0" cat "$tmp/session.out" check_out wayland/session-connect "CONNECT_TO_FD=ok" cat "$tmp/session.out" check_out wayland/session-own-fd "DISPLAY_FD_IS_OURS=1" cat "$tmp/session.out" check_out wayland/session-registry "GET_REGISTRY=1" cat "$tmp/session.out" check_out wayland/session-roundtrip "ROUNDTRIP1=" cat "$tmp/session.out" # The dispatched half against the native build, byte for byte: the globals the # listener recorded, the bind and surface that depend on them, the second round # trip, and what the peer read off the socket. sed -n '/^== session$/,$p' "$ORACLE" | grep -v '^== ' > "$tmp/session.want" grep -v '^== ' "$tmp/session.out" > "$tmp/session.got" if [ -s "$tmp/session.want" ]; then if diff "$tmp/session.want" "$tmp/session.got" > "$tmp/session.diff" 2>&1; then bst_ok wayland/client-dispatch else bst_notok wayland/client-dispatch "$(_bst_1line "$(cat "$tmp/session.diff")")" fi else bst_notok wayland/client-dispatch "no session block in $ORACLE" fi rm -rf "$tmp"