. "$BST_LIB" # BST-TIMEOUT: 300 # 76 cases over ~30 execs of one 427 816 B wasm module, # NINE of which are BOUNDED because they may not return # (see the input-thread arm below): 8s once and 6s eight # times. MEASURED on this image at elapsed=58s -- the # watchdog fires on only one of the nine here, because in # the nothread arm tio stops itself at the thread and # returns. A machine where the thread merely never runs # pays the full 56s of watchdog time instead, which is why # the bound is not trimmed to what this arm cost. # tio 2.7 (meta-openembedded/meta-oe recipes-support/tio at SRCREV 1c32555c, # with a meta-brintos bbappend carrying one RDEPENDS line) -- a simple serial # device I/O tool. ONE binary, /usr/bin/tio, 427 816 B, out of fifteen .c files. # # WHAT IS ASSERTED HERE IS THE SURFACE IN FRONT OF THE INPUT THREAD, and that # boundary is the whole story of this port on this image. main() runs, in order: # options_parse (1st pass), config_file_parse, options_parse_final, # tty_configure, log_open, the banner, socket_configure, and only THEN # tty_input_thread_create() followed by tty_input_thread_wait_ready(). Every # assertion below that expects a RETURN lives before that pair. # # THE PAIR IS WHERE EVERY RUN STOPS ON THIS SUBSTRATE, AND NOT AS tio's DEFECT. # tty.c:384-394 is # # pthread_mutex_lock(&mutex_input_ready); # if (pthread_create(&thread, NULL, tty_stdin_input_thread, NULL) != 0) { # tio_error_printf("pthread_create() error"); exit(1); } # ... # pthread_mutex_lock(&mutex_input_ready); /* wait_ready */ # # and the only pthread_mutex_unlock of that mutex is tty.c:300, inside the # spawned thread. MEASURED on this image: the kernel REFUSES the thread-mode # clone outright -- "REFUSING thread-mode clone (CLONE_VM, flags=0x3d0f00) with # EAGAIN ... pthread_create() gets EAGAIN" -- so tio's own `!= 0` guard fires, # it prints its diagnostic and exit(1)s. It does NOT deadlock here: the second # lock is never reached. That refusal is brintos/linux-in-the-browser#85, the # same gap that ships pigz serial and leaves mbuffer inert. # # THE EARLIER READING OF THIS FILE PREDICTED A DEADLOCK -- pthread_create # returning 0 for a thread that never runs, and wait_ready() blocking forever. # That is what #85 does to some ports and it is NOT what happens to tio, whose # guard catches the refused clone. The difference is recorded rather than # quietly overwritten because it changes what the suite can assert: a program # that returns can be given a status and a diagnostic to check, and one that # hangs can only be bounded. The arm is PROBED at runtime over all three # outcomes, so the day #85 closes these cases assert tio's real connect answers # on their own evidence rather than on this comment. # # WHAT THAT COSTS THE PORT TICKET, stated plainly: BRINTOS-1900 asked for # `tio /dev/ttyS0` against an absent device to "report the absence with the # documented message and a non-zero status rather than block". Under #85 it # blocks -- and it would block under the DEFAULT invocation anyway, because # without -n/--no-autoconnect main enters `while (true) { tty_wait_for_device(); # tty_connect(); }` and waiting for the device to appear IS the documented # behaviour. The reporting arm is `-n`, and even that one is behind the thread. # # THE CONFIGURATION FILE IS NOT BEHIND IT, which is what makes this port worth # shipping non-interactively. config_file_parse() runs third, and it can be made # to say a great deal before anything blocks: # * every rejected value is echoed back BY LITERAL, with the option name it was # given for -- read_integer/read_boolean print "Invalid value '' for # option '' in configuration file" and exit 1 (configfile.c:113,127); # * an unknown key is a warning naming the key, and parsing continues; # * a malformed file is NOT refused -- config_file_parse() tests ini_parse()'s # return for `< 0` and a syntax error is reported as a POSITIVE line number, # so the "Unable to parse configuration file ()" branch is unreachable # for one. That is an upstream defect in tio 2.7, asserted below as it is # (7c) rather than as the manual describes it; # * --complete-sub-configs enumerates the section names and exits, touching # neither tty_configure nor the thread. # ACCEPTED values have no non-interactive echo at all: options_print() is # reachable only from handle_command_sequence (tty.c:676), i.e. from the ctrl-t # key handler, which needs the very thread that cannot start. So a valid # baudrate/databits/parity is asserted here as ACCEPTED-AND-SILENT against the # rejection cases that name their literal, and the profile's own selection is # asserted positively through the two side effects that land BEFORE the thread: # the log file log_open() creates, and the path socket_configure() prints for # the unix socket it binds -- the socket FILE is unlinked by an atexit handler, # so it does not survive a run that exits rather than one that is killed. # # THE CONFIG FILE IS NOT CALLED tiorc. resolve_config_file() (configfile.c:414) # tries $XDG_CONFIG_HOME/tio/config, then $HOME/.config/tio/config, then # $HOME/.tioconfig, and nothing else. The fixtures below use XDG_CONFIG_HOME so # the whole thing stays in the scratch tree and no case depends on $HOME. # # THE DEVICE-LISTING OPTION IS -L/--list-devices, not --list; --list works only # because it is an unambiguous getopt_long abbreviation, and that is asserted as # its own case rather than assumed. On Linux -- and __linux__ IS defined by this # toolchain, measured from the string "/dev/serial/by-id/" surviving into the # shipped binary -- it opendir()s /dev/serial/by-id/ and prints one line per # entry. That directory does not exist on this image, so the enumeration is # empty and the exit status is 0: opendir fails, the `if (d)` guard skips the # loop, and list_serial_devices() returns having printed nothing (tty.c:1600). # An empty listing is the DETERMINISTIC ANSWER here, not a failure, and the arm # is read at runtime so a machine that grows the directory asserts its contents. # # THE PACKAGE IS ONE FILE. /usr/bin/tio and nothing else: tio.1 splits to # tio-doc, which this image does not install, and the bash-completion script is # not built at all -- src/bash-completion/meson.build installs it only if # `dependency('bash-completion')` is found or -Dbashcompletiondir is given, and # neither holds here. The recipe's `FILES:${PN} += /usr/share/bash-completion/ # completions/tio` is therefore inert, which the packaging cases assert rather # than leave to be rediscovered. # # AND ITS RUNTIME DEPENDENCY LIST HAD TO BE CORRECTED, which is the one thing # the bbappend does. Upstream declares `RDEPENDS:${PN} += " libinih"`, but inih # is built --default-library static on this distro, so libinih-dev holds ini.h # and inih.pc, libinih-staticdev holds libinih.a, and the runtime libinih # package is EMPTY and never written as a .deb. Left alone that is an # unsatisfiable dependency and do_rootfs fails -- for every package in the # image, not just this one. The archive is linked into the binary, so there is # nothing to depend on; pkg-depends-no-inih below asserts the corrected line # from dpkg's own side, on the image, so a bbappend that stopped applying says # so once rather than presenting as a broken image build. bst_ok tio/suite-canary # first line: absorbs brintos/linux#96, which drops it TIO=/usr/bin/tio # The absolute path, not the PATH name: whether the name also resolves through # PATH is asserted below as its own case, and a `require tio` that missed # /usr/bin would SKIP this whole file rather than red. require "$TIO" tio/present W=${TMPDIR:-/tmp}/bst-tio.$$ if mkdir -p "$W" 2>/dev/null; then bst_ok tio/workdir else bst_notok tio/workdir "cannot create working directory $W" exit 0 fi export W TIO nl=' ' has() { case "$3" in *"$2"*) bst_ok "$1" ;; *) bst_notok "$1" "want:'$(_bst_1line "$2")' got:'$(_bst_1line "$3")'" ;; esac; } hasnt() { case "$3" in *"$2"*) bst_notok "$1" "present and must not be: $(_bst_1line "$2")" ;; *) bst_ok "$1" ;; esac; } # trun -- run a command that MAY NOT RETURN, keeping its # stdout, stderr and status as $W/.out/.err/.rc, and leaving $W/.killed # behind if the watchdog had to stop it. Always returns promptly and exits 0. # # Not bst_probe: bst_probe scores a non-zero child as a red, and every device # path here is expected to fail. Not timeout(1) either -- it can kill nothing # this watchdog cannot (brintos/linux#21), and before the brintos/linux#77 fix # its timer never fired at all. # # The `exec` matters. Without it the backgrounded subshell is what $! names and # a kill leaves the real process orphaned and still holding its module; with it # the subshell IS the program, so the watchdog kills the thing that is stuck. # `wait` then yields its true status (137 when the watchdog got there first). cat > "$W/trun" <<'EOS' #!/bin/sh tag=$1; bound=$2; shift 2 rm -f "$W/$tag.killed" "$W/$tag.rc" ( exec "$@" >"$W/$tag.out" 2>"$W/$tag.err" "$W/$tag.killed"; kill -9 "$p" 2>/dev/null ) & k=$! # The braces and the redirect are not decoration: sh announces a job it reaps # from a signal ("Killed") on ITS OWN stderr, non-interactively too, and that # line would land on the runner's console between two BST result lines. { wait "$p"; rc=$?; } 2>/dev/null kill -9 "$k" 2>/dev/null printf '%s\n' "$rc" > "$W/$tag.rc" exit 0 EOS chmod +x "$W/trun" # What a trun run produced. `killed` is true iff the watchdog stopped it, which # is the whole arm signal. out() { cat "$W/$1.out" 2>/dev/null; } err() { cat "$W/$1.err" 2>/dev/null; } both() { cat "$W/$1.out" "$W/$1.err" 2>/dev/null; } rc() { cat "$W/$1.rc" 2>/dev/null; } killed() { [ -e "$W/$1.killed" ]; } # t : run $TIO with -- which is the # whole of the argument list, tio itself is supplied here -- for the invocations # that DO return. # 6s is generous for a program that is about to exit in argv or config parsing; # a run that hits it has stopped somewhere this suite did not expect, and the # case says so rather than taking the whole file down with it. The tag is the # case id with '/' folded to '-', so every capture is named after its case. t() { _id=$1; _want=$2; _needle=$3; shift 3 _tag=$(printf '%s' "$_id" | tr '/' '-') "$W/trun" "$_tag" 6 "$TIO" "$@" if killed "$_tag"; then bst_notok "$_id" "did not return within 6s; output so far: $(_bst_1line "$(both "$_tag")")" return fi _o=$(both "$_tag"); _r=$(rc "$_tag") if bst_execfail "$_o"; then bst_unrun "$_id" "$_o"; return; fi if [ "$_r" != "$_want" ]; then bst_notok "$_id" "rc=$_r want=$_want out:'$(_bst_1line "$_o")'"; return fi case "$_needle" in '') bst_ok "$_id" ;; *) case "$_o" in *"$_needle"*) bst_ok "$_id" ;; *) bst_notok "$_id" "rc=$_r as wanted, but output lacks '$_needle': $(_bst_1line "$_o")" ;; esac ;; esac } # --- 1. the packaging --------------------------------------------------------- check tio/install-path test -x /usr/bin/tio check_eq tio/which "/usr/bin/tio" "$(command -v tio)" check tio/not-in-usr-sbin test ! -e /usr/sbin/tio pkglist=$(cat /var/lib/dpkg/info/tio.list 2>/dev/null) if [ -n "$pkglist" ]; then bst_ok tio/pkg-list hasline() { case "$nl$3$nl" in *"$nl$2$nl"*) bst_ok "$1" ;; *) bst_notok "$1" "not listed: $2" ;; esac; } hasline tio/pkg-binary /usr/bin/tio "$pkglist" # tio.1 goes to tio-doc by the default FILES split and this image installs # no -doc packages: its absence is the packaging working, not a lost file. hasnt tio/pkg-man-split "/usr/share/man/man1/tio.1" "$pkglist" # The recipe's FILES line names the completion script; meson never built it, # so the entry is inert. Asserted, because a FILES line naming a path the # image does not carry is the kind of thing a reader assumes is live. hasnt tio/pkg-no-completion "/usr/share/bash-completion/completions/tio" "$pkglist" check tio/pkg-completion-absent test ! -e /usr/share/bash-completion/completions/tio # One packaged FILE, whatever directories dpkg also recorded. nfiles=0 for p in $pkglist; do [ -f "$p" ] && nfiles=$((nfiles + 1)); done check_eq tio/pkg-one-file "1" "$nfiles" else bst_notok tio/pkg-list "no /var/lib/dpkg/info/tio.list -- the packaging cases here cannot run" for c in pkg-binary pkg-man-split pkg-no-completion pkg-completion-absent pkg-one-file; do bst_notok "tio/$c" "no dpkg file list for tio" done fi # The bbappend, asserted from the image's side. `libinih` here would mean the # RDEPENDS:remove stopped applying -- which do_rootfs would have caught first, # so this case is really a guard against it being "fixed" by making the empty # libinih package shippable instead. sed -n '/^Package: tio$/,/^$/p' /var/lib/dpkg/status > "$W/status" 2>/dev/null tiostatus=$(cat "$W/status") if [ -n "$tiostatus" ]; then bst_ok tio/pkg-status has tio/pkg-depends-libc "brintos-libc-so" "$tiostatus" hasnt tio/pkg-depends-no-inih "libinih" "$tiostatus" else bst_notok tio/pkg-status "no Package: tio stanza in /var/lib/dpkg/status" bst_notok tio/pkg-depends-libc "no dpkg status stanza" bst_notok tio/pkg-depends-no-inih "no dpkg status stanza" fi # --- 2. --version ------------------------------------------------------------- # `case 'v'` is printf("tio v%s\n", VERSION) then exit(0), inside the getopt # loop: the shortest path through this program and the only one that names the # version. VERSION comes from meson.project_version(), so pinning the string # pins the recipe's SRCREV against its own filename. t tio/version-rc 0 "tio v2.7" -v t tio/version-long-rc 0 "tio v2.7" --version check_eq tio/version-exact "tio v2.7" "$(out tio-version-rc)" # stdout, not stderr: a build that printed the banner on the wrong stream would # satisfy a content-only comparison. if [ -s "$W/tio-version-rc.err" ]; then bst_notok tio/version-stdout-only "version wrote to stderr: $(_bst_1line "$(err tio-version-rc)")" else bst_ok tio/version-stdout-only fi # --- 3. help, and the no-argument path ---------------------------------------- # argc == 1 is print_help() + exit(EXIT_SUCCESS) at the TOP of options_parse, so # a bare `tio` is the help and exits 0 -- not a usage error, which is the fact a # reader is most likely to assume wrongly here. t tio/help-rc 0 "Usage: tio [] " -h t tio/help-long-rc 0 "Usage: tio [] " --help t tio/noargs-rc 0 "Usage: tio [] " check_eq tio/noargs-is-help "$(out tio-help-rc)" "$(out tio-noargs-rc)" helptext=$(out tio-help-rc) has tio/help-lists-devices "-L, --list-devices" "$helptext" has tio/help-lists-baudrate "-b, --baudrate " "$helptext" has tio/help-lists-noauto "-n, --no-autoconnect" "$helptext" has tio/help-mentions-config "Options and sub-configurations may be set via configuration file." "$helptext" if [ -s "$W/tio-help-rc.err" ]; then bst_notok tio/help-stdout-only "help wrote to stderr: $(_bst_1line "$(err tio-help-rc)")" else bst_ok tio/help-stdout-only fi # --- 4. the device listing ---------------------------------------------------- # list_serial_devices() then exit(EXIT_SUCCESS), also inside the getopt loop -- # before the config file, before tty_configure, and a long way before the # thread. So this is the one device-facing question this image can answer. t tio/list-rc 0 "" -L t tio/list-long-rc 0 "" --list-devices # --list is not a long option. It is accepted only because "list-devices" is the # only long name with that prefix, so getopt_long resolves the abbreviation -- # which is why the ticket's spelling works and why it is asserted separately. t tio/list-abbrev-rc 0 "" --list check_eq tio/list-abbrev-same "$(out tio-list-long-rc)" "$(out tio-list-abbrev-rc)" check_eq tio/list-same "$(out tio-list-rc)" "$(out tio-list-long-rc)" # Nothing on stderr in either arm: an unreadable directory is silent by # construction (`if (d)`), it is not a diagnostic tio suppresses. if [ -s "$W/tio-list-rc.err" ]; then bst_notok tio/list-quiet "list wrote to stderr: $(_bst_1line "$(err tio-list-rc)")" else bst_ok tio/list-quiet fi # The arm, read at runtime rather than assumed. /dev/serial/by-id/ is a udev # artifact and this image has no udev and no USB host controller, so the # expected arm is "absent -> empty listing". If the directory ever exists, the # listing must agree with it entry for entry. if [ -d /dev/serial/by-id ]; then bst_ok tio/list-arm-present listwant=$(ls /dev/serial/by-id 2>/dev/null | sed 's|^|/dev/serial/by-id/|' | sort) listgot=$(out tio-list-rc | sort) check_eq tio/list-matches-dir "$listwant" "$listgot" else bst_ok tio/list-arm-absent if [ -s "$W/tio-list-rc.out" ]; then bst_notok tio/list-empty "no /dev/serial/by-id, so the listing must be empty, got: $(_bst_1line "$(out tio-list-rc)")" else bst_ok tio/list-empty fi fi # --- 5. argv validation, all of it in front of the thread --------------------- # getopt_long prints its own message and options.c exits EXIT_FAILURE on '?'. t tio/unknown-option-rc 1 "" --brintos-no-such-option if [ -s "$W/tio-unknown-option-rc.err" ]; then bst_ok tio/unknown-option-diag else bst_notok tio/unknown-option-diag "rejected with an empty stderr" fi # A device word is REQUIRED once any option is given: options_parse ends with # strlen(tty_device)==0 -> error. (`tio` alone is the help, asserted above.) t tio/missing-device 1 "Missing tty device or sub-configuration name" -e # Two positional words: the first becomes the device, the rest are reported. t tio/extra-argument 1 "Error: Unknown argument" /dev/brintos-no-such-tty extra # string_to_long()'s own diagnostic, and it goes to STDOUT (misc.c:60 uses # printf, not the error machinery) -- asserted as written, not as expected. t tio/bad-digit 1 "Error: Invalid digit" -b notanumber /dev/brintos-no-such-tty has tio/bad-digit-on-stdout "Error: Invalid digit" "$(out tio-bad-digit)" # tty_configure()'s four rejections. Each runs after the config file and before # log_open and the thread, so each returns. t tio/bad-databits 1 "Error: Invalid data bits" -d 9 /dev/brintos-no-such-tty t tio/bad-stopbits 1 "Error: Invalid stop bits" -s 3 /dev/brintos-no-such-tty t tio/bad-parity 1 "Error: Invalid parity" -p sideways /dev/brintos-no-such-tty t tio/bad-flow 1 "Error: Invalid flow control" -f sideways /dev/brintos-no-such-tty # --- 6. the input-thread arm, probed once ------------------------------------- # -n makes main call tty_connect() exactly once instead of entering the connect # loop, so this one invocation separates the THREE things that can happen at # tty_input_thread_create(). Everything after this point asserts $ARM. # # threads -- the thread runs. tio returns 1 having named the open failure. # nothread -- pthread_create is REFUSED. tio's own `!= 0` guard fires # (tty.c:386), it prints "pthread_create() error" and exit(1)s, so # the run returns promptly and never reaches tty_connect(). # trapped -- pthread_create succeeds for a thread that never runs, and # tty_input_thread_wait_ready() blocks on a mutex only that thread # unlocks (tty.c:300). The run has to be killed. # # MEASURED on this image: nothread. The kernel refuses the thread-mode clone in # its own voice -- "REFUSING thread-mode clone (CLONE_VM, flags=0x3d0f00) with # EAGAIN ... pthread_create() gets EAGAIN" -- which is # brintos/linux-in-the-browser#85 in the form it takes here. The trapped arm is # kept because it is what #85 does to a program that does not check the return, # and the threads arm because that is what a machine without #85 will report. "$W/trun" arm 8 "$TIO" -n /dev/brintos-no-such-tty armout=$(both arm) if killed arm; then ARM=trapped else case "$armout" in *"pthread_create() error"*) ARM=nothread ;; *) ARM=threads ;; esac fi case "$ARM" in threads) bst_ok tio/input-thread-arm-threads check_eq tio/connect-rc "1" "$(rc arm)" has tio/connect-diag "Could not open tty device" "$armout" ;; nothread) bst_ok tio/input-thread-arm-nothread # tio stops itself, with its own diagnostic, at a call it makes AFTER the # banner and BEFORE the open -- so the port is intact and #85 is what ends # the run. rc is tio's own exit(1), not a signal. check_eq tio/nothread-rc "1" "$(rc arm)" hasnt tio/nothread-before-open "Could not open tty device" "$armout" ;; *) bst_ok tio/input-thread-arm-trapped # It got as far as the banner and stopped at the wait, which is what makes # this #85 and not a fault of tio's: the banner is printed two calls before # tty_input_thread_create() and the open is one call after it. has tio/trapped-reached-banner "tio v2.7" "$armout" hasnt tio/trapped-before-open "Could not open tty device" "$armout" ;; esac # True in every arm, and the one case here a reader can compare across images: # the program is non-interactive when its stdin is not a tty, and it says so # before it does anything with the device. has tio/non-interactive-mode "Non-interactive mode enabled" "$armout" # The DEFAULT invocation -- no -n. Where the thread exists at all this never # returns, for one of two reasons: with a working thread it is # tty_wait_for_device() polling for the device once a second, which is the # documented autoconnect behaviour; with a thread that never runs it is the # mutex. In the nothread arm it DOES return, because tio stops itself one call # before the connect loop is entered -- so the assertion is per-arm, and the # warning tty_wait_for_device() prints is what only the working arm reaches. "$W/trun" autoconnect 6 "$TIO" /dev/brintos-no-such-tty acout=$(both autoconnect) if [ "$ARM" = nothread ]; then check_eq tio/autoconnect-stops-at-thread "1" "$(rc autoconnect)" elif killed autoconnect; then bst_ok tio/autoconnect-waits else bst_notok tio/autoconnect-waits "returned rc=$(rc autoconnect); the connect loop has no exit for an absent device: $(_bst_1line "$acout")" fi if [ "$ARM" = threads ]; then has tio/autoconnect-warns "Waiting for tty device" "$acout" else hasnt tio/autoconnect-warns "Waiting for tty device" "$acout" fi # A baud rate no B constant covers. meson probes asm-generic/ioctls.h for # TCGETS2 and this toolchain has it, so HAVE_TERMIOS2 is defined and # tty_configure takes the `standard_baudrate = false` branch instead of the # `#else` that rejects. Measured from the shipped binary carrying no "Invalid # baud rate" string at all -- so the assertion is that the rejection does NOT # happen, and the run gets as far as the arm above. "$W/trun" oddbaud 6 "$TIO" -b 12345 -n /dev/brintos-no-such-tty hasnt tio/nonstandard-baudrate-accepted "Invalid baud rate" "$(both oddbaud)" # --- 7. the configuration file ------------------------------------------------ # resolve_config_file() tries $XDG_CONFIG_HOME/tio/config first, so the whole # fixture lives in the scratch tree and no case depends on $HOME. # # `no-autoconnect = enable` sits in the UNNAMED section, which data_handler # applies on its first pass for every invocation: without it each case below # would enter the connect loop rather than reaching its own verdict. X="$W/xdg" mkdir -p "$X/tio" : > "$W/notatty" cat > "$X/tio/config" <" line is printed right after listen(2) and reads # back the configured path in every arm. "$W/trun" cfgsocket 6 "$TIO" socketed has tio/cfg-socket-created "Listening on socket $W/alpha.sock" "$(both cfgsocket)" # `mute = enable` suppresses tio_printf and ansi_error_printf alike, so the # banner this same invocation prints without it must be gone. Compared against # the [alpha] run, which is identical but for the one key. "$W/trun" cfgmuted 6 "$TIO" muted has tio/cfg-alpha-banner "tio v2.7" "$alphaout" if [ -s "$W/cfgmuted.out" ] || [ -s "$W/cfgmuted.err" ]; then bst_notok tio/cfg-mute-silences "mute set, yet output: $(_bst_1line "$(both cfgmuted)")" else bst_ok tio/cfg-mute-silences fi # A name no section matches leaves tty_device as the name itself, which is how # the two are told apart: [alpha] resolves to a path that EXISTS and is not a # tty, an unmatched name resolves to nothing. Only the working-thread arm can # see the difference, because both diagnostics live past the thread. "$W/trun" cfgnomatch 6 "$TIO" brintos-no-such-profile if [ "$ARM" = threads ]; then has tio/cfg-profile-device-applied "Not a tty device" "$(both cfgalpha)" has tio/cfg-unmatched-name "Could not open tty device" "$(both cfgnomatch)" else bst_notok tio/cfg-profile-device-applied "brintos/linux-in-the-browser#85: the connect attempt is behind tty_input_thread_create(), whose clone is refused EAGAIN here, so no run reaches tty_connect(); [alpha]'s device key is asserted instead by cfg-log-file-created and cfg-socket-created" bst_notok tio/cfg-unmatched-name "brintos/linux-in-the-browser#85: same refusal; an unmatched sub-config name cannot be told from a matched one without reaching tty_connect()" fi # --- 7c. a malformed file is NOT refused, and this says so -------------------- # UPSTREAM DEFECT IN tio 2.7, measured here rather than predicted. inih's # ini_parse() reports a SYNTAX error as the positive line number it first failed # at, and reserves negatives for "could not open" (-1) and "out of memory" (-2). # config_file_parse() tests `if (ret < 0)` at both of its call sites # (configfile.c:447, 469), so the one branch that prints "Unable to parse # configuration file ()" is reachable only for a file that could not be READ. # A malformed file is therefore accepted silently, and inih -- which does not # stop at the first error -- goes on handling the lines after the bad one. # # Not reported upstream (docs/conventions/no-public-upstream-reports.md) and not # patched here: correcting the test to `!= 0` would also make an unknown key # fatal, since data_handler returns 0 for one, and cfg-unknown-key-warns above # asserts that an unknown key is a WARNING that parsing survives. So the defect # is asserted as it is, and the assertion is what will notice if it is fixed. # # Its own file, in its own XDG root. `[alpha` is an unterminated section header, # which inih refuses whatever its INI_ALLOW_* options are set to -- a bare word # would depend on INI_ALLOW_NO_VALUE, which this recipe does not pin. The line # AFTER it carries a rejectable value, so the run proves two things at once: the # syntax error was not fatal, and parsing continued past it. Y="$W/xdg-bad" mkdir -p "$Y/tio" printf '%s\n' 'no-autoconnect = enable' '[alpha' 'baudrate = notanumber' > "$Y/tio/config" XDG_CONFIG_HOME="$Y" "$W/trun" cfgbad 6 "$TIO" alpha badout=$(both cfgbad) hasnt tio/cfg-malformed-not-refused "Unable to parse configuration file" "$badout" # Line 3 is past the unterminated header, and its value is rejected by name -- # so the file went on being read, and the settings after a syntax error apply. has tio/cfg-malformed-parsing-continues "Invalid value 'notanumber' for option 'baudrate' in configuration file" "$badout" # read_integer()'s exit(1), reached inside config_file_parse -- which is third # in main(), before tty_configure and long before the thread. So this status is # the config file's answer in every arm, not the input thread's. check_eq tio/cfg-malformed-rc "1" "$(rc cfgbad)"