From 9a3ce26650fedc8bd1b14f4ca4be3833565de3d9 Mon Sep 17 00:00:00 2001 From: guo-feng-rui Date: Mon, 20 Jul 2026 03:11:21 +0800 Subject: [PATCH] fix(tests): make unit tests build on Windows --- tests/debug_dump_unit.cpp | 30 ++++++++++++++++---- tests/whisper_bin_parser_unit.cpp | 47 +++++++++++++++++++++---------- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/tests/debug_dump_unit.cpp b/tests/debug_dump_unit.cpp index 02e6d332..1e443943 100644 --- a/tests/debug_dump_unit.cpp +++ b/tests/debug_dump_unit.cpp @@ -30,7 +30,11 @@ #include "ggml.h" #include "transcribe-debug.h" -#include // ::getpid() +#ifdef _WIN32 +# include // ::_getpid() +#else +# include // ::getpid() +#endif #include #include @@ -70,7 +74,11 @@ int g_failures = 0; // scoped to this test process. Caller deletes it on success. fs::path make_unique_dump_dir() { fs::path base = fs::temp_directory_path() / "transcribe-debug-dump-test"; +#ifdef _WIN32 + base /= std::to_string(::_getpid()); +#else base /= std::to_string(::getpid()); +#endif fs::remove_all(base); // ensure clean state in case of leftover fs::create_directories(base); return base; @@ -106,6 +114,16 @@ bool contains(const std::string & haystack, const std::string & needle) { return haystack.find(needle) != std::string::npos; } +// Portable setenv(name, value, 1). fs::path::c_str() is wchar_t* on +// Windows, so callers pass a narrow std::string instead. +bool set_env(const char * name, const std::string & value) { +#ifdef _WIN32 + return ::_putenv_s(name, value.c_str()) == 0; +#else + return ::setenv(name, value.c_str(), 1) == 0; +#endif +} + } // namespace int main() { @@ -114,8 +132,9 @@ int main() { // Create a unique dump dir, point TRANSCRIBE_DUMP_DIR at it, and // initialize the dumper. setenv before init is the contract: // init() reads the env var once and caches it. - const fs::path dump_dir = make_unique_dump_dir(); - if (::setenv("TRANSCRIBE_DUMP_DIR", dump_dir.c_str(), 1) != 0) { + const fs::path dump_dir = make_unique_dump_dir(); + const std::string dump_dir_str = dump_dir.string(); + if (!set_env("TRANSCRIBE_DUMP_DIR", dump_dir_str)) { std::fprintf(stderr, "FAIL: setenv failed\n"); return EXIT_FAILURE; } @@ -127,9 +146,10 @@ int main() { return EXIT_FAILURE; } CHECK(transcribe::debug::enabled()); - if (transcribe::debug::dump_dir() == nullptr || std::strcmp(transcribe::debug::dump_dir(), dump_dir.c_str()) != 0) { + if (transcribe::debug::dump_dir() == nullptr || + std::strcmp(transcribe::debug::dump_dir(), dump_dir_str.c_str()) != 0) { std::fprintf(stderr, "FAIL: dump_dir() = \"%s\", expected \"%s\"\n", - transcribe::debug::dump_dir() ? transcribe::debug::dump_dir() : "(null)", dump_dir.c_str()); + transcribe::debug::dump_dir() ? transcribe::debug::dump_dir() : "(null)", dump_dir_str.c_str()); ++g_failures; } diff --git a/tests/whisper_bin_parser_unit.cpp b/tests/whisper_bin_parser_unit.cpp index 1e8e008c..e8507d76 100644 --- a/tests/whisper_bin_parser_unit.cpp +++ b/tests/whisper_bin_parser_unit.cpp @@ -36,13 +36,17 @@ #include "transcribe-bin-loader.h" #include -#include +#ifndef _WIN32 +# include +#endif #include #include #include #include +#include #include +#include #include #include @@ -186,29 +190,45 @@ bool write_synthetic_bin(const std::string & path, return static_cast(f); } +// Portable stand-in for mkstemps(): create a unique empty file named +// transcribe_bin_test_.bin in the system temp dir. Returns the +// path, or an empty string on failure. Collision-then-overwrite is +// acceptable for these synthetic-fixture tests. +std::string make_temp_bin_path() { + std::error_code ec; + const std::filesystem::path dir = std::filesystem::temp_directory_path(ec); + if (ec) { + return {}; + } + std::random_device rd; + const std::filesystem::path p = dir / ("transcribe_bin_test_" + std::to_string(rd()) + ".bin"); + std::ofstream f(p, std::ios::binary); + if (!f) { + return {}; + } + return p.string(); +} + void test_bad_n_fft() { - char tmpl[] = "/tmp/transcribe_bin_test_XXXXXX.bin"; - const int fd = ::mkstemps(tmpl, 4); - if (fd < 0) { + const std::string path = make_temp_bin_path(); + if (path.empty()) { std::fprintf(stderr, "SKIP: could not create tempfile for synthetic bin\n"); ++g_skipped; return; } - ::close(fd); - const std::string path = tmpl; // Whisper-shaped hparams but non-canonical n_fft (200 instead of // 201). Parser must reject before we even get to the vocab phase. if (!write_synthetic_bin(path, 51865, 4, 4, 80, 80, 200)) { std::fprintf(stderr, "SKIP: failed to write synthetic .bin\n"); ++g_skipped; - ::unlink(path.c_str()); + std::remove(path.c_str()); return; } transcribe::bin_loader::WhisperBinModel m; const auto rc = transcribe::bin_loader::parse_whisper_bin(path.c_str(), m); CHECK(rc == TRANSCRIBE_ERR_GGUF); - ::unlink(path.c_str()); + std::remove(path.c_str()); } void test_distil_layer_count_accepted() { @@ -216,18 +236,15 @@ void test_distil_layer_count_accepted() { // whisper-shaped hparams should pass the hparams gate. We don't // care that the parser later fails at "no tensors" — the hparams // check should not be the gating step. - char tmpl[] = "/tmp/transcribe_bin_test_XXXXXX.bin"; - const int fd = ::mkstemps(tmpl, 4); - if (fd < 0) { + const std::string path = make_temp_bin_path(); + if (path.empty()) { ++g_skipped; return; } - ::close(fd); - const std::string path = tmpl; if (!write_synthetic_bin(path, 51865, 24, 2, 80, 80, 201)) { ++g_skipped; - ::unlink(path.c_str()); + std::remove(path.c_str()); return; } transcribe::bin_loader::WhisperBinModel m; @@ -237,7 +254,7 @@ void test_distil_layer_count_accepted() { // "no tensors" / truncated diagnostic), NOT UNSUPPORTED_ARCH — // that's the proof that the geometry gate accepts distil layers. CHECK(rc == TRANSCRIBE_ERR_GGUF); - ::unlink(path.c_str()); + std::remove(path.c_str()); } } // namespace