test(windows): build and run the test suite for Windows from WSL
scripts/test-windows.sh cross-compiles ObsidianDragonTests.exe (and the fake_dragonxd stub) with mingw into build/windows-tests, reusing the toolchain and dependency sources from build/windows, and runs it from WSL through Windows interop (or wine). All tests pass on Windows. CMake: - Windows forced BUILD_TESTING OFF in the cache to keep curl's tests out, which also removed ObsidianDragonTests from every Windows build. Hide it from curl with a scoped variable instead, and restore our cache value afterwards (curl's cmake_dependent_option FORCE-writes it). Windows still defaults to OFF, so release builds are unchanged. - ObsidianDragonTests now depends on copy_langs: i18n.cpp includes the generated embedded/lang_*.h, so a tree that builds only the tests failed (latent on Linux too). - Link the Windows system libraries the tests need. Tests: - testTmpPath/fixtureDir/removeTree helpers: no hard-coded /tmp, a runtime fixture-dir override (the compiled-in path is a Linux one), and best-effort cleanup that can't abort the suite. - Close the explorer cache before deleting its directory (Windows can't delete an open file). - Compile out the address-book and wallet-index round-trips on Windows: getConfigDir() uses SHGetFolderPath there, which no environment variable redirects, so they would overwrite the real %APPDATA%\ObsidianDragon files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+54
-18
@@ -126,6 +126,29 @@ fs::path makeTempDir()
|
||||
return dir;
|
||||
}
|
||||
|
||||
// Best-effort cleanup. Windows can't delete a file that is still open, and the throwing remove_all
|
||||
// would abort the whole suite over a leftover temp file.
|
||||
void removeTree(const fs::path& path)
|
||||
{
|
||||
std::error_code ec;
|
||||
fs::remove_all(path, ec);
|
||||
}
|
||||
|
||||
// A named path under the platform temp dir (/tmp on Linux, %TEMP% on Windows, where "/tmp/..."
|
||||
// would mean C:\tmp).
|
||||
fs::path testTmpPath(const std::string& name)
|
||||
{
|
||||
return fs::temp_directory_path() / name;
|
||||
}
|
||||
|
||||
// The fixture root: compiled in, unless DRAGONX_TEST_FIXTURE_DIR is set at runtime. The Windows
|
||||
// build runs from WSL (scripts/test-windows.sh), where the compiled-in Linux path doesn't resolve.
|
||||
std::string fixtureDir()
|
||||
{
|
||||
const char* env = std::getenv("DRAGONX_TEST_FIXTURE_DIR");
|
||||
return (env && *env) ? std::string(env) : std::string(DRAGONX_TEST_FIXTURE_DIR);
|
||||
}
|
||||
|
||||
void writeTestFile(const fs::path& path, const std::string& content)
|
||||
{
|
||||
std::ofstream output(path, std::ios::binary);
|
||||
@@ -723,7 +746,7 @@ void testConnectionConfig()
|
||||
EXPECT_EQ(cookieConfig.auth_source, AuthSource::Cookie);
|
||||
EXPECT_TRUE(cookieConfig.use_tls);
|
||||
|
||||
fs::remove_all(dir);
|
||||
removeTree(dir);
|
||||
}
|
||||
|
||||
void testPaymentUri()
|
||||
@@ -763,7 +786,7 @@ void testWalletFileProbe()
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
using dragonx::util::probeWalletFile;
|
||||
const fs::path dir = fs::path("/tmp/dragonx") / "probe";
|
||||
const fs::path dir = testTmpPath("dragonx") / "probe";
|
||||
std::error_code ec; fs::create_directories(dir, ec);
|
||||
auto writef = [](const fs::path& p, const std::string& d) {
|
||||
std::ofstream o(p, std::ios::binary); o.write(d.data(), (std::streamsize)d.size());
|
||||
@@ -3292,7 +3315,7 @@ void testDaemonLifecycleAdapters()
|
||||
EXPECT_FALSE(fs::exists(dataDir / "peers.dat"));
|
||||
EXPECT_FALSE(fs::exists(dataDir / "fee_estimates.dat"));
|
||||
EXPECT_TRUE(fs::exists(dataDir / "wallet.dat"));
|
||||
fs::remove_all(dataDir);
|
||||
removeTree(dataDir);
|
||||
}
|
||||
|
||||
// Pure console wrap layout / hit-test / selection (console_text_layout.h), driven by a
|
||||
@@ -4189,7 +4212,8 @@ void testExplorerBlockCache()
|
||||
file << legacy.dump(2);
|
||||
}
|
||||
|
||||
ExplorerBlockCache cache(databasePath.string(), legacyPath.string());
|
||||
auto cacheOwner = std::make_unique<ExplorerBlockCache>(databasePath.string(), legacyPath.string());
|
||||
ExplorerBlockCache& cache = *cacheOwner;
|
||||
EXPECT_TRUE(cache.ensureOpen());
|
||||
EXPECT_EQ(cache.cachedBlockCount(), 2);
|
||||
|
||||
@@ -4234,7 +4258,8 @@ void testExplorerBlockCache()
|
||||
EXPECT_EQ(reopened.cachedBlockCount(), 0);
|
||||
}
|
||||
|
||||
fs::remove_all(dir);
|
||||
cacheOwner.reset(); // close the database first: Windows can't delete an open file
|
||||
removeTree(dir);
|
||||
}
|
||||
|
||||
void testTransactionHistoryCache()
|
||||
@@ -4345,7 +4370,7 @@ void testTransactionHistoryCache()
|
||||
EXPECT_EQ(cache.snapshotCount(), 0);
|
||||
}
|
||||
|
||||
fs::remove_all(dir);
|
||||
removeTree(dir);
|
||||
}
|
||||
|
||||
// GC: generating a new address changes the wallet-identity hash; the prior hash's snapshot
|
||||
@@ -4377,7 +4402,7 @@ void testTransactionHistoryCachePrunesOldWallets()
|
||||
auto loadedB = cache.load(idB, 100, "tip-b");
|
||||
EXPECT_TRUE(loadedB.loaded);
|
||||
|
||||
fs::remove_all(dir);
|
||||
removeTree(dir);
|
||||
}
|
||||
|
||||
void testTransactionHistoryCacheRefreshApply()
|
||||
@@ -4443,7 +4468,7 @@ void testTransactionHistoryCacheRefreshApply()
|
||||
EXPECT_EQ(lastTxBlock, 333);
|
||||
EXPECT_EQ(confirmedIds.count("rpc-refreshed-send"), static_cast<size_t>(1));
|
||||
|
||||
fs::remove_all(dir);
|
||||
removeTree(dir);
|
||||
}
|
||||
|
||||
|
||||
@@ -4753,7 +4778,7 @@ void testGeneratedResourceBehavior()
|
||||
EXPECT_TRUE(dragonx::resources::getEmbeddedResource("__missing_resource__") == nullptr);
|
||||
if (!dragonx::resources::hasEmbeddedResources()) {
|
||||
EXPECT_TRUE(themes[0].data == nullptr);
|
||||
EXPECT_EQ(dragonx::resources::extractBundledThemes("/tmp/obsidian-dragon-empty-themes"), 0);
|
||||
EXPECT_EQ(dragonx::resources::extractBundledThemes(testTmpPath("obsidian-dragon-empty-themes").string()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6030,7 +6055,7 @@ void testLiteRolloutManifestLoader()
|
||||
{
|
||||
using namespace dragonx::wallet;
|
||||
// Missing file -> not present -> fail-open allowed.
|
||||
auto missing = loadLiteRolloutManifestFromFile("/tmp/obsidian-rollout-missing-xyz.json");
|
||||
auto missing = loadLiteRolloutManifestFromFile(testTmpPath("obsidian-rollout-missing-xyz.json").string());
|
||||
EXPECT_FALSE(missing.present);
|
||||
{
|
||||
LiteRolloutInputs in; in.appVersion = "1.2.0"; in.manifest = missing;
|
||||
@@ -6038,7 +6063,7 @@ void testLiteRolloutManifestLoader()
|
||||
}
|
||||
|
||||
// A valid manifest file parses into the expected fields.
|
||||
const std::string path = "/tmp/obsidian-rollout-test.json";
|
||||
const std::string path = testTmpPath("obsidian-rollout-test.json").string();
|
||||
{
|
||||
std::ofstream f(path);
|
||||
f << R"({"global_enabled":false,"min_version":"1.0.0","rollout_permille":250,"message":"hold"})";
|
||||
@@ -6053,7 +6078,7 @@ void testLiteRolloutManifestLoader()
|
||||
std::remove(path.c_str());
|
||||
|
||||
// Malformed JSON -> present but invalid -> fail-open allowed.
|
||||
const std::string badPath = "/tmp/obsidian-rollout-bad.json";
|
||||
const std::string badPath = testTmpPath("obsidian-rollout-bad.json").string();
|
||||
{ std::ofstream f(badPath); f << "{ this is not json"; }
|
||||
auto bad = loadLiteRolloutManifestFromFile(badPath);
|
||||
EXPECT_TRUE(bad.present);
|
||||
@@ -6098,7 +6123,7 @@ void testLiteWalletControllerRolloutGate()
|
||||
// ── xmrig updater pure core (util/xmrig_updater.h) — driven by the real release fixture ──
|
||||
static std::string readXmrigFixture()
|
||||
{
|
||||
const std::string path = std::string(DRAGONX_TEST_FIXTURE_DIR) + "/xmrig/release_latest.json";
|
||||
const std::string path = fixtureDir() + "/xmrig/release_latest.json";
|
||||
std::ifstream f(path, std::ios::binary);
|
||||
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
}
|
||||
@@ -6241,7 +6266,7 @@ void testAtomicFileWrite()
|
||||
using dragonx::util::Platform;
|
||||
|
||||
fs::path dir = fs::temp_directory_path() / "obsidian_atomic_test";
|
||||
fs::remove_all(dir);
|
||||
removeTree(dir);
|
||||
const std::string target = (dir / "nested" / "settings.json").string();
|
||||
|
||||
// Creates parent dirs and writes content.
|
||||
@@ -6270,7 +6295,7 @@ void testAtomicFileWrite()
|
||||
EXPECT_TRUE((perms & (fs::perms::group_all | fs::perms::others_all)) == fs::perms::none);
|
||||
#endif
|
||||
|
||||
fs::remove_all(dir);
|
||||
removeTree(dir);
|
||||
}
|
||||
|
||||
// Address checksum validation. Uses standard Base58Check / BIP173 Bech32 vectors — the
|
||||
@@ -6562,7 +6587,7 @@ void testXmrigSignatureVerify()
|
||||
// Reads any fixture file by repo-relative path under tests/fixtures/.
|
||||
static std::string readFixtureFile(const std::string& rel)
|
||||
{
|
||||
std::ifstream f(std::string(DRAGONX_TEST_FIXTURE_DIR) + "/" + rel, std::ios::binary);
|
||||
std::ifstream f(fixtureDir() + "/" + rel, std::ios::binary);
|
||||
return std::string((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
@@ -6761,7 +6786,7 @@ void testXmrigLiveInstall()
|
||||
return;
|
||||
}
|
||||
using namespace dragonx::util;
|
||||
const std::string dir = "/tmp/obsidian-xmrig-live-test";
|
||||
const std::string dir = testTmpPath("obsidian-xmrig-live-test").string();
|
||||
std::error_code ec; std::filesystem::remove_all(dir, ec);
|
||||
|
||||
XmrigUpdater up;
|
||||
@@ -7577,6 +7602,11 @@ void testAddressBookScope()
|
||||
EXPECT_TRUE(legacy.visibleInWallet("anything"));
|
||||
|
||||
// --- Round-trip + migration + scope-aware dedup, isolated under a temp $HOME ---
|
||||
#ifdef _WIN32
|
||||
// getConfigDir() uses SHGetFolderPath(CSIDL_APPDATA), which no env var redirects: this would
|
||||
// overwrite the real %APPDATA%\ObsidianDragon\addressbook.json.
|
||||
std::printf("[skip] testAddressBookScope round-trip (Windows config dir can't be redirected)\n");
|
||||
#else
|
||||
const char* oldHome = std::getenv("HOME");
|
||||
std::string savedHome = oldHome ? oldHome : "";
|
||||
fs::path tmp = fs::temp_directory_path() /
|
||||
@@ -7629,6 +7659,7 @@ void testAddressBookScope()
|
||||
|
||||
if (!savedHome.empty()) setenv("HOME", savedHome.c_str(), 1); else unsetenv("HOME");
|
||||
std::error_code ec; fs::remove_all(tmp, ec);
|
||||
#endif // !_WIN32
|
||||
}
|
||||
|
||||
// Wallet-files metadata index (multi-wallet P1b): upsert change-detection, find, extra folders,
|
||||
@@ -7658,7 +7689,11 @@ void testWalletIndex()
|
||||
EXPECT_TRUE(!idx.removeExtraFolder("/some/dir")); // already gone
|
||||
}
|
||||
|
||||
// save + reload round-trip under a temp $HOME
|
||||
// save + reload round-trip under a temp $HOME. Not on Windows: the config dir comes from
|
||||
// SHGetFolderPath there, so this would overwrite the real %APPDATA%\ObsidianDragon\wallets.json.
|
||||
#ifdef _WIN32
|
||||
std::printf("[skip] testWalletIndex round-trip (Windows config dir can't be redirected)\n");
|
||||
#else
|
||||
{
|
||||
const char* oldHome = std::getenv("HOME");
|
||||
std::string savedHome = oldHome ? oldHome : "";
|
||||
@@ -7693,6 +7728,7 @@ void testWalletIndex()
|
||||
if (!savedHome.empty()) setenv("HOME", savedHome.c_str(), 1); else unsetenv("HOME");
|
||||
std::error_code ec2; fs::remove_all(tmp, ec2);
|
||||
}
|
||||
#endif // !_WIN32
|
||||
}
|
||||
|
||||
int main()
|
||||
|
||||
Reference in New Issue
Block a user