fix: share one language-test mutex; read the OFX marker via Win32 env

i18n: test_lock() and lang_test_lock() used to be TWO different static
mutexes, so the i18n tests and the ~30 app/actions/dialogs tests that
mutate the language global never excluded each other. Windows thread
scheduling exposed the race: tr_falls_back_to_english_then_the_key got
the English value because another test flipped the language mid-assert.
Both entry points now lock the same mutex.

oak_test_plugin.c: on Windows the plugin DLL has its own CRT
environment block, so getenv() never sees what the host's
std::env::set_var set via SetEnvironmentVariableW — the interact
lifecycle test's marker file stayed empty ("lifecycle actions
missing: []"). Read the marker path through GetEnvironmentVariableA
on _WIN32.
This commit is contained in:
2026-08-21 13:36:19 +08:00
parent d9dc16ec26
commit 3efdee5a10
2 changed files with 28 additions and 5 deletions
+22 -2
View File
@@ -35,6 +35,26 @@
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#ifdef _WIN32
#include <windows.h>
#endif
/* Windows 上插件 DLL 带自己的 CRT 环境块:宿主 Rust 侧的
* std::env::set_var 走 SetEnvironmentVariableW,只更新 Win32 进程
* 环境,插件里 getenv 读的 CRT `_environ` 看不到它。marker 路径
* 统一经 Win32 进程环境读取(POSIX 下就是 getenv)。 */
static const char *marker_env(const char *name)
{
#ifdef _WIN32
static char buf[32768];
DWORD n = GetEnvironmentVariableA(name, buf, (DWORD)sizeof buf);
if (n == 0 || n >= sizeof buf)
return NULL;
return buf;
#else
return getenv(name);
#endif
}
#include "ofxCore.h"
#include "ofxColour.h"
@@ -99,7 +119,7 @@ static int g_button_instance_changed = 0;
static void record_button_instance_changed(void)
{
g_button_instance_changed++;
const char *marker = getenv("OAK_TEST_PLUGIN_INSTANCECHANGED_MARKER");
const char *marker = marker_env("OAK_TEST_PLUGIN_INSTANCECHANGED_MARKER");
if (!marker)
return;
FILE *f = fopen(marker, "a");
@@ -593,7 +613,7 @@ static OfxStatus mainEntryID(const char *action, const void *handle,
* (宿主测试断言用;未设环境变量时静默)。 */
static void interact_record(const char *fmt, ...)
{
const char *marker = getenv("OAK_TEST_PLUGIN_INTERACT_MARKER");
const char *marker = marker_env("OAK_TEST_PLUGIN_INTERACT_MARKER");
if (!marker)
return;
FILE *f = fopen(marker, "a");
+6 -3
View File
@@ -425,11 +425,14 @@ mod tests {
/// Serializes tests that mutate the language global / HOME.
#[cfg(test)]
pub(crate) fn test_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
lang_test_lock().lock().unwrap_or_else(|e| e.into_inner())
}
/// Compatibility alias used by the app/actions test modules.
/// Compatibility alias used by the app/actions test modules. Both entry
/// points MUST share the one static mutex: with two separate mutexes the
/// i18n tests and the app/actions tests do not exclude each other, and
/// the language global races (observed as a Windows-only CI failure
/// where `tr` returned the English value mid-test).
#[cfg(test)]
pub(crate) fn lang_test_lock() -> &'static std::sync::Mutex<()> {
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());