summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig5
-rw-r--r--GNUmakefile4
-rw-r--r--lib9p/CMakeLists.txt15
-rwxr-xr-xlib9p/tests/runtest81
-rw-r--r--lib9p/tests/test_server/main.c33
-rwxr-xr-xlib9p/tests/testclient-p9p59
-rw-r--r--lib9p/tests/testclient-p9p.explog106
7 files changed, 245 insertions, 58 deletions
diff --git a/.editorconfig b/.editorconfig
index 23d17cc..7aadb15 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -64,10 +64,13 @@ _mode = man-cat
[lib9p/idl/*.9p{,.wip}]
_mode = 9p-idl
+[lib9p/tests/*.explog]
+_mode = 9p-log
+
[{lib9p/tests/test_server/static.h.gen,lib9p/tests/test_compile.c.gen}]
_mode = sh
-[{lib9p/linux-errno.txt.gen,lib9p/tests/runtest}]
+[{lib9p/linux-errno.txt.gen,lib9p/tests/runtest,lib9p/tests/testclient-p9p}]
_mode = bash
[{lib9p/proto.gen,lib9p/include/lib9p/linux-errno.h.gen}]
diff --git a/GNUmakefile b/GNUmakefile
index 4183e37..904977d 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -132,7 +132,7 @@ lint/python3: lint/%: build-aux/venv
./build-aux/venv/bin/pylint $(sources_$*)
! grep -nh 'SPECIAL$$' -- lib9p/proto.gen lib9p/protogen/*.py
./build-aux/venv/bin/pytest $(foreach f,$(sources_python3),$(if $(filter test_%.py,$(notdir $f)),$f))
-lint/make lint/cmake lint/gitignore lint/ini lint/9p-idl lint/markdown lint/pip lint/man-cat: lint/%:
+lint/make lint/cmake lint/gitignore lint/ini lint/9p-idl lint/9p-log lint/markdown lint/pip lint/man-cat: lint/%:
@: TODO: Write/adopt linters for these file types
# `format` #############################
@@ -148,5 +148,5 @@ format/sh format/bash: format/%:
format/python3: format/%: ./build-aux/venv
./build-aux/venv/bin/black $(sources_$*)
./build-aux/venv/bin/isort $(sources_$*)
-format/make format/cmake format/gitignore format/ini format/9p-idl format/markdown format/pip format/man-cat: format/%:
+format/make format/cmake format/gitignore format/ini format/9p-idl format/9p-log format/markdown format/pip format/man-cat: format/%:
@: TODO: Write/adopt formatters for these file types
diff --git a/lib9p/CMakeLists.txt b/lib9p/CMakeLists.txt
index d433a12..8f22068 100644
--- a/lib9p/CMakeLists.txt
+++ b/lib9p/CMakeLists.txt
@@ -20,10 +20,17 @@ target_link_libraries(lib9p INTERFACE
if (ENABLE_TESTS)
add_subdirectory(tests/test_server)
- add_test(
- NAME "lib9p/runtest"
- COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/runtest"
- )
+
+ function(add_lib9p_test arg_testscript)
+ get_filename_component(tmp_basename "${arg_testscript}" "NAME")
+ add_test(
+ NAME "lib9p/${tmp_basename}"
+ COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/runtest" "${arg_testscript}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/${tmp_basename}.explog"
+ )
+ endfunction()
+
+ add_lib9p_test("${CMAKE_CURRENT_SOURCE_DIR}/tests/testclient-p9p")
+
add_lib_test(lib9p test_compile)
target_include_directories(test_compile PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_compile_config)
endif()
diff --git a/lib9p/tests/runtest b/lib9p/tests/runtest
index fb66a43..6883391 100755
--- a/lib9p/tests/runtest
+++ b/lib9p/tests/runtest
@@ -1,67 +1,50 @@
#!/usr/bin/env bash
-# lib9p/tests/runtest - Simple tests for the 9P `test_server`
+# lib9p/tests/runtest - Test harness for the 9P `test_server`
#
# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
set -euE -o pipefail
-set -x
build_aux=$(realpath --canonicalize-missing -- "${BASH_SOURCE[0]}/../../../build-aux")
-port=$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
-"${build_aux}/valgrind" ./tests/test_server/test_server "$port" &
-server_pid=$!
-# shellcheck disable=SC2064
-trap "kill $server_pid || true; wait $server_pid || true" EXIT
-server_addr="localhost:${port}"
-
-client=(9p -a "$server_addr")
+if [[ $# != 2 ]]; then
+ echo >&2 "Usage: $0 CLIENTSCRIPT EXPLOG"
+ exit 2
+fi
+clientscript="$1"
+explog="$2"
-expect_lines() (
+cleanup=()
+cleanup() {
{ set +x; } &>/dev/null
- printf >&2 '+ diff -u expected.txt actual.txt\n'
- diff -u <(printf '%s\n' "$@") <(printf '%s\n' "$out")
-)
-
-while [[ -d /proc/$server_pid ]] && ! (readlink /proc/$server_pid/fd/* 2>/dev/null | grep -q ^socket:); do sleep 0.1; done
+ local i
+ for ((i = ${#cleanup[@]} - 1; i >= 0; i--)); do
+ eval "set -x; ${cleanup[$i]}"
+ { set +x; } &>/dev/null
+ done
+}
+trap cleanup EXIT
-out=$("${client[@]}" ls -l '')
-expect_lines \
- 'd-r-xr-xr-x M 0 root root 0 Oct 7 2024 Documentation' \
- '--r--r--r-- M 0 root root 166 Oct 7 2024 README.md' \
- '---w--w--w- M 0 root root 0 Oct 7 2024 shutdown'
+logfile=$(mktemp -t lib9p-log.XXXXXXXXXX)
+cleanup+=("rm -f -- ${logfile@Q}")
-out=$("${client[@]}" ls -l 'Documentation/')
-expect_lines \
- '--r--r--r-- M 0 root root 166 Oct 7 2024 x'
-
-out=$("${client[@]}" read 'README.md')
-expect_lines \
- '<!--' \
- ' README.md - test static file' \
- '' \
- ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
- ' SPDX-License-Identifier: AGPL-3.0-or-later' \
- '-->' \
- 'Hello, world!'
+port=$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
-out=$("${client[@]}" read 'Documentation/x')
-expect_lines \
- '<!--' \
- ' Documentation/x.txt - test static file' \
- '' \
- ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
- ' SPDX-License-Identifier: AGPL-3.0-or-later' \
- '-->' \
- 'foo'
+set -x
-out=$("${client[@]}" stat 'Documentation/x')
-expect_lines \
- "'x' 'root' 'root' 'root' q (0000000000000001 1 ) m 0444 at 1728337905 mt 1728337904 l 166 t 0 d 0"
+"${build_aux}/valgrind" ./tests/test_server/test_server "$port" "$logfile" &
+server_pid=$!
+cleanup+=("kill $server_pid || true; wait $server_pid || true")
+while [[ -d /proc/$server_pid ]] && ! (readlink /proc/$server_pid/fd/* 2>/dev/null | grep -q ^socket:); do sleep 0.1; done
-out=$("${client[@]}" write 'shutdown' <<<1)
-expect_lines ''
+if [[ "$(head -c2 -- "$clientscript")" == '#!' ]]; then
+ "$clientscript" "$port"
+else
+ "${build_aux}/valgrind" "$clientscript" "$port"
+fi
wait "$server_pid"
-trap - EXIT
+cleanup=("${cleanup[@]::1}")
+
+diff -u -- <(grep -e '^[<>]' -- "$explog") "$logfile"
diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c
index 8d22a04..01a1738 100644
--- a/lib9p/tests/test_server/main.c
+++ b/lib9p/tests/test_server/main.c
@@ -5,6 +5,8 @@
*/
#include <error.h>
+#include <errno.h>
+#include <stdio.h>
#include <stdlib.h> /* for atoi() */
#include <lib9p/srv.h>
@@ -37,6 +39,7 @@ struct {
uint16_t port;
struct hostnet_tcp_listener listeners[CONFIG_SRV9P_NUM_CONNS];
struct lib9p_srv srv;
+ FILE *logstream;
} globals = {
.srv = (struct lib9p_srv){
.rootdir = get_root,
@@ -109,15 +112,41 @@ static COROUTINE init_cr(void *) {
cr_exit();
}
+static void log_fct(char character, void *_stream) {
+ FILE *stream = _stream;
+ putc(character, stream);
+ putchar(character);
+}
+
+static void log_msg(struct lib9p_srv_ctx *ctx, enum lib9p_msg_type typ, void *hostmsg) {
+ /* It sucks that %v trips -Wformat and -Wformat-extra-args
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ fmt_fctprintf(log_fct, globals.logstream,
+ "%c %v\n", typ % 2 ? '<' : '>',
+ lo_box_lib9p_msg_as_fmt_formatter(&ctx->basectx, typ, hostmsg));
+#pragma GCC diagnostic pop
+ fflush(globals.logstream);
+}
+
int main(int argc, char *argv[]) {
- if (argc != 2)
- error(2, 0, "usage: %s PORT_NUMBER", argv[0]);
+ if (argc != 3)
+ error(2, 0, "usage: %s PORT_NUMBER LOGFILE", argv[0]);
+
globals.port = atoi(argv[1]);
+ globals.logstream = fopen(argv[2], "w");
+ if (!globals.logstream)
+ error(2, errno, "fopen");
+ globals.srv.msglog = log_msg;
+
struct hostclock clock_monotonic = {
.clock_id = CLOCK_MONOTONIC,
};
bootclock = lo_box_hostclock_as_alarmclock(&clock_monotonic);
coroutine_add("init", init_cr, NULL);
coroutine_main();
+ fclose(globals.logstream);
return 0;
}
diff --git a/lib9p/tests/testclient-p9p b/lib9p/tests/testclient-p9p
new file mode 100755
index 0000000..5e4c2c8
--- /dev/null
+++ b/lib9p/tests/testclient-p9p
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+# lib9p/tests/testclient-p9p - Test the 9P `test_server` against Plan 9 Port's `9p` utility
+#
+# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+set -euE -o pipefail
+
+if [[ $# != 1 ]]; then
+ echo >&2 "Usage: $0 SERVER_PORT"
+ echo >&2 "Usage: ./runtest $0 EXPLOG"
+ exit 2
+fi
+
+expect_lines() (
+ { set +x; } &>/dev/null
+ printf >&2 '+ diff -u expected.txt actual.txt\n'
+ diff -u <(printf '%s\n' "$@") <(printf '%s\n' "$out")
+)
+
+set -x
+client=(9p -a "localhost:${1}")
+
+out=$("${client[@]}" ls -l '')
+expect_lines \
+ 'd-r-xr-xr-x M 0 root root 0 Oct 7 2024 Documentation' \
+ '--r--r--r-- M 0 root root 166 Oct 7 2024 README.md' \
+ '---w--w--w- M 0 root root 0 Oct 7 2024 shutdown'
+
+out=$("${client[@]}" ls -l 'Documentation/')
+expect_lines \
+ '--r--r--r-- M 0 root root 166 Oct 7 2024 x'
+
+out=$("${client[@]}" read 'README.md')
+expect_lines \
+ '<!--' \
+ ' README.md - test static file' \
+ '' \
+ ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
+ ' SPDX-License-Identifier: AGPL-3.0-or-later' \
+ '-->' \
+ 'Hello, world!'
+
+out=$("${client[@]}" read 'Documentation/x')
+expect_lines \
+ '<!--' \
+ ' Documentation/x.txt - test static file' \
+ '' \
+ ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
+ ' SPDX-License-Identifier: AGPL-3.0-or-later' \
+ '-->' \
+ 'foo'
+
+out=$("${client[@]}" stat 'Documentation/x')
+expect_lines \
+ "'x' 'root' 'root' 'root' q (0000000000000001 1 ) m 0444 at 1728337905 mt 1728337904 l 166 t 0 d 0"
+
+out=$("${client[@]}" write 'shutdown' <<<1)
+expect_lines ''
diff --git a/lib9p/tests/testclient-p9p.explog b/lib9p/tests/testclient-p9p.explog
new file mode 100644
index 0000000..f3ff1ce
--- /dev/null
+++ b/lib9p/tests/testclient-p9p.explog
@@ -0,0 +1,106 @@
+# lib9p/tests/testclient-p9p.explog - Expected 9P logfile of testclient-p9p
+#
+# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="lukeshu" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=5 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=0 wname=[ ] }
+< Rwalk { tag=0 nwqid=0 wqid=[ ] }
+> Tstat { tag=0 fid=1 }
+< Rstat { tag=0 stat={ kern_type=0 kern_dev=0 file_qid={ type=(DIR) vers=1 path=5 } file_mode=(DIR|0555) file_atime=1728337905 file_mtime=1728337904 file_size=0 file_name="" file_owner_uid="root" file_owner_gid="root" file_last_modified_uid="root" file_extension="" file_owner_n_uid=0 file_owner_n_gid=0 file_last_modified_n_uid=0 } }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Twalk { tag=0 fid=0 newfid=1 nwname=0 wname=[ ] }
+< Rwalk { tag=0 nwqid=0 wqid=[ ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(DIR) vers=1 path=5 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=213 data=<bytedata> }
+> Tread { tag=0 fid=1 offset=213 count=4096 }
+< Rread { tag=0 count=0 data=<bytedata> }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="lukeshu" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=5 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["Documentation" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(DIR) vers=1 path=2 } ] }
+> Tstat { tag=0 fid=1 }
+< Rstat { tag=0 stat={ kern_type=0 kern_dev=0 file_qid={ type=(DIR) vers=1 path=2 } file_mode=(DIR|0555) file_atime=1728337905 file_mtime=1728337904 file_size=0 file_name="Documentation" file_owner_uid="root" file_owner_gid="root" file_last_modified_uid="root" file_extension="" file_owner_n_uid=0 file_owner_n_gid=0 file_last_modified_n_uid=0 } }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["Documentation" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(DIR) vers=1 path=2 } ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(DIR) vers=1 path=2 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=62 data=<bytedata> }
+> Tread { tag=0 fid=1 offset=62 count=4096 }
+< Rread { tag=0 count=0 data=<bytedata> }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="lukeshu" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=5 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["README.md" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=3 } ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=3 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=166 data=<bytedata> }
+> Tread { tag=0 fid=1 offset=166 count=4096 }
+< Rread { tag=0 count=0 data=<bytedata> }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="lukeshu" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=5 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=2 wname=["Documentation", "x" ] }
+< Rwalk { tag=0 nwqid=2 wqid=[{ type=(DIR) vers=1 path=2 }, { type=(0) vers=1 path=1 } ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=1 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=166 data=<bytedata> }
+> Tread { tag=0 fid=1 offset=166 count=4096 }
+< Rread { tag=0 count=0 data=<bytedata> }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="lukeshu" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=5 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=2 wname=["Documentation", "x" ] }
+< Rwalk { tag=0 nwqid=2 wqid=[{ type=(DIR) vers=1 path=2 }, { type=(0) vers=1 path=1 } ] }
+> Tstat { tag=0 fid=1 }
+< Rstat { tag=0 stat={ kern_type=0 kern_dev=0 file_qid={ type=(0) vers=1 path=1 } file_mode=(0444) file_atime=1728337905 file_mtime=1728337904 file_size=166 file_name="x" file_owner_uid="root" file_owner_gid="root" file_last_modified_uid="root" file_extension="" file_owner_n_uid=0 file_owner_n_gid=0 file_last_modified_n_uid=0 } }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="lukeshu" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=5 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["shutdown" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=4 } ] }
+> Topen { tag=0 fid=1 mode=(TRUNC|MODE_WRITE) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=4 } iounit=0 }
+> Twrite { tag=0 fid=1 offset=0 count=2 data=<bytedata> }
+< Rwrite { tag=0 count=2 }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }