Files
clice/tests/unit/server/worker_test_helpers.h
ykiko bc04845293 refactor(tests): CMake-based CDB, workspace fixture, test cleanup (#378)
## Summary

- **CMake-based CDB generation for module tests**: Replace hand-written
compile_commands.json with CMakeLists.txt (CMake 3.28 `FILE_SET
CXX_MODULES`) in all 26 `tests/data/modules/*/` directories. CDB is
generated on-the-fly via `cmake -G Ninja` during test setup.
- **`@pytest.mark.workspace()` decorator**: Introduce a marker + fixture
pattern so tests declare their workspace via decorator and receive a
resolved `workspace` path. The fixture auto-generates CDB when a
CMakeLists.txt is present.
- **`CliceClient` helper methods**: Add `initialize()`, `open()`,
`wait_diagnostics()`, and `open_and_wait()` to reduce boilerplate across
all test files.
- **Use `asyncio_mode = "auto"`**: Switch from `@pytest_asyncio.fixture`
+ `@pytest.mark.asyncio` to `@pytest.fixture` + auto mode for proper
Pylance type inference on fixtures.
- **Test cleanup**: Remove redundant section separators and docstrings,
delete `tests/pyproject.toml` (config moved to `pytest.ini`).
- **Format task**: Add `.cppm` to `format-cpp` glob pattern.
- **CI fix**: Disable `CMAKE_CXX_SCAN_FOR_MODULES` and prefer pixi
clang++ to fix macOS CI where CMake rejects module scanning.

## Test plan

- [x] All 26 module test directories have CMakeLists.txt with FILE_SET
CXX_MODULES
- [x] generate_cdb() produces valid compile_commands.json with module
flags
- [x] Integration tests pass locally
- [ ] CI passes on all platforms (Linux, macOS, Windows)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Unified fixtures and client workflow: new init/open/wait helpers,
workspace marker support, bounded diagnostics waiting, CMake-based
compilation-database generation, and directory-backed temp-file
workflows; enabled asyncio test mode.
* **Chores**
* Added many C++20 module test projects and test data; removed prior
test pyproject in favor of pytest config; updated formatter to include
.cppm files.
* **Style**
* Reformatted many module/source implementations to consistent
multi-line function bodies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 16:57:48 +08:00

123 lines
3.6 KiB
C++

#pragma once
#include <csignal>
#include <string>
#ifndef _WIN32
#include <fcntl.h>
#include <unistd.h>
#endif
#include "test/temp_dir.h"
#include "command/argument_parser.h"
#include "command/command.h"
#include "eventide/async/async.h"
#include "eventide/ipc/peer.h"
#include "eventide/ipc/transport.h"
#include "server/protocol.h"
#include "support/filesystem.h"
namespace clice::testing {
namespace {
/// Ignore SIGPIPE so broken pipes from exited workers don't kill the test binary.
struct SigpipeGuard {
SigpipeGuard() {
#ifndef _WIN32
std::signal(SIGPIPE, SIG_IGN);
#endif
}
};
static SigpipeGuard sigpipe_guard;
namespace et = eventide;
/// Resolve path to the clice binary for spawning workers.
inline std::string clice_binary() {
auto res_dir = resource_dir();
// res_dir is <build>/lib/clang/...
// clice binary is at <build>/bin/clice
auto build_dir = llvm::sys::path::parent_path(
llvm::sys::path::parent_path(llvm::sys::path::parent_path(res_dir)));
llvm::SmallString<256> path(build_dir);
llvm::sys::path::append(path, "bin", "clice");
return std::string(path);
}
/// Build compile arguments for a source file, including -resource-dir.
inline std::vector<std::string> make_args(const std::string& file_path,
const std::string& extra = "") {
std::vector<std::string> args =
{"clang++", "-fsyntax-only", "-resource-dir", std::string(resource_dir()), "-c", file_path};
if(!extra.empty()) {
args.insert(args.begin() + 1, extra);
}
return args;
}
/// Helper: spawn a worker process and return a BincodePeer connected to it.
struct WorkerHandle {
et::event_loop loop;
et::process proc{};
std::unique_ptr<et::ipc::StreamTransport> transport;
std::unique_ptr<et::ipc::BincodePeer> peer;
int stderr_fd = -1;
bool spawn(const std::string& mode, std::uint64_t memory_limit = 0) {
auto binary = clice_binary();
#ifndef _WIN32
// Redirect worker stderr to a temp file for debugging.
std::string stderr_path = "/tmp/clice_worker_stderr_" + mode + ".log";
stderr_fd = ::open(stderr_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
#endif
et::process::options opts;
opts.file = binary;
opts.args = {binary, "--mode", mode};
if(memory_limit > 0) {
opts.args.push_back("--worker-memory-limit");
opts.args.push_back(std::to_string(memory_limit));
}
opts.streams = {
et::process::stdio::pipe(true, false), // stdin: child reads
et::process::stdio::pipe(false, true), // stdout: child writes
stderr_fd >= 0 ? et::process::stdio::from_fd(stderr_fd) : et::process::stdio::ignore(),
};
auto result = et::process::spawn(opts, loop);
if(!result) {
#ifndef _WIN32
if(stderr_fd >= 0)
::close(stderr_fd);
#endif
return false;
}
auto& spawn = *result;
transport = std::make_unique<et::ipc::StreamTransport>(std::move(spawn.stdout_pipe),
std::move(spawn.stdin_pipe));
peer = std::make_unique<et::ipc::BincodePeer>(loop, std::move(transport));
proc = std::move(spawn.proc);
#ifndef _WIN32
if(stderr_fd >= 0)
::close(stderr_fd);
#endif
return true;
}
/// Run a coroutine on the event loop and return when it completes.
template <typename F>
void run(F&& coro_factory) {
loop.schedule(peer->run());
loop.schedule(coro_factory());
loop.run();
}
};
} // namespace
} // namespace clice::testing