test: Check bitcoin wrapper child exit status on windows #36190

pull Bortlesboat wants to merge 5 commits into bitcoin:master from Bortlesboat:review/windows-launcher-36105-20260907 changing 7 files +70 −20
  1. Bortlesboat commented at 5:16 AM on September 8, 2026: contributor

    Follow-up to #36105, which makes the Windows bitcoin.exe wrapper wait for its child and forward the child's exit code. This adds the functional test for that.

    The case worth covering is exit code -1. The first version of the wrapper used _spawnvp(_P_WAIT), which returns -1 both when the spawn fails and when the child started fine and exited with -1, so bitcoin.exe reported a startup error and exited 1 instead of passing -1 through. #36105 now uses _P_NOWAIT + _cwait, which keeps the spawn result and the child status separate. Against the old launcher this test fails at not(1 == 4294967295).

    The test copies bitcoin.exe into a temp dir next to a copy of cmd.exe named bitcoind.exe, so bitcoin -M node /d /c exit N runs the fake child, and checks 0, 1 and -1 come back unchanged. Windows reports exit codes as unsigned 32-bit, so -1 arrives as 4294967295. It also checks the wrapper adds nothing to stdout/stderr. The exit code test is Windows only.

    ryanofsky preferred keeping the runtime fix in #36105 and reviewing the test here (https://github.com/bitcoin/bitcoin/pull/36190#issuecomment-5603692520), so this is the same commit he briefly carried in his branch. The branch still contains #36105's commits and should land after it.

    Checked on a native Windows 11 clang/UCRT release build, wallet/GUI/IPC off:

    cmake --build build --target bitcoin bitcoind bitcoin-cli
    python test/functional/tool_bitcoin.py --configfile=build/test/config.ini --tmpdir=build/tool-bitcoin-exit-status
    

    Fails against the pre-fix launcher, passes against the fixed one, and the rest of tool_bitcoin.py still passes. Use a fresh --tmpdir when re-running.

  2. util: Fix ExecVp on Windows to wait for child process
    Switch from _execvp to _spawnvp(_P_NOWAIT) + _cwait so bitcoin.exe
    waits for the child process to finish and forwards its exit code.
    Previously _execvp would exit the parent as soon as the child started,
    making it impossible for anything waiting on bitcoin.exe (such as a test
    framework) to track whether the child succeeded or failed. _P_NOWAIT is
    used instead of _P_WAIT so that a child exit code of -1 (0xffffffff) is
    not confused with a spawn failure: _spawnvp(_P_WAIT) returns -1 for both,
    but _spawnvp(_P_NOWAIT) returns the child handle on success, and _cwait
    fills a separate status that is forwarded via _exit.
    
    Co-Authored-By: Bortlesboat <169967362+Bortlesboat@users.noreply.github.com>
    This change was written with Claude Sonnet 4.6.
    4453ead327
  3. DrahtBot added the label Utils/log/libs on Sep 8, 2026
  4. DrahtBot commented at 5:16 AM on September 8, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36190.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    Stale ACK cyb3ralbert, ryanofsky

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36106 (bitcoin wrapper: respect CMAKE_INSTALL_BINDIR/LIBEXECDIR by ryanofsky)
    • #36022 (test: add coverage for bitcoin wrapper argument handling by cyb3ralbert)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  5. ryanofsky commented at 2:21 AM on September 9, 2026: contributor

    Thanks @Bortlesboat! I added your fix and test to #36105 since I think it makes more sense to change the execvp code once instead of multiple times. I think with those changes it probably makes sense to close this PR. I can also revert or partially revert though if you'd prefer this PR to be separate.

  6. Bortlesboat commented at 4:51 AM on September 9, 2026: contributor

    Thanks Ryan. I'd prefer to keep the exit-status regression here as a test-only follow-up, with the runtime fix staying in #36105. Would you be happy to leave the test and its imports out of your branch? I can rebase this PR once that's done.

  7. ryanofsky commented at 2:38 PM on September 9, 2026: contributor

    re: #36190 (comment)

    Would you be happy to leave the test and its imports out of your branch?

    No problem, dropped the test and happy to review it here

  8. Bortlesboat referenced this in commit 57b837251e on Sep 9, 2026
  9. Bortlesboat force-pushed on Sep 9, 2026
  10. Bortlesboat renamed this:
    util: preserve Windows child exit status -1
    test: Check Windows wrapper child exit status
    on Sep 9, 2026
  11. Bortlesboat marked this as ready for review on Sep 9, 2026
  12. Bortlesboat renamed this:
    test: Check Windows wrapper child exit status
    test: Check bitcoin wrapper child exit status on windows
    on Sep 9, 2026
  13. Bortlesboat force-pushed on Sep 9, 2026
  14. DrahtBot added the label CI failed on Sep 9, 2026
  15. DrahtBot removed the label CI failed on Sep 9, 2026
  16. Bortlesboat force-pushed on Sep 11, 2026
  17. cyb3ralbert commented at 1:50 PM on September 11, 2026: contributor

    Tested the test commit (b78e0a9d) on both Linux and a native Windows runner. It does what it says: reverting _P_NOWAIT + _cwait back to _P_WAIT is caught, and the -1 case is the one that catches it.

    One gap I'd suggest closing while this is still open.

    The bug the commit message refers to was actually a conflation: _spawnvp(_P_WAIT) returns -1 both when the spawn fails and when the child exits with -1. The test covers one side of that — a child exiting with -1 is forwarded — but not the other: a failed spawn must still be reported as a wrapper error rather than forwarded as a child status.

    Nothing in tool_bitcoin.py currently covers the failure path, and the branch carries a commit dedicated to it (68ae4a0f, where MSVCRT returns EINVAL instead of ENOENT for a path with a directory component).

    Leaving bitcoind out of the wrapper directory is enough, since the wrapper is invoked by absolute path and therefore does not fall back to a PATH search:

            # The other half of what _P_NOWAIT + _cwait separates: a child that
            # could not be launched at all must be reported as a wrapper error, not
            # forwarded as a child exit status. Leave the internal executable out so
            # the lookup fails. The wrapper is invoked by absolute path, so it does
            # not fall back to searching PATH.
            self.log.info("Ensure bitcoin reports a launch failure instead of a child exit status")
            missing_dir = self.nodes[0].datadir_path / "exit_status_missing"
            missing_dir.mkdir()
            missing_wrapper = missing_dir / "bitcoin.exe"
            shutil.copyfile(self.get_binaries().paths.bitcoin_bin, missing_wrapper)
            result = subprocess.run([str(missing_wrapper), "-M", "node", "-version"],
                                    capture_output=True, timeout=30)
            assert_equal(result.returncode, 1)
            assert b"failed to execute" in result.stderr
    

    I ran this on a windows-2022 runner against the cross-built executables from this branch, with both CRTs, and it passes: the wrapper exits with 1 and reports its own error instead of forwarding a child status.

    I also rebuilt the branch with ExecVp reverted to _spawnvp(_P_WAIT) and ran the test against that, with both CRTs: it fails at AssertionError: not(1 == 4294967295), as described. Worth noting that the launch-failure case is never reached there — the old code did report a failed spawn correctly, so that side genuinely was left uncovered.

    One more thing that fell out of running the above: the message the user gets differs between CRTs.

    MSVCRT: Error: execvp failed to execute '...\bitcoind': Invalid argument
    UCRT:   Error: execvp failed to execute '...\bitcoind': No such file or directory
    

    That is the EINVAL from 68ae4a0f surfacing: the intermediate lookups treat it as "not found" and move on, but the last attempt has allow_notfound=false, so errno goes into the std::system_error as-is, and an MSVCRT build reports "Invalid argument" for a file that simply isn't there. Not a problem with this test — if anything it's an argument for covering the case — and the fix would belong in #36105.

    Environment:

    • Linux: Debian, GCC 12.2.0, Release, BUILD_TESTS=OFF, ENABLE_IPC=OFF, Python 3.11 — tool_bitcoin.py passes; the Windows case is skipped.
    • Windows: windows-2022 runner with the UCRT and MSVCRT cross-built binaries from this branch — tool_bitcoin.py passes on both.
  18. ryanofsky commented at 4:58 PM on September 11, 2026: contributor

    re: #36190 (comment)

    I had trouble understanding the comment at first, but it's just suggesting adding a second test to accompany the existing test in b78e0a9d3e004988c54a5438df0fee16d6abc7f3. The existing test confirms exit codes (0, 1, -1) from bitcoind all get passed through the bitcoin wrapper. But it doesn't confirm that an execvp failed to execute error appears if bitcoind can't be started at all.

    This seems like a nice thing to check. The original bug in 80fc82c4aed020afc0f8e2230886733282f9b755 fixed in 6bce70ad0330467abc38e5ee23f3fc3b4379112b was that the execvp failed to execute error previously trigger incorrectly when bitcoind returned -1, and now it will correctly exit with -1 instead. But it would be good to check that the error still does trigger in cases where it is supposed to trigger.

  19. cyb3ralbert commented at 5:56 AM on September 13, 2026: contributor

    re: #36190 (comment)

    Thanks for restating my points more clearly than my original comment. That's exactly what was needed.

    ACK f2a1beb29873643ad382deb5d1848bd8572f4105

    Ran tool_bitcoin.py against this head on Linux (Debian, GCC 12.2.0) and on a windows-2022 runner with the cross-built binaries, both CRTs. It passes in all three, and the new case is reached in each. @Bortlesboat thanks for catching the copyfile → copy issue — my snippet only ever ran under the Windows branch, so it got away without the executable bit. Checking this on every platform is better too.

  20. test: Enable tool_bitcoin.py and interface_gui.py tests on Windows
    Now that util::ExecVp on Windows uses _spawnvp(_P_NOWAIT) + _cwait
    instead of _execvp, the bitcoin wrapper process blocks until the child
    exits and Python can capture its stdout/stderr and exit code normally.
    Remove the Windows skips added in #33229 and #35551.
    
    The interface_gui.py test is still skipped in vcpkg Qt builds. vcpkg builds Qt
    with -opengl dynamic, making the minimal platform plugin unusable due to
    internal Qt bugs. This matches existing logic in src/qt/test/CMakeLists.txt
    avoiding the minimal platform plugin with test_bitcoin-qt. A comment
    there is also updated for clarity.
    
    Co-Authored-By: Hodlinator <172445034+hodlinator@users.noreply.github.com>
    7fc08852e4
  21. bitcoin: Use generic_category for errno from exec/spawn CRT functions
    Switch from system_category to generic_category when throwing from
    ExecVp failure, so errno is read as a POSIX value. On Windows,
    system_category interprets codes as Win32 errors, so EINVAL=22 would
    produce "The device does not recognize the command" instead of
    "Invalid argument".
    
    This change was written with Claude Sonnet 4.6.
    ad98271f51
  22. ryanofsky approved
  23. ryanofsky commented at 10:40 PM on September 23, 2026: contributor

    Code review ACK f2a1beb29873643ad382deb5d1848bd8572f4105 (last 2 commits only). Both tests are well written and add useful coverage.

  24. ryanofsky marked this as a draft on Sep 23, 2026
  25. ryanofsky commented at 10:41 PM on September 23, 2026: contributor

    Converted this to a draft since it depends on #36105

  26. test: Check bitcoin wrapper child exit status on windows
    This detects a bug that was present in the original implementation of the
    earlier commit "Fix ExecVp on Windows to wait for child process"
    
    Co-Authored-By: Ryan Ofsky <ryan@ofsky.org>
    7395505c2f
  27. test: Check bitcoin wrapper reports launch errors
    Make sure the wrapper still fails with its own error when bitcoind can't
    be started, instead of returning something that looks like a child exit
    status.
    
    Co-authored-by: cyb3ralbert <cyberalbert@protonmail.ch>
    190d2350e0
  28. Bortlesboat force-pushed on Sep 25, 2026

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-01 04:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me