From ab227a5aea355d47011198cf0c7562cb0994913b Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 10 Nov 2026 03:34:22 +0900 Subject: [PATCH 1/7] 0.21.0: a second name for a file, and the requests to end counted (openkal 0.25) - kal_fs_hard_link: NtSetInformationFile with the link record; KAL_FS_PROP_HARD_LINKS where the volume reports FILE_SUPPORTS_HARD_LINKS; a directory is kal_err_is_directory and STATUS_NOT_SAME_DEVICE kal_err_not_supported - the stop word counts each console event atomically and skips zero on wrap - openkal follows the specification's branch feat/0.25.0 until 0.25.0 is released --- README.md | 18 +++++++++++++-- mcpp.toml | 4 ++-- src/fs.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ src/process.cpp | 7 +++++- src/win.h | 7 +++++- 5 files changed, 90 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index de96f34..75e0ca7 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ An implementation of [openkal](https://github.com/mcpplibs/openkal) for Windows. ```toml [dependencies] -openkal = "0.24.0" +openkal = "0.25.0" [target.'cfg(windows)'.dependencies] -openkal-windows = "0.20.0" +openkal-windows = "0.21.0" ``` Its purpose is as much to test the specification as to be used. openkal was @@ -15,6 +15,20 @@ drawn against systems that share an ancestry; a third implementation on a system that shares none of it is what turns "this interface is not Unix's shape" from a claim into an observation. +## What 0.21 adds (openkal 0.25) + +**A second name for a file** (`kal_fs_hard_link`) is the link record of +`NtSetInformationFile`, which has the rename record's layout; the file is opened by +its name without following a final link, as `CreateHardLinkW` opens it. +`KAL_FS_PROP_HARD_LINKS` is claimed where the volume reports +`FILE_SUPPORTS_HARD_LINKS`. A directory is refused with `kal_err_is_directory` +before the call, and a name on another volume (`STATUS_NOT_SAME_DEVICE`) with +`kal_err_not_supported`. + +**The requests to end are counted.** The console control routine adds one to the +stop word for each event, atomically because events may overlap, and skips zero +when the count wraps. + ## Observable process units The process interface provides the OpenKal 0.24 declared-unit operations. diff --git a/mcpp.toml b/mcpp.toml index 300dc63..f67cbbd 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-windows" -version = "0.20.0" +version = "0.21.0" description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol." license = "Apache-2.0" @@ -66,7 +66,7 @@ provides-interfaces = [ ] [dependencies] -openkal = "0.24.0" +openkal = { git = "https://github.com/mcpplibs/openkal.git", branch = "feat/0.25.0" } # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/src/fs.cpp b/src/fs.cpp index 6e6b722..06cdeb7 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -1267,6 +1267,62 @@ int kal_fs_rename(kal_dir from, const char* a, kal_uintptr alen, return rename_with(from, a, alen, to, b, blen, true); } +// A SECOND NAME FOR A FILE (0.25) is the link record, which has the rename +// record's layout: the file is opened by its name without following a final +// link, as `CreateHardLinkW' opens it, and given `b' beside `to'. A directory is +// answered before the call, because the object manager's refusal names a +// different condition; a name on another volume is STATUS_NOT_SAME_DEVICE, +// which this interface states as an operation the pair cannot perform. +int kal_fs_hard_link(kal_dir from, const char* a, kal_uintptr alen, + kal_dir to, const char* b, kal_uintptr blen) { + void* source_root = dir_handle(from); + void* target_root = dir_handle(to); + if (!source_root || !target_root) return kal_err_invalid; + if (!okw::acceptable(a, alen) || !okw::acceptable(b, blen)) return kal_err_invalid; + if ((kal_fs_props(to) & KAL_FS_PROP_HARD_LINKS) == 0) return kal_err_not_supported; + if (refused_write(target_root)) return kal_err_permission; + + void* h = nullptr; + const long r = open_relative(source_root, a, alen, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE, + okw::file_open, okw::file_open_reparse_point | okw::file_open_for_backup_intent, &h); + if (!okw::ok(r)) return okw::translate_nt(r); + { + okw::file_basic_information basic{}; + okw::io_status_block s{}; + if (okw::ok(okw::NtQueryInformationFile(h, &s, &basic, sizeof basic, okw::file_basic_information_class)) + && (basic.attributes & FILE_ATTRIBUTE_DIRECTORY) != 0 + && (basic.attributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { + okw::NtClose(h); + return kal_err_is_directory; + } + } + + okw::wide_name w(b, blen); + if (!w.ok) { okw::NtClose(h); return kal_err_invalid; } + const kal_uintptr storage_bytes = sizeof(okw::file_rename_information) + + okw::kMaxName * sizeof(wchar_t); + auto* info = static_cast( + kal_alloc(storage_bytes, alignof(okw::file_rename_information))); + if (!info) { okw::NtClose(h); return kal_err_no_memory; } + info->replace_if_exists = 0; + info->root_directory = target_root; + info->file_name_length = w.string.length; + for (unsigned i = 0; i < w.string.length / 2; ++i) info->file_name[i] = w.buffer[i]; + + okw::io_status_block s{}; + const long n = okw::NtSetInformationFile( + h, &s, info, static_cast(sizeof(okw::file_rename_information) + + w.string.length), + okw::file_link_information_class); + okw::NtClose(h); + kal_free(info, storage_bytes, alignof(okw::file_rename_information)); + if (n == static_cast(0xC0000035) /* STATUS_OBJECT_NAME_COLLISION */) return kal_err_exists; + if (n == static_cast(0xC00000D4) /* STATUS_NOT_SAME_DEVICE */) return kal_err_not_supported; + if (!okw::ok(n)) return okw::translate_nt(n); + wrote(target_root, b, blen); + return kal_ok; +} + int kal_fs_rename_flags(kal_dir from, const char* a, kal_uintptr alen, kal_dir to, const char* b, kal_uintptr blen, kal_uintptr flags) { if ((flags & ~static_cast(KAL_RENAME_NO_REPLACE)) != 0) return kal_err_invalid; @@ -1545,6 +1601,10 @@ kal_uintptr kal_fs_props(kal_dir d) { // own control code. if (a.info.attributes & okw::fs_supports_reparse_points) p |= KAL_FS_PROP_LINKS | KAL_FS_PROP_MAKE_LINKS; + // A second name for a file (0.25) where the volume reports that it stores + // several names for one file. + if (a.info.attributes & okw::fs_supports_hard_links) + p |= KAL_FS_PROP_HARD_LINKS; // A rename that does not replace is a field of the rename record (0.17). p |= KAL_FS_PROP_RENAME_NO_REPLACE; // A name held open is removed at once (0.17, clause 7.14) where the volume diff --git a/src/process.cpp b/src/process.cpp index 46e9035..c8535d5 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -999,7 +999,12 @@ int g_stop_armed = 0; // installing a routine does not clear, so a request its starter declined // stays declined. BOOL OKW_API stop_routine(DWORD) { - g_stop_word = 1; + // EACH REQUEST IS COUNTED (openkal 0.25), and the count skips zero when it + // wraps, so the word is never zero again once a request has arrived. The + // routine runs on a context of the system's for each event, and events may + // overlap, so the addition is atomic. + volatile long* word = reinterpret_cast(&g_stop_word); + if (okw::atomic::increment(word) == 0) (void)okw::atomic::increment(word); WakeByAddressAll(&g_stop_word); return TRUE; } diff --git a/src/win.h b/src/win.h index f6ec23e..f78385a 100644 --- a/src/win.h +++ b/src/win.h @@ -78,8 +78,10 @@ using okw_u32 = unsigned int; extern "C" long long _InterlockedExchange64(volatile long long*, long long); extern "C" long long _InterlockedExchangeAdd64(volatile long long*, long long); extern "C" long long _InterlockedOr64(volatile long long*, long long); -#pragma intrinsic(_InterlockedExchange64, _InterlockedExchangeAdd64, _InterlockedOr64) +extern "C" long _InterlockedIncrement(volatile long*); +#pragma intrinsic(_InterlockedExchange64, _InterlockedExchangeAdd64, _InterlockedOr64, _InterlockedIncrement) namespace okw::atomic { +inline long increment(volatile long* p) { return _InterlockedIncrement(p); } inline long long exchange(volatile long long* p, long long v) { return _InterlockedExchange64(p, v); } inline long long fetch_add(volatile long long* p, long long v) { return _InterlockedExchangeAdd64(p, v); } inline long long load(volatile long long* p) { return _InterlockedOr64(p, 0); } @@ -88,6 +90,7 @@ inline long long fetch_or(volatile long long* p, long long v) { return _Interloc } // namespace okw::atomic #else namespace okw::atomic { +inline long increment(volatile long* p) { return __atomic_add_fetch(p, 1, __ATOMIC_ACQ_REL); } inline long long exchange(volatile long long* p, long long v) { return __atomic_exchange_n(p, v, __ATOMIC_ACQ_REL); } inline long long fetch_add(volatile long long* p, long long v) { return __atomic_fetch_add(p, v, __ATOMIC_ACQ_REL); } inline long long load(volatile long long* p) { return __atomic_load_n(p, __ATOMIC_ACQUIRE); } @@ -216,6 +219,7 @@ enum : unsigned long { fs_case_sensitive_search = 0x00000001u, fs_supports_reparse_points = 0x00000080u, fs_supports_posix_unlink_rename = 0x00000400u, + fs_supports_hard_links = 0x00400000u, // FILE_SUPPORTS_HARD_LINKS (0.25) fs_supports_open_by_file_id = 0x01000000u, }; @@ -230,6 +234,7 @@ enum : int { file_disposition_information_ex_class = 64, file_end_of_file_information_class = 20, file_rename_information_class = 10, + file_link_information_class = 11, // the rename record's layout (0.25) file_id_full_directory_information_class = 38, }; From 9f441f4dd15322630ec54393e9534293ef8817c8 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 10 Nov 2026 03:59:49 +0900 Subject: [PATCH 2/7] ci: the runs are kept and reported through mtest - the conformance suite runs through the pinned mtest engine (MTEST, set by .github/actions/mtest): one case per interface, its observations within it - mcpp 2026.11.9.1: `mcpp test` keeps this package's runs in the mtest store (target/mtest), one case per test program - each job's runs in its summary and as an artifact; one report over every job The test programs stay programs of their own (protocol level 0): the mtest library reaches mcpplibs.os, which depends on openkal and on this implementation, so a dev-dependency on it would close a cycle through the package under test. --- .github/actions/mtest/action.yml | 69 +++++++++++++++++++++++++++++ .github/workflows/ci.yml | 74 +++++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 .github/actions/mtest/action.yml diff --git a/.github/actions/mtest/action.yml b/.github/actions/mtest/action.yml new file mode 100644 index 0000000..549468c --- /dev/null +++ b/.github/actions/mtest/action.yml @@ -0,0 +1,69 @@ +name: mtest +description: > + The mtest engine the conformance suite reports to: the release the workflow pins (MTEST_VERSION), an + xlings package (xim:mtest) that the index verifies, else the release bundle checked against MTEST_SHA256; + MTEST names its launcher. A job that has no xlings yet gets the pinned one first (XLINGS_VERSION). +runs: + using: composite + steps: + - name: xlings, where the job has none + if: runner.os != 'Windows' + shell: bash + run: | + set -euo pipefail + command -v xlings > /dev/null 2>&1 && exit 0 + curl -fsSL --retry 3 --retry-all-errors --retry-delay 2 \ + https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ + | bash -s "$XLINGS_VERSION" + echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" + - name: xlings, where the job has none (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + if (Get-Command xlings -ErrorAction SilentlyContinue) { exit 0 } + irm https://d2learn.org/xlings-install.ps1.txt | iex + "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + # xlings by the configured mirror (GLOBAL), then by CN (GitCode first), each with a refreshed index + # (a cached index can predate the release, and xlings keeps it when an anonymous refresh fails); else + # the release bundle itself, checked against MTEST_SHA256: the web address, then the API's, which + # answered 200 while the web one answered 504 (GitHub's 2026-11-09 incident). + launcher="" + for mirror in GLOBAL CN; do + xlings config --mirror "$mirror" > /dev/null 2>&1 || true + xlings update > /dev/null 2>&1 || true + if xlings install "mtest@$MTEST_VERSION" -y -g; then launcher=$(command -v mtest); break; fi + done + # The package's own launcher, not the xvm shim: on Windows the shim runs the package's alias through + # cmd, which reads `&` in an argument (`--tag "on:win32-x64&release"`) as a second command. + exe=''; [ "$RUNNER_OS" != Windows ] || exe='.exe' + own="${XLINGS_HOME:-$HOME/.xlings}/data/xpkgs/xim-x-mtest/$MTEST_VERSION/bin/mtest$exe" + if [ -n "$launcher" ] && [ -f "$own" ]; then launcher="$own"; fi + xlings config --mirror GLOBAL > /dev/null 2>&1 || true + if [ -z "$launcher" ]; then + echo "::warning::xlings could not install mtest $MTEST_VERSION; taking the release itself" + archive="mtest-$MTEST_VERSION-openkal-bundle-all.tar.gz" + temp="$RUNNER_TEMP"; python=python3 + if [ "$RUNNER_OS" = Windows ]; then temp=$(cygpath -u "$RUNNER_TEMP"); python=python; fi + if ! curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -o "$temp/$archive" \ + "https://github.com/mcpp-community/mtest/releases/download/v$MTEST_VERSION/$archive"; then + id=$(curl -fsSL --retry 3 --retry-all-errors -H "Authorization: Bearer $GH_TOKEN" \ + "https://api.github.com/repos/mcpp-community/mtest/releases/tags/v$MTEST_VERSION" \ + | "$python" -c 'import json,sys; print(next(str(a["id"]) for a in json.load(sys.stdin)["assets"] if a["name"] == sys.argv[1]))' "$archive") + curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -H "Authorization: Bearer $GH_TOKEN" \ + -H "Accept: application/octet-stream" -o "$temp/$archive" \ + "https://api.github.com/repos/mcpp-community/mtest/releases/assets/$id" + fi + actual=$("$python" -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$temp/$archive") + [ "$actual" = "$MTEST_SHA256" ] || { echo "::error::$archive: sha256 $actual, the workflow pins $MTEST_SHA256"; exit 1; } + mkdir -p "$temp/mtest" && tar xzf "$temp/$archive" -C "$temp/mtest" + launcher="$temp/mtest/mtest-$MTEST_VERSION-openkal-bundle-all/bin/mtest" + [ "$RUNNER_OS" = Windows ] && launcher="$launcher.exe" + fi + "$launcher" --version + "$launcher" --version | grep -qF "$MTEST_VERSION" || { echo "::error::the mtest at $launcher is not $MTEST_VERSION"; exit 1; } + echo "MTEST=$launcher" >> "$GITHUB_ENV" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e513301..2214810 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,8 +26,12 @@ on: default: "" env: MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} - MCPP_VERSION: 2026.11.5.1 + MCPP_VERSION: 2026.11.9.1 XLINGS_VERSION: v2026.10.26.1 + # The mtest engine the runs are reported with: the conformance suite's cases + # (tools/run-conformance.sh with MTEST set) and `mcpp test`'s store (target/mtest). + MTEST_VERSION: 0.5.2 + MTEST_SHA256: be443f8c546b3f4628dcd3f1eb6f45b4d698a7f805232f966349faf8250863c6 XLINGS_NON_INTERACTIVE: '1' jobs: @@ -245,6 +249,8 @@ jobs: esac mcpp toolchain list + - uses: ./.github/actions/mtest + - name: Every interface, every kind of examination run: | extra='' @@ -298,6 +304,11 @@ jobs: test "$missing" -eq 0 git checkout -- mcpp.toml + - name: This package's test runs, kept for the report + if: always() + run: | + if [ -d target/mtest ]; then cp -R target/mtest "$RUNNER_TEMP/mcpp-test-runs"; fi + # --------------------------------------------------------------------------- # From a system that is not this one. # @@ -307,6 +318,39 @@ jobs: # that read this environment's object format and are not on it, and a package # that only builds on the system it targets is a package a contributor on any # other system cannot work on. + + # A FAILED RUN, EXPLAINED: through the engine the suite's own text is not in + # the log, so the failing cases are named here with what each observed. + - name: The failing cases, explained + if: failure() + run: | + [ -n "${MTEST:-}" ] && [ -d .spec/conformance/.mtest ] || exit 0 + ( cd .spec/conformance && "$MTEST" explain ) || true + + # THE RUNS OF THIS JOB, REPORTED: the conformance suite's (one case per + # interface, its observations within it) and `mcpp test`'s (one case per + # test program), taken into one store, written as the job's summary and kept + # for the report over every job. + - name: The runs, reported + if: always() + run: | + [ -n "${MTEST:-}" ] || { echo "no mtest engine in this job"; exit 0; } + store="$(cygpath -u "$RUNNER_TEMP" 2> /dev/null || printf '%s' "$RUNNER_TEMP")/mtest-store" + mkdir -p "$store"; here="$PWD" + for from in "$here/.spec/conformance/.mtest" "$RUNNER_TEMP/mcpp-test-runs"; do + if [ -f "$from/index.ndjson" ]; then ( cd "$store" && "$MTEST" store import --from "$from" ); fi + done + [ -f "$store/.mtest/index.ndjson" ] || { echo "no run was kept"; exit 0; } + ( cd "$store" && "$MTEST" report --format md --group "$GITHUB_RUN_ID" ) >> "$GITHUB_STEP_SUMMARY" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: mtest-${{ github.job }}-${{ strategy.job-index }} + path: ${{ runner.temp }}/mtest-store/.mtest + include-hidden-files: true + if-no-files-found: ignore + retention-days: 7 cross: name: cross build, run under wine, and examine the objects runs-on: ubuntu-24.04 @@ -534,3 +578,31 @@ jobs: fi rm -f src/probe.cpp echo "a dependence upon a C runtime is visible to the check" + + # --------------------------------------------------------------------------- + # EVERY JOB'S RUNS AS ONE REPORT (`mtest ci report`, the workflow run's group). + report: + name: tests (report) + needs: conformance + if: always() + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/mtest + - uses: actions/download-artifact@v4 + with: + pattern: mtest-* + path: ${{ runner.temp }}/stores + - name: Every job's runs, reported together + shell: bash + run: | + set -euo pipefail + found=0 + for store in "$RUNNER_TEMP"/stores/*/; do + [ -f "$store/index.ndjson" ] || continue + "$MTEST" store import --from "$store" + found=1 + done + if [ "$found" = 0 ]; then echo "no job kept a run" >> "$GITHUB_STEP_SUMMARY"; exit 0; fi + "$MTEST" ci report --out "$RUNNER_TEMP/tests.md" From d7d13fc660f7ed43bf32a2eeae168d4364a3a400 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 10 Nov 2026 04:44:37 +0900 Subject: [PATCH 3/7] Tests: written with the mtest library Every test under tests/ declares its cases as mtest UTest objects, with the same assertions as before; what a test reported as not observed is an mtest skip. A test that starts a copy of itself declares what the copy does as a child and starts it with child_command. The copy that reads the environment first from two contexts is entered from the system's command line before mtest reads its own variables, which would fill the tables the copy observes; the copy that writes through a handle number is started without the runner's report variables. mcpplibs.mtest 0.5.2 is a dev-dependency. --- mcpp.toml | 6 ++ tests/bounded_read_of_an_idle_pipe.cpp | 26 ++--- tests/bounded_wait_of_a_bound_program.cpp | 21 ++-- tests/console_input.cpp | 24 ++--- tests/enumeration_ends_at_close.cpp | 20 ++-- tests/environment_read_from_two_threads.cpp | 58 +++++++--- tests/handle_inheritance.cpp | 29 ++--- tests/links_unc_and_bound.cpp | 15 ++- tests/null_device_at_a_position.cpp | 60 +++++------ tests/one_string_command_line.cpp | 18 ++-- tests/openkal_0_23.cpp | 114 ++++++++++---------- tests/stack_size.cpp | 16 ++- tests/what_a_started_program_receives.cpp | 81 +++++++++----- tests/write_handle_answers_info.cpp | 26 ++--- 14 files changed, 263 insertions(+), 251 deletions(-) diff --git a/mcpp.toml b/mcpp.toml index f67cbbd..ab53a91 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -146,3 +146,9 @@ default = [] # the loader, and this implementation adds nothing. Where it does not, the entry # point is this implementation's, and the link line names it. standalone = { defines = ["OKW_STANDALONE"] } + +# The tests are written with the mtest library (mcpp-community/mtest): each file +# declares its cases, and a test that starts a copy of itself declares what the +# copy does as a `child'. +[dev-dependencies] +mcpplibs.mtest = "0.5.2" diff --git a/tests/bounded_read_of_an_idle_pipe.cpp b/tests/bounded_read_of_an_idle_pipe.cpp index d4e8de8..ac85114 100644 --- a/tests/bounded_read_of_an_idle_pipe.cpp +++ b/tests/bounded_read_of_an_idle_pipe.cpp @@ -11,12 +11,15 @@ #include #include #include +import mcpplibs.mtest; -int main() { +using namespace mcpplibs::mtest; + +UTest bounded_read_of_an_idle_pipe_test{"bounded_read_of_an_idle_pipe", [] { kal_stream mine{}, theirs{}; if (kal_process_channel(&mine, &theirs) != kal_ok) { - std::printf("FAIL: a channel is made\n"); - return 1; + fail("a channel is made"); + return; } char byte = 0; const kal_duration started = kal_time_monotonic(); @@ -24,16 +27,7 @@ int main() { const kal_duration took = kal_time_monotonic() - started; kal_process_channel_close(theirs); kal_process_channel_close(mine); - int failures = 0; - if (n != -kal_err_again) { - std::printf("FAIL: a bounded read of an idle pipe expires (it answered %lld)\n", static_cast(n)); - ++failures; - } - if (took > 400000000ull) { - std::printf("FAIL: it expires near its bound (100 ms asked, %llu ms taken)\n", - static_cast(took / 1000000ull)); - ++failures; - } - if (failures == 0) std::printf("openkal-windows: a bounded read of an idle pipe expires near its bound\n"); - return failures == 0 ? 0 : 1; -} + expect(n == -kal_err_again) << "a bounded read of an idle pipe expires (it answered " << static_cast(n) << ")"; + expect(took <= 400000000ull) << "it expires near its bound (100 ms asked, " + << static_cast(took / 1000000ull) << " ms taken)"; +}}; diff --git a/tests/bounded_wait_of_a_bound_program.cpp b/tests/bounded_wait_of_a_bound_program.cpp index 214a0c7..a78cb41 100644 --- a/tests/bounded_wait_of_a_bound_program.cpp +++ b/tests/bounded_wait_of_a_bound_program.cpp @@ -11,16 +11,13 @@ #include #include #include +import mcpplibs.mtest; -namespace { +using namespace mcpplibs::mtest; -int failures = 0; +namespace { -void check(bool held, const char* what) { - if (held) { std::printf("ok: %s\n", what); return; } - std::printf("FAIL: %s\n", what); - ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } bool system_volume(kal_dir* volume) { const kal_uintptr count = kal_fs_preopen_count(); @@ -47,10 +44,9 @@ int start(const char* command, kal_process* p) { } // namespace -int main() { +UTest bounded_wait_of_a_bound_program_test{"bounded_wait_of_a_bound_program", [] { if ((kal_process_props() & KAL_PROCESS_PROP_BOUND_LIFETIME) == 0) { - std::printf("openkal-windows: no bound lifetime here; nothing to observe\n"); - return 0; + skip("no bound lifetime here; nothing to observe"); } kal_process slow{}; check(start("ping -n 3 127.0.0.1 >NUL", &slow) == kal_ok, "a bound program starts"); @@ -65,7 +61,4 @@ int main() { check(start("exit 3", &quick) == kal_ok, "a second bound program starts"); check(kal_timeout_wait_process(quick, 20000000000ull, &status, &terminated) == kal_ok && status == 3, "a bounded wait reports the status the program returned"); - - if (failures == 0) std::printf("openkal-windows: a bound program is waited for with a bound\n"); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/console_input.cpp b/tests/console_input.cpp index 0926d3a..12022c3 100644 --- a/tests/console_input.cpp +++ b/tests/console_input.cpp @@ -23,16 +23,13 @@ #include #include #include +import mcpplibs.mtest; -namespace { +using namespace mcpplibs::mtest; -int failures = 0; +namespace { -void check(bool held, const char* what) { - std::printf(" %s %s\n", held ? "held" : "FAIL", what); - std::fflush(stdout); - if (!held) ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } DWORD WINAPI watchdog(void*) { Sleep(20000); @@ -112,17 +109,15 @@ void show(const char* mode, const unsigned char* b, int n) { } // namespace -int main() { +UTest console_input_test{"console_input", [] { FreeConsole(); if (!AllocConsole()) { - std::printf("openkal-windows: no console could be allocated; not observed\n"); - return 0; + skip("no console could be allocated; not observed"); } HANDLE in = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr); if (in == INVALID_HANDLE_VALUE) { - std::printf("openkal-windows: the console's input could not be opened; not observed\n"); - return 0; + skip("the console's input could not be opened; not observed"); } CreateThread(nullptr, 0, watchdog, nullptr, 0, nullptr); const kal_stream s{ reinterpret_cast(in) }; @@ -190,8 +185,5 @@ int main() { } show("line on: a Enter", b, got); check(got >= 2 && b[0] == 'a', "a line is delivered"); - - std::printf("openkal-windows: what a console delivers, %s\n", failures ? "FAILED" : "held"); std::fflush(stdout); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/enumeration_ends_at_close.cpp b/tests/enumeration_ends_at_close.cpp index 96aaa4e..4563e39 100644 --- a/tests/enumeration_ends_at_close.cpp +++ b/tests/enumeration_ends_at_close.cpp @@ -11,18 +11,16 @@ #include #include #include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; extern "C" OKW_IMPORT BOOL OKW_API GetProcessHandleCount(HANDLE, DWORD*); extern "C" OKW_IMPORT HANDLE OKW_API GetCurrentProcess(void); namespace { -int failures = 0; - -void check(bool held, const char* what) { - std::printf(" %s %s\n", held ? "held" : "FAIL", what); - if (!held) ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } DWORD handles() { DWORD n = 0; return GetProcessHandleCount(GetCurrentProcess(), &n) ? n : 0; } @@ -41,11 +39,11 @@ void reader(void* p) { } // namespace -int main() { +UTest enumeration_ends_at_close_test{"enumeration_ends_at_close", [] { kal_dir cwd{}; if (kal_fs_preopen_count() == 0 || kal_fs_preopen(0, &cwd, nullptr, 0, nullptr) != kal_ok) { - std::printf("openkal-windows: no preopened directory to test in\n"); - return 1; + fail("a preopened directory to test in"); + return; } kal_fs_mkdir(cwd, "okw-enum.d", 10); kal_file f{}; @@ -101,6 +99,4 @@ int main() { kal_fs_remove(cwd, "okw-enum.d/inner", 16); kal_fs_remove(cwd, "okw-enum.d", 10); - std::printf("openkal-windows: what closing a directory ends, %s\n", failures ? "FAILED" : "held"); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/environment_read_from_two_threads.cpp b/tests/environment_read_from_two_threads.cpp index 5b37504..6d689d2 100644 --- a/tests/environment_read_from_two_threads.cpp +++ b/tests/environment_read_from_two_threads.cpp @@ -6,13 +6,27 @@ // --- PATH --- was reported absent. The first enquiry is made here from two // contexts released together, in a fresh copy of this program each round, and // both are told the variable. +// +// THE COPY BEGINS BEFORE THE TEST LIBRARY DOES. mtest's state reads its own +// variables through openkal when the first case or child of this file is +// declared, which would fill the tables before the two contexts ask. The copy +// is therefore recognised from the system's own command line, by the +// arguments child_command("copy") gives it, during this file's initialisation +// and ahead of every declaration, and it ends there with its status. #include #include +#include +#include +#include "../src/win32.h" #include #include #include #include #include +#include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; namespace { @@ -37,14 +51,29 @@ int copy() { return g_found[0] && g_found[1] ? 0 : 1; } +// Whether the system's command line ends with the arguments of the copy. +bool started_as_copy() { + const wchar_t* line = GetCommandLineW(); + const wchar_t tail[] = L" --mtest-child copy"; + kal_uintptr n = 0, t = sizeof tail / sizeof tail[0] - 1; + while (line[n]) ++n; + while (n > 0 && line[n - 1] == L' ') --n; + if (n < t) return false; + for (kal_uintptr i = 0; i < t; ++i) if (line[n - t + i] != tail[i]) return false; + return true; +} + +[[maybe_unused]] const int g_copy = started_as_copy() ? (kal_exit(copy()), 0) : 0; + } // namespace -int main(int argc, char** argv) { - if (argc > 1 && std::strcmp(argv[1], "--copy") == 0) return copy(); +// Declared so that the copy's arguments are mtest's; it is entered above. +child copy_of_this{"copy", [](std::vector) { return copy(); }}; +UTest environment_read_from_two_threads_test{"environment_read_from_two_threads", [] { char self[1024]; const kal_intptr n = kal_env_image(self, sizeof self - 1); - if (n <= 0) { std::printf("FAIL: this program's image is named\n"); return 1; } + if (n <= 0) { fail("this program's image is named"); return; } self[n] = 0; // `C:\...\x.exe' named relative to the volume the image is on. kal_dir volume{}; @@ -53,26 +82,23 @@ int main(int argc, char** argv) { if (kal_fs_preopen(i, &d, name, sizeof name, &len) != kal_ok) continue; if (len == 2 && (name[0] | 0x20) == (self[0] | 0x20) && name[1] == ':') { volume = d; break; } } - if (!volume.h) { std::printf("openkal-windows: the image's volume is not granted; nothing to observe\n"); return 0; } + if (!volume.h) skip("the image's volume is not granted; nothing to observe"); const char* path = self + 3; // past `C:\' int failures = 0; for (int round = 0; round < 20; ++round) { - const char* args[2] = { self, "--copy" }; - const kal_uintptr lens[2] = { std::strlen(self), 6 }; + const std::vector command = child_command("copy"); + std::vector args; + std::vector lens; + for (const auto& a : command) { args.push_back(a.c_str()); lens.push_back(a.size()); } const kal_spawn how{ volume, volume, nullptr, nullptr, 0, 0 }; kal_process p{}; - if (kal_process_spawn(&how, path, std::strlen(path), args, lens, 2, nullptr, nullptr, 0, nullptr, &p) != kal_ok) { - std::printf("FAIL: a copy starts\n"); - return 1; + if (kal_process_spawn(&how, path, std::strlen(path), args.data(), lens.data(), args.size(), nullptr, nullptr, 0, nullptr, &p) != kal_ok) { + fail("a copy starts"); + return; } int status = -1, terminated = 0; kal_process_wait(p, &status, &terminated); if (status != 0) ++failures; } - if (failures) { - std::printf("FAIL: in %d of 20 copies a context read the environment before it was filled\n", failures); - return 1; - } - std::printf("openkal-windows: the environment read first from two contexts at once\n"); - return 0; -} + expect(failures == 0) << "in " << failures << " of 20 copies a context read the environment before it was filled"; +}}; diff --git a/tests/handle_inheritance.cpp b/tests/handle_inheritance.cpp index 85a6c06..cf9b8a6 100644 --- a/tests/handle_inheritance.cpp +++ b/tests/handle_inheritance.cpp @@ -18,6 +18,7 @@ // the child's own duplicate keeps the pipe open and the bounded read does not // complete within the bound -- which is what turns a hang into a report. #include +#include #include #include "../src/win32.h" #include @@ -25,16 +26,13 @@ #include #include #include +import mcpplibs.mtest; -namespace { +using namespace mcpplibs::mtest; -int failures = 0; +namespace { -void check(bool held, const char* what) { - if (held) { std::printf("ok: %s\n", what); return; } - std::printf("FAIL: %s\n", what); - ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } // A volume, found among the preopens by its own name: exactly a letter and a // colon, which is how src/fs.cpp reports one and how it distinguishes a @@ -55,11 +53,10 @@ bool find_a_volume(kal_dir* out) { } // namespace -int main() { +UTest handle_inheritance_test{"handle_inheritance", [] { kal_dir sys{}; if (!find_a_volume(&sys)) { - std::printf("SKIP: no volume was found among the preopens\n"); - return 0; + skip("no volume was found among the preopens"); } // The handle this test makes inheritable and never places. A real caller @@ -70,18 +67,16 @@ int main() { sa.bInheritHandle = TRUE; HANDLE leak_read = nullptr, leak_write = nullptr; if (!CreatePipe(&leak_read, &leak_write, &sa, 0)) { - std::printf("SKIP: the pipe for the leak this test looks for could not be made\n"); - return 0; + skip("the pipe for the leak this test looks for could not be made"); } // The one stream this spawn legitimately places: the child's standard // input, which keeps a shell reading from it alive until told to stop. kal_stream mine{}, child_stdin{}; if (kal_process_channel(&mine, &child_stdin) != kal_ok) { - std::printf("SKIP: a channel for the child's standard input could not be made\n"); CloseHandle(leak_read); CloseHandle(leak_write); - return 0; + skip("a channel for the child's standard input could not be made"); } const char* path = "Windows/System32/cmd.exe"; @@ -92,12 +87,11 @@ int main() { const int spawned = kal_process_spawn(&how, path, path_len, &path, &path_len, 1, nullptr, nullptr, 0, &streams, &child); if (spawned != kal_ok) { - std::printf("SKIP: cmd.exe could not be started (kal error %d)\n", spawned); kal_process_channel_close(mine); kal_process_channel_close(child_stdin); CloseHandle(leak_read); CloseHandle(leak_write); - return 0; + skip("cmd.exe could not be started (kal error " + std::to_string(spawned) + ")"); } // The child's own copy of its standard input. This end belongs to the // child now; this process keeps `mine', which is what lets it end the @@ -128,5 +122,4 @@ int main() { CloseHandle(leak_read); std::printf("openkal-windows: a spawn inherits only the handles it placed\n"); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/links_unc_and_bound.cpp b/tests/links_unc_and_bound.cpp index b669644..5a497ac 100644 --- a/tests/links_unc_and_bound.cpp +++ b/tests/links_unc_and_bound.cpp @@ -12,13 +12,12 @@ #include #include #include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; namespace { -int failures = 0; -void check(bool held, const char* what) { - std::printf(" %s %s\n", held ? "held" : "FAIL", what); - if (!held) ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } kal_dir preopen_named(const char* want) { for (kal_uintptr i = 0; i < kal_fs_preopen_count(); ++i) { kal_dir d{}; char n[64]; kal_uintptr l = 0; @@ -29,7 +28,7 @@ kal_dir preopen_named(const char* want) { } } -int main() { +UTest links_unc_and_bound_test{"links_unc_and_bound", [] { kal_dir here{}; char n[512]; kal_uintptr l = 0; kal_fs_preopen(0, &here, n, sizeof n, &l); @@ -76,6 +75,4 @@ int main() { if (rc == kal_ok) { kal_process_wait(p, &status, &terminated); kal_process_close(p); } check(rc == kal_ok && status == 5, "a program bound to this one is started and awaited"); } - std::printf(failures == 0 ? "openkal-windows: links, the network's names, a bound lifetime\n" : ""); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/null_device_at_a_position.cpp b/tests/null_device_at_a_position.cpp index ff21e5b..14f0a0f 100644 --- a/tests/null_device_at_a_position.cpp +++ b/tests/null_device_at_a_position.cpp @@ -5,25 +5,24 @@ // was; and a flag beside a placed stream at one position is refused. #include #include +#include +#include #include "../src/win32.h" #include #include #include #include #include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; extern "C" OKW_IMPORT unsigned long OKW_API GetModuleFileNameW(void*, wchar_t*, unsigned long); extern "C" OKW_IMPORT BOOL OKW_API GetProcessHandleCount(HANDLE, DWORD*); namespace { -int failures = 0; - -void check(bool held, const char* what) { - if (held) { std::printf("ok: %s\n", what); return; } - std::printf("FAIL: %s\n", what); - ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } constexpr kal_u64 kBound = 10000ull * 1000ull * 1000ull; // ten seconds @@ -65,34 +64,37 @@ bool locate_self(kal_dir* volume, char* relative, kal_uintptr cap) { } // namespace -int main(int argc, char** argv) { - // The started side. 41-43: a position is not a character device. 44: a - // read did not report the end of input. 45: a write was not accepted. - if (argc == 2 && std::strcmp(argv[1], "--null") == 0) { - if (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) != FILE_TYPE_CHAR) return 41; - if (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) != FILE_TYPE_CHAR) return 42; - if (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) != FILE_TYPE_CHAR) return 43; - char b[8]; - if (kal_stream_read(kal_stdin(), b, sizeof b) != 0) return 44; - if (kal_stream_write(kal_stdout(), "discarded", 9) != 9) return 45; - return 0; - } +// The started side. 41-43: a position is not a character device. 44: a read +// did not report the end of input. 45: a write was not accepted. +child null_device{"null", [](std::vector args) { + if (!args.empty()) return 46; + if (GetFileType(GetStdHandle(STD_INPUT_HANDLE)) != FILE_TYPE_CHAR) return 41; + if (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) != FILE_TYPE_CHAR) return 42; + if (GetFileType(GetStdHandle(STD_ERROR_HANDLE)) != FILE_TYPE_CHAR) return 43; + char b[8]; + if (kal_stream_read(kal_stdin(), b, sizeof b) != 0) return 44; + if (kal_stream_write(kal_stdout(), "discarded", 9) != 9) return 45; + return 0; +}}; + +UTest null_device_at_a_position_test{"null_device_at_a_position", [] { kal_dir volume{}; char self[1024]; if (!locate_self(&volume, self, sizeof self)) { - std::printf("SKIP: this program's own name could not be expressed against a volume\n"); - return 0; + skip("this program's own name could not be expressed against a volume"); } check((kal_process_props() & KAL_PROCESS_PROP_NULL_STREAMS) != 0, "the null device is claimed"); - const char* args[2] = { "null_device_at_a_position", "--null" }; - const kal_uintptr lens[2] = { std::strlen(args[0]), std::strlen(args[1]) }; + const std::vector command = child_command("null"); + std::vector args; + std::vector lens; + for (const auto& a : command) { args.push_back(a.c_str()); lens.push_back(a.size()); } const kal_spawn how{ volume, volume, nullptr, nullptr, 0, KAL_SPAWN_NULL_IN | KAL_SPAWN_NULL_OUT | KAL_SPAWN_NULL_ERR }; auto run = [&](int& status, int& terminated) { kal_process child{}; - const int spawned = kal_process_spawn(&how, self, std::strlen(self), args, lens, 2, + const int spawned = kal_process_spawn(&how, self, std::strlen(self), args.data(), lens.data(), args.size(), nullptr, nullptr, 0, nullptr, &child); if (spawned == kal_ok) { if (kal_timeout_wait_process(child, kBound, &status, &terminated) != kal_ok) @@ -103,9 +105,8 @@ int main(int argc, char** argv) { }; int status = -1, terminated = -1; check(run(status, terminated) == kal_ok, "a copy is started with the null device at its three positions"); - check(terminated == 0 && status == 0, - "the copy finds a character device there, reads its end and writes to it"); - if (status > 0) std::printf(" the copy reported %d\n", status); + expect(terminated == 0 && status == 0) + << "the copy finds a character device there, reads its end and writes to it (reported " << status << ")"; // Counted across a second start: the first start of a process opens // handles of the system's own that it keeps (measured on the runner, not // under Wine), and those are not this operation's. @@ -118,7 +119,7 @@ int main(int argc, char** argv) { const kal_spawn_streams placed{ kal_stream{0}, theirs, kal_stream{0} }; const kal_spawn out_null{ volume, volume, nullptr, nullptr, 0, KAL_SPAWN_NULL_OUT }; kal_process p{}; - check(kal_process_spawn(&out_null, self, std::strlen(self), args, lens, 2, + check(kal_process_spawn(&out_null, self, std::strlen(self), args.data(), lens.data(), args.size(), nullptr, nullptr, 0, &placed, &p) == kal_err_invalid, "the null device beside a placed stream at one position is refused"); kal_process_channel_close(theirs); @@ -126,5 +127,4 @@ int main(int argc, char** argv) { } std::printf("openkal-windows: the null device at a position\n"); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/one_string_command_line.cpp b/tests/one_string_command_line.cpp index be4acc6..b99ef9d 100644 --- a/tests/one_string_command_line.cpp +++ b/tests/one_string_command_line.cpp @@ -13,16 +13,13 @@ #include #include #include +import mcpplibs.mtest; -namespace { +using namespace mcpplibs::mtest; -int failures = 0; +namespace { -void check(bool held, const char* what) { - if (held) { std::printf("ok: %s\n", what); return; } - std::printf("FAIL: %s\n", what); - ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } bool system_volume(kal_dir* volume) { const kal_uintptr count = kal_fs_preopen_count(); @@ -68,15 +65,14 @@ int run(kal_uintptr flags, const char* command, char* out, kal_uintptr cap, kal_ } // namespace -int main() { +UTest one_string_command_line_test{"one_string_command_line", [] { check((kal_process_props() & KAL_PROCESS_PROP_ONE_STRING) != 0, "the position is claimed"); char out[4096]; kal_uintptr got = 0; const char* command = "\"echo \"a b\" && echo c\""; const int rc = run(KAL_SPAWN_ONE_STRING, command, out, sizeof out, &got); - if (rc == -1) { std::printf("skip: no C: volume among the preopens\n"); return 0; } + if (rc == -1) skip("no C: volume among the preopens"); check(rc == kal_ok, "the interpreter starts with the command line joined as it is"); check(std::strstr(out, "\"a b\"") != nullptr && std::strstr(out, "c") != nullptr, "and runs the command as written, quotation marks included"); - return failures; -} +}}; diff --git a/tests/openkal_0_23.cpp b/tests/openkal_0_23.cpp index e837c8f..325cafd 100644 --- a/tests/openkal_0_23.cpp +++ b/tests/openkal_0_23.cpp @@ -14,6 +14,8 @@ // own file. #include #include +#include +#include #include "../src/win32.h" #include "../src/handle.h" #include @@ -23,15 +25,13 @@ #include #include #include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; namespace { -int failures = 0; -void check(bool held, const char* what) { - if (held) { std::printf("ok: %s\n", what); return; } - std::printf("FAIL: %s\n", what); - ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } kal_uintptr length(const char* s) { return std::strlen(s); } @@ -76,11 +76,14 @@ kal_dir by_name(const char* want) { // the caller's preopens, and returns its status, or -1. int start(kal_dir volume, const char* errand, const kal_preopen* grants, kal_uintptr n, kal_uintptr flags, const kal_spawn_streams* streams = nullptr, bool null_grants = false) { - const char* argv[2] = { "openkal_0_23", errand }; - const kal_uintptr lens[2] = { length(argv[0]), length(errand) }; + const std::vector command = child_command(errand); + std::vector argv; + std::vector lens; + for (const auto& a : command) { argv.push_back(a.c_str()); lens.push_back(a.size()); } const kal_spawn how{ volume, volume, nullptr, null_grants ? nullptr : grants, n, flags }; kal_process p{}; - if (kal_process_spawn(&how, g_self, length(g_self), argv, lens, 2, nullptr, nullptr, 0, streams, &p) != kal_ok) + if (kal_process_spawn(&how, g_self, length(g_self), argv.data(), lens.data(), argv.size(), nullptr, nullptr, 0, + streams, &p) != kal_ok) return -1; int status = -1, terminated = 0; kal_process_wait(p, &status, &terminated); @@ -109,50 +112,50 @@ int memory_priority() { } // namespace -int main(int argc, char** argv) { - const char* errand = argc > 1 ? argv[1] : ""; +// The recorded grant is the first preopen: a file read, a name absent, and the +// end through kal_exit, which ends the record. +child read_granted{"read", [](std::vector) -> int { + kal_dir d = by_name("/granted"); + kal_file f{}; + if (kal_fs_open(d, "present.txt", 11, KAL_OPEN_READ, &f) != kal_ok) kal_exit(52); + kal_fs_close_file(f); + kal_node_info i{}; i.self_size = sizeof i; + kal_fs_info(d, "absent.txt", 10, 0, KAL_INFO_KIND, &i); + kal_exit(0); +}}; - if (std::strcmp(errand, "--read") == 0) { - // The recorded grant is the first preopen: a file read, a name absent, - // and the end through kal_exit, which ends the record. - kal_dir d = by_name("/granted"); - kal_file f{}; - if (kal_fs_open(d, "present.txt", 11, KAL_OPEN_READ, &f) != kal_ok) kal_exit(52); - kal_fs_close_file(f); - kal_node_info i{}; i.self_size = sizeof i; - kal_fs_info(d, "absent.txt", 10, 0, KAL_INFO_KIND, &i); - kal_exit(0); - } - if (std::strcmp(errand, "--grants") == 0) { - if (kal_fs_preopen_count() != 2) return 41; - kal_dir own_volume{}; - if (!locate_self(&own_volume)) return 47; - kal_dir granted = by_name("/granted"); - if (!granted.h) return 42; - DWORD flags = 1; - if (!GetHandleInformation(okw::unpack(granted.h), &flags) || (flags & HANDLE_FLAG_INHERIT)) return 43; - // A program this one starts without grants, the variable inherited. - kal_dir volume{}; - for (kal_uintptr i = 0; i < kal_fs_preopen_count(); ++i) { - char name[16]{}; kal_uintptr len = 0; kal_dir d{}; - if (kal_fs_preopen(i, &d, name, sizeof name, &len) == kal_ok && len == 2 && name[1] == ':') volume = d; - } - if (!volume.h) return 44; - return start(volume, "--defaults", nullptr, 0, 0, nullptr, true) == 0 ? 0 : 45; - } - if (std::strcmp(errand, "--defaults") == 0) - return kal_fs_preopen_count() > 2 && by_name("/granted").h == 0 ? 0 : 46; - if (std::strcmp(errand, "--priority") == 0) { - const int p = memory_priority(); - return p < 0 ? 69 : 70 + p; +child holds_its_grants{"grants", [](std::vector) { + if (kal_fs_preopen_count() != 2) return 41; + kal_dir own_volume{}; + if (!locate_self(&own_volume)) return 47; + kal_dir granted = by_name("/granted"); + if (!granted.h) return 42; + DWORD flags = 1; + if (!GetHandleInformation(okw::unpack(granted.h), &flags) || (flags & HANDLE_FLAG_INHERIT)) return 43; + // A program this one starts without grants, the variable inherited. + kal_dir volume{}; + for (kal_uintptr i = 0; i < kal_fs_preopen_count(); ++i) { + char name[16]{}; kal_uintptr len = 0; kal_dir d{}; + if (kal_fs_preopen(i, &d, name, sizeof name, &len) == kal_ok && len == 2 && name[1] == ':') volume = d; } - if (std::strcmp(errand, "--fault") == 0) { fault_here(); return 0; } + if (!volume.h) return 44; + return start(volume, "defaults", nullptr, 0, 0, nullptr, true) == 0 ? 0 : 45; +}}; + +child has_the_defaults{"defaults", [](std::vector) { + return kal_fs_preopen_count() > 2 && by_name("/granted").h == 0 ? 0 : 46; +}}; + +child reports_priority{"priority", [](std::vector) { + const int p = memory_priority(); + return p < 0 ? 69 : 70 + p; +}}; +child faults{"fault", [](std::vector) { fault_here(); return 0; }}; + +UTest openkal_0_23_test{"openkal_0_23", [] { kal_dir volume{}; - if (!locate_self(&volume)) { - std::printf("SKIP: this program's own name could not be expressed against a volume\n"); - return 0; - } + if (!locate_self(&volume)) skip("this program's own name could not be expressed against a volume"); char tmp[1100]; std::snprintf(tmp, sizeof tmp, "%s/okw-0.23.tmp", g_dir); kal_fs_mkdir(volume, tmp, length(tmp)); @@ -170,7 +173,7 @@ int main(int argc, char** argv) { check((kal_process_props() & KAL_PROCESS_PROP_GRANT_DIR) != 0, "the directory grant is claimed"); { const kal_preopen grants[2] = { { dir, "/granted", 8 }, { volume, g_volume_name, 2 } }; - check(start(volume, "--grants", grants, 2, 0) == 0, + check(start(volume, "grants", grants, 2, 0) == 0, "the started program has its grants, uninheritable, and one it starts without grants has the defaults"); } @@ -186,7 +189,7 @@ int main(int argc, char** argv) { kal_dir rd{}; check(kal_fs_dir_attenuate(dir, &rights, &rd) == kal_ok, "the directory is attenuated, recorded"); const kal_preopen grants[2] = { { rd, "/granted", 8 }, { volume, g_volume_name, 2 } }; - check(start(volume, "--read", grants, 2, 0) == 0, "a program granted the recorded handle reads through it"); + check(start(volume, "read", grants, 2, 0) == 0, "a program granted the recorded handle reads through it"); kal_fs_close_dir(rd); kal_fs_close_file(rec); char buf[4096] = {}; @@ -239,11 +242,11 @@ int main(int argc, char** argv) { // ECONOMY. { const int mine = memory_priority(); - const int low = start(volume, "--priority", nullptr, 0, KAL_SPAWN_RESOURCES_ECONOMY, nullptr, true); + const int low = start(volume, "priority", nullptr, 0, KAL_SPAWN_RESOURCES_ECONOMY, nullptr, true); if (low == 69 || mine < 0) std::printf(" this system does not report a program's memory priority\n"); else { check(low == 72, "a program started for economy has a low memory priority"); - check(start(volume, "--priority", nullptr, 0, KAL_SPAWN_RESOURCES_PERFORMANCE, nullptr, true) == 70 + mine, + check(start(volume, "priority", nullptr, 0, KAL_SPAWN_RESOURCES_PERFORMANCE, nullptr, true) == 70 + mine, "and one started for performance has the system's, its starter's"); } } @@ -253,7 +256,7 @@ int main(int argc, char** argv) { kal_stream mine{}, theirs{}; if (kal_process_channel(&mine, &theirs) == kal_ok) { const kal_spawn_streams streams{ {0}, {0}, theirs }; - const int s = start(volume, "--fault", nullptr, 0, 0, &streams, true); + const int s = start(volume, "fault", nullptr, 0, 0, &streams, true); kal_process_channel_close(theirs); static char report[16384]; kal_uintptr have = 0; @@ -281,5 +284,4 @@ int main(int argc, char** argv) { kal_fs_close_dir(dir); kal_fs_remove(volume, tmp, length(tmp)); std::printf("openkal-windows: openkal 0.23\n"); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/stack_size.cpp b/tests/stack_size.cpp index 392b04a..9115a07 100644 --- a/tests/stack_size.cpp +++ b/tests/stack_size.cpp @@ -6,15 +6,13 @@ #include #include #include +import mcpplibs.mtest; -namespace { +using namespace mcpplibs::mtest; -int failures = 0; +namespace { -void check(bool held, const char* what) { - std::printf(" %s %s\n", held ? "held" : "FAIL", what); - if (!held) ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } long recurse(long n); long (*volatile g_recurse)(long) = recurse; @@ -37,7 +35,7 @@ void room(void* p) { } // namespace -int main() { +UTest stack_size_test{"stack_size", [] { const kal_uintptr asks[] = { 1u << 20, 16u << 20, 64u << 20 }; for (kal_uintptr ask : asks) { probe q{ ask, 0, 0 }; @@ -58,6 +56,4 @@ int main() { kal_task never{}; check(kal_task_start_sized(room, nullptr, ~kal_uintptr{0} - 4095, &never) == kal_err_no_memory, "a stack larger than the address space is reported as exhaustion"); - std::printf("openkal-windows: a least stack size, %s\n", failures ? "FAILED" : "held"); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/what_a_started_program_receives.cpp b/tests/what_a_started_program_receives.cpp index 289d3f0..3f7d4b7 100644 --- a/tests/what_a_started_program_receives.cpp +++ b/tests/what_a_started_program_receives.cpp @@ -21,12 +21,18 @@ #include #include #include +#include +#include #include "../src/win32.h" +#include #include #include #include #include #include +import mcpplibs.mtest; + +using namespace mcpplibs::mtest; // The one declaration src/win32.h does not carry, because the implementation // never needs to know where its own program is. @@ -34,13 +40,7 @@ extern "C" OKW_IMPORT unsigned long OKW_API GetModuleFileNameW(void*, wchar_t*, namespace { -int failures = 0; - -void check(bool held, const char* what) { - if (held) { std::printf("ok: %s\n", what); return; } - std::printf("FAIL: %s\n", what); - ++failures; -} +void check(bool held, const char* what) { expect(held) << what; } constexpr kal_u64 kBound = 10000ull * 1000ull * 1000ull; // ten seconds @@ -74,14 +74,33 @@ bool locate_self(kal_dir* volume, char* relative, kal_uintptr cap) { return false; } +// This program's environment without the test runner's report. A copy that +// inherited MTEST_REPORT would hold the report open, and the number it writes +// through could name that file and not nothing. +std::vector environment_without_report() { + std::vector out; + for (kal_uintptr i = 0; i < kal_env_var_count(); ++i) { + char name[512]; + const kal_intptr n = kal_env_var_at(i, name, sizeof name); + if (n <= 0 || static_cast(n) >= sizeof name) continue; + const std::string key(name, static_cast(n)); + if (key == "MTEST_REPORT" || key == "MTEST_PROTOCOL") continue; + const kal_intptr v = kal_env_var(key.data(), key.size(), nullptr, 0); + if (v < 0) continue; + std::string value(static_cast(v), '\0'); + if (v > 0) kal_env_var(key.data(), key.size(), value.data(), value.size()); + out.push_back(key + "=" + value); + } + return out; +} + // Starts a copy that writes one byte through `value', awaits it, and reports // whether the byte reached the pipe the caller holds the other end of. // `place' selects whether the start places one stream of its own. void observe(kal_dir volume, const char* self, bool place, const char* what) { HANDLE pipe_read = nullptr, pipe_write = nullptr; if (!CreatePipe(&pipe_read, &pipe_write, nullptr, 0)) { - std::printf("SKIP: %s (the pipe could not be made)\n", what); - return; + skip(std::string(what) + " (the pipe could not be made)"); } // Inheritable for a reason of the caller's own, and never placed. SetHandleInformation(pipe_write, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); @@ -89,8 +108,14 @@ void observe(kal_dir volume, const char* self, bool place, const char* what) { char value[32]; std::snprintf(value, sizeof value, "%llu", static_cast(reinterpret_cast(pipe_write))); - const char* argv[3] = { "what_a_started_program_receives", "--write-through", value }; - const kal_uintptr lens[3] = { std::strlen(argv[0]), std::strlen(argv[1]), std::strlen(argv[2]) }; + const std::vector command = child_command("write-through", std::vector{ value }); + std::vector argv; + std::vector lens; + for (const auto& a : command) { argv.push_back(a.c_str()); lens.push_back(a.size()); } + const std::vector environment = environment_without_report(); + std::vector envp; + std::vector elens; + for (const auto& e : environment) { envp.push_back(e.c_str()); elens.push_back(e.size()); } kal_stream mine{}, theirs{}; const bool channel = place && kal_process_channel(&mine, &theirs) == kal_ok; @@ -98,8 +123,8 @@ void observe(kal_dir volume, const char* self, bool place, const char* what) { const kal_spawn how{ volume, volume, nullptr, nullptr, 0, 0 }; kal_process child{}; - const int spawned = kal_process_spawn(&how, self, std::strlen(self), argv, lens, 3, - nullptr, nullptr, 0, channel ? &streams : nullptr, + const int spawned = kal_process_spawn(&how, self, std::strlen(self), argv.data(), lens.data(), argv.size(), + envp.data(), elens.data(), envp.size(), channel ? &streams : nullptr, &child); check(spawned == kal_ok, "a copy of this program is started"); if (channel) kal_process_channel_close(theirs); @@ -124,23 +149,24 @@ void observe(kal_dir volume, const char* self, bool place, const char* what) { } // namespace -int main(int argc, char** argv) { - // The started side: write one byte through the number it was given. Where - // the handle was not conveyed the number names nothing, or names some - // object of this program's own that refuses the write. - if (argc == 3 && std::strcmp(argv[1], "--write-through") == 0) { - const HANDLE h = reinterpret_cast( - static_cast(std::strtoull(argv[2], nullptr, 10))); - DWORD wrote = 0; - WriteFile(h, "x", 1, &wrote, nullptr); - return 0; - } +// The started side: write one byte through the number it was given. Where the +// handle was not conveyed the number names nothing, or names some object of +// this program's own that refuses the write. +child write_through{"write-through", [](std::vector args) { + if (args.size() != 1) return 2; + const HANDLE h = reinterpret_cast( + static_cast(std::strtoull(args[0].c_str(), nullptr, 10))); + DWORD wrote = 0; + WriteFile(h, "x", 1, &wrote, nullptr); + return 0; +}}; + +UTest what_a_started_program_receives_test{"what_a_started_program_receives", [] { kal_dir volume{}; char self[1024]; if (!locate_self(&volume, self, sizeof self)) { - std::printf("SKIP: this program's own name could not be expressed against a volume\n"); - return 0; + skip("this program's own name could not be expressed against a volume"); } observe(volume, self, false, @@ -149,5 +175,4 @@ int main(int argc, char** argv) { "an unplaced inheritable handle does not reach a program started with a placed stream"); std::printf("openkal-windows: what a started program receives\n"); - return failures == 0 ? 0 : 1; -} +}}; diff --git a/tests/write_handle_answers_info.cpp b/tests/write_handle_answers_info.cpp index b0d418b..5e38aa6 100644 --- a/tests/write_handle_answers_info.cpp +++ b/tests/write_handle_answers_info.cpp @@ -25,23 +25,20 @@ // environment that cannot produce the failing condition is not a second // opinion about it. #include +#include #include #include +import mcpplibs.mtest; -namespace { +using namespace mcpplibs::mtest; -int failures = 0; +namespace { -void check(bool ok, const char* what) { - if (!ok) { - std::printf(" FAILED %s\n", what); - ++failures; - } -} +void check(bool ok, const char* what) { expect(ok) << what; } } // namespace -int main() { +UTest write_handle_answers_info_test{"write_handle_answers_info", [] { // The first preopened directory, the same way handle_inheritance.cpp finds // one: a test writes where the program was given somewhere to write. kal_dir cwd{}; @@ -58,8 +55,8 @@ int main() { } } if (!haveDir) { - std::printf("openkal-windows: no preopened directory to test in\n"); - return 1; + fail("a preopened directory to test in"); + return; } static const char name[] = "okw-write-handle-info.tmp"; @@ -71,8 +68,8 @@ int main() { &f); check(opened == kal_ok, "a file opens for writing"); if (opened != kal_ok) { - std::printf("openkal-windows: kal_fs_open returned %d\n", opened); - return 1; + fail("kal_fs_open returned " + std::to_string(opened)); + return; } // THE ASSERTION. The handle was granted a line ago and the caller asks it @@ -94,5 +91,4 @@ int main() { kal_fs_remove(cwd, name, len); std::printf("openkal-windows: a write-only handle answers for itself\n"); - return failures == 0 ? 0 : 1; -} +}}; From a9598afe92eb7c9ee0b880137d15fa71bcacec73 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 10 Nov 2026 05:16:13 +0900 Subject: [PATCH 4/7] Build: the gnu ABI's flags reach this package's sources only -fno-exceptions and -fno-rtti applied to every unit of the build, the tests included; the tests now import the mtest library, which imports std, and mcpp refuses a dialect flag that the prebuilt std interface was not compiled with. The flags are attached to src/** as openkal-linux attaches its own; the objects the checks examine are compiled as before. --- mcpp.toml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mcpp.toml b/mcpp.toml index ab53a91..b9577f8 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -125,7 +125,14 @@ ldflags = ["-lntdll", "-lsynchronization", "-lshell32", "-lkernel32", "-lbcrypt" # built with `--release` came out shortened by a different amount. The dev # profile does not optimize and never showed it, which is also why the check # below now builds both profiles. -cxxflags = ["-fno-exceptions", "-fno-rtti", "-fno-builtin"] +# +# The flags are attached to this package's own sources. The tests are +# consumers: they import the test library, which imports `std`, and the +# prebuilt `std` interface is compiled with exceptions and run-time type +# information, which a unit compiled without them cannot import. +flags = [ + { glob = "src/**", cxxflags = ["-fno-exceptions", "-fno-rtti", "-fno-builtin"] }, +] # The other ABI names them in the sources instead --- src/win.cpp for the four # above and src/random.cpp for bcrypt --- where its compilers record From f1424fa12707ddb62e5cdab9761d9638dfa5e250 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 10 Nov 2026 05:31:12 +0900 Subject: [PATCH 5/7] Tests on the gcc/gnu-CRT leg: console_input without , and the link accepts GCC's repeated std static console_input declares the console functions and the key record it uses beside src/win32.h: GCC on PE refuses together with the imported std ("conflicting language linkage for imported declaration std::__is_constant_evaluated()"). GCC on PE emits the function-local static std::_Sp_make_shared_tag::_S_ti()::__tag of an imported std as a definition that is not COMDAT, in every object importing std and in libstdc++.a, so every test link was refused. The CI step of the gcc leg writes -Wl,--allow-multiple-definition into its working copy of the manifest for the tests and restores the manifest afterwards; the committed manifest does not carry it. --- .github/workflows/ci.yml | 15 +++++++++++++++ tests/console_input.cpp | 31 +++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2214810..fd76c0c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -295,6 +295,21 @@ jobs: run: | extra='' [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' + # A TOOLCHAIN DEFECT, ANSWERED ON THIS LEG ONLY AND NOT IN THE MANIFEST. + # GCC on PE emits the function-local static + # `std::_Sp_make_shared_tag::_S_ti()::__tag' of an imported std as a + # definition that is not COMDAT, once in every object that imports + # std (here mtest's and mcpplibs.os's) and once in libstdc++.a, and + # the link of every test is refused as a multiple definition. The + # definitions are one entity, so the test links accept the repeats; + # the option is written into the working copy for this step and + # taken out after it, so it never reaches a consumer's link. + case '${{ matrix.toolchain }}' in gcc@*) + sed 's|^ldflags = \["-lntdll", \(.*\)\]$|ldflags = ["-lntdll", \1, "-Wl,--allow-multiple-definition"]|' \ + mcpp.toml > mcpp.toml.next && mv mcpp.toml.next mcpp.toml + grep -q -- '--allow-multiple-definition' mcpp.toml \ + || { echo "::error::the link option was not written"; exit 1; } ;; + esac mcpp test $extra 2>&1 | tee tests.log missing=0 for f in tests/*.cpp; do diff --git a/tests/console_input.cpp b/tests/console_input.cpp index 12022c3..61d461b 100644 --- a/tests/console_input.cpp +++ b/tests/console_input.cpp @@ -18,8 +18,8 @@ // absent or redirected. Every measurement ends with a key that always produces // a byte, so a key that produces none is measured rather than waited for; a // watchdog ends the test if even that does not arrive. -#include #include +#include "../src/win32.h" #include #include #include @@ -27,6 +27,33 @@ import mcpplibs.mtest; using namespace mcpplibs::mtest; +// The console's own functions and its key record, declared here beside +// src/win32.h rather than taken from : GCC on PE refuses the system +// header together with the imported std ("conflicting language linkage"). +struct KEY_EVENT_RECORD { + BOOL bKeyDown; + WORD wRepeatCount; + WORD wVirtualKeyCode; + WORD wVirtualScanCode; + union { wchar_t UnicodeChar; char AsciiChar; } uChar; + DWORD dwControlKeyState; +}; +struct INPUT_RECORD { + WORD EventType; + union { KEY_EVENT_RECORD KeyEvent; DWORD space[4]; } Event; +}; +static_assert(sizeof(INPUT_RECORD) == 20, "the system's layout"); +constexpr WORD KEY_EVENT = 0x0001; +constexpr DWORD LEFT_CTRL_PRESSED = 0x0008; +constexpr WORD VK_RETURN = 0x0D; +#define WINAPI OKW_API +extern "C" { +OKW_IMPORT BOOL OKW_API AllocConsole(void); +OKW_IMPORT BOOL OKW_API FreeConsole(void); +OKW_IMPORT BOOL OKW_API FlushConsoleInputBuffer(HANDLE); +OKW_IMPORT BOOL OKW_API WriteConsoleInputW(HANDLE, const INPUT_RECORD*, DWORD, DWORD*); +} + namespace { void check(bool held, const char* what) { expect(held) << what; } @@ -39,7 +66,7 @@ DWORD WINAPI watchdog(void*) { } void key(INPUT_RECORD* r, WORD vk, WORD scan, wchar_t ch, DWORD ctrl, BOOL down) { - ZeroMemory(r, sizeof *r); + *r = INPUT_RECORD{}; r->EventType = KEY_EVENT; r->Event.KeyEvent.bKeyDown = down; r->Event.KeyEvent.wRepeatCount = 1; From df84873cb23c952316faafd5667ec13674e76f19 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 10 Nov 2026 05:31:20 +0900 Subject: [PATCH 6/7] openkal 0.25.0, as released --- mcpp.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcpp.toml b/mcpp.toml index b9577f8..e77eda8 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -66,7 +66,7 @@ provides-interfaces = [ ] [dependencies] -openkal = { git = "https://github.com/mcpplibs/openkal.git", branch = "feat/0.25.0" } +openkal = "0.25.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package From 1ffab79304e524ada2e9a53c459b0832e2d42da3 Mon Sep 17 00:00:00 2001 From: Sunrisepeak Date: Tue, 10 Nov 2026 05:41:46 +0900 Subject: [PATCH 7/7] Tests on the gcc/gnu-CRT leg run under the profile gnu-odr The link option that GCC's repeated std static needs on PE is stated in a profile, [profile.gnu-odr]: the dev profile with -Wl,--allow-multiple-definition. A profile is the root's own and never reaches a consumer's link. The CI step no longer edits its working copy of the manifest. console_input's watchdog returns after ExitProcess, which src/win32.h does not declare as not returning (msvc C4716). --- .github/workflows/ci.yml | 18 +++--------------- mcpp.toml | 13 +++++++++++++ tests/console_input.cpp | 1 + 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fd76c0c..b9b06da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -295,21 +295,9 @@ jobs: run: | extra='' [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' - # A TOOLCHAIN DEFECT, ANSWERED ON THIS LEG ONLY AND NOT IN THE MANIFEST. - # GCC on PE emits the function-local static - # `std::_Sp_make_shared_tag::_S_ti()::__tag' of an imported std as a - # definition that is not COMDAT, once in every object that imports - # std (here mtest's and mcpplibs.os's) and once in libstdc++.a, and - # the link of every test is refused as a multiple definition. The - # definitions are one entity, so the test links accept the repeats; - # the option is written into the working copy for this step and - # taken out after it, so it never reaches a consumer's link. - case '${{ matrix.toolchain }}' in gcc@*) - sed 's|^ldflags = \["-lntdll", \(.*\)\]$|ldflags = ["-lntdll", \1, "-Wl,--allow-multiple-definition"]|' \ - mcpp.toml > mcpp.toml.next && mv mcpp.toml.next mcpp.toml - grep -q -- '--allow-multiple-definition' mcpp.toml \ - || { echo "::error::the link option was not written"; exit 1; } ;; - esac + # The gcc/gnu-CRT leg tests under `gnu-odr' (mcpp.toml says why): the + # dev profile, with the link option a GCC defect on PE needs. + case '${{ matrix.toolchain }}' in gcc@*) extra="$extra --profile gnu-odr" ;; esac mcpp test $extra 2>&1 | tee tests.log missing=0 for f in tests/*.cpp; do diff --git a/mcpp.toml b/mcpp.toml index e77eda8..d92a959 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -159,3 +159,16 @@ standalone = { defines = ["OKW_STANDALONE"] } # copy does as a `child'. [dev-dependencies] mcpplibs.mtest = "0.5.2" + +# THE TESTS ON THE GNU ABI WITH GCC, which CI runs under this profile. GCC on PE +# emits the function-local static `std::_Sp_make_shared_tag::_S_ti()::__tag' of +# an imported std as a definition that is not COMDAT, once in every object that +# imports std --- the test library's and mcpplibs.os's --- and once in +# libstdc++.a, so the link of every test is refused as a multiple definition. +# The definitions are one entity, and this profile's link accepts the repeats. +# A profile is the root's own, so the option never reaches a consumer's link. +# Otherwise it is the dev profile, so the leg tests what dev tests. +[profile.gnu-odr] +opt = 0 +debug = true +ldflags = ["-Wl,--allow-multiple-definition"] diff --git a/tests/console_input.cpp b/tests/console_input.cpp index 61d461b..06f06c2 100644 --- a/tests/console_input.cpp +++ b/tests/console_input.cpp @@ -63,6 +63,7 @@ DWORD WINAPI watchdog(void*) { std::printf(" FAIL a read waited for a key that never arrived\n"); std::fflush(stdout); ExitProcess(3); + return 3; } void key(INPUT_RECORD* r, WORD vk, WORD scan, wchar_t ch, DWORD ctrl, BOOL down) {