From 3b954ba1b4798828fb6f39cca6f9b0a90bdaf4a1 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Wed, 1 Jul 2026 09:48:20 -0400 Subject: [PATCH 1/3] Fix RealDataTestsBase cross-worktree test collisions --- .../RootSiteTests/RealDataTestsBase.cs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs b/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs index 8475864706..c49b327794 100644 --- a/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs +++ b/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Security.Cryptography; +using System.Text; using System.Threading; using NUnit.Framework; using SIL.FieldWorks.Common.FwUtils; @@ -20,11 +22,22 @@ namespace SIL.FieldWorks.Common.RootSites.RootSiteTests [TestFixture] public abstract class RealDataTestsBase { - private const string ReusableProjectName = "integration_test_data"; - private const string ProjectMutexName = - @"Local\FieldWorks.RealDataTests.integration_test_data"; + // The project name must be unique per worktree, so that multiple worktrees of the same repo + // can run tests in parallel without colliding on the same project directory. + private static readonly string ReusableProjectName = "integration_test_data_" + WorktreeSuffix(); + private static readonly string ProjectMutexName = + @"Local\FieldWorks.RealDataTests." + ReusableProjectName; private const string TestProjectSentinelFileName = ".fieldworks-real-data-test-project"; + private static string WorktreeSuffix() + { + using (var sha = SHA1.Create()) + { + var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(FwDirectoryFinder.SourceDirectory)); + return BitConverter.ToString(hash, 0, 4).Replace("-", string.Empty).ToLowerInvariant(); + } + } + protected FwNewLangProjectModel m_model; protected LcmCache Cache; protected string m_dbName; @@ -138,7 +151,11 @@ public virtual void TestTearDown() protected string DbDirectory(string name) { - return Path.Combine(FwDirectoryFinder.ProjectsDirectory, name); + // Deliberately NOT FwDirectoryFinder.ProjectsDirectory - allow for multiple worktrees + // of the same repo to run tests in parallel without colliding on the same project directory. + var worktreeProjectsDirectory = Path.Combine( + Path.GetDirectoryName(FwDirectoryFinder.SourceDirectory), "DistFiles", "Projects"); + return Path.Combine(worktreeProjectsDirectory, name); } private void AcquireProjectMutex() From 0b558be6b3638aa5c58748a93e148b1fb27f30dd Mon Sep 17 00:00:00 2001 From: John Lambert Date: Tue, 7 Jul 2026 13:26:11 -0400 Subject: [PATCH 2/3] Fix DbDirectory() to match where CreateNewLangProj creates projects CreateNewLangProj creates projects under FwDirectoryFinder.ProjectsDirectory (via ILcmDirectories) and can't be redirected to a worktree-local path. DbDirectory()'s worktree-local repoint therefore diverged from EnsureSafeProjectDirectory's expectation, so the pre-create cleanup step checked the wrong location and could miss orphaned data left by a crashed prior run. Cross-worktree collision avoidance is already handled by ReusableProjectName's worktree-hash suffix, which gives each worktree a distinct subdirectory name (and mutex name) under the same shared ProjectsDirectory. Co-Authored-By: Claude Sonnet 5 --- .../RootSite/RootSiteTests/RealDataTestsBase.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs b/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs index c49b327794..3b2a4498c1 100644 --- a/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs +++ b/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs @@ -151,11 +151,13 @@ public virtual void TestTearDown() protected string DbDirectory(string name) { - // Deliberately NOT FwDirectoryFinder.ProjectsDirectory - allow for multiple worktrees - // of the same repo to run tests in parallel without colliding on the same project directory. - var worktreeProjectsDirectory = Path.Combine( - Path.GetDirectoryName(FwDirectoryFinder.SourceDirectory), "DistFiles", "Projects"); - return Path.Combine(worktreeProjectsDirectory, name); + // FwDirectoryFinder.ProjectsDirectory is a real, machine-wide location - CreateNewLangProj + // (via ILcmDirectories.ProjectsDirectory) always creates the project there and can't be + // redirected to a worktree-local path, so this must agree with EnsureSafeProjectDirectory's + // expectation below. Cross-worktree collision avoidance comes from ReusableProjectName's + // worktree-hash suffix instead: different worktrees get different subdirectory names (and + // different mutex names) under this same shared directory. + return Path.Combine(FwDirectoryFinder.ProjectsDirectory, name); } private void AcquireProjectMutex() From 4ad271e9dc92810d4e3cfaebbeedded045a9a0cc Mon Sep 17 00:00:00 2001 From: John Lambert Date: Tue, 7 Jul 2026 13:59:47 -0400 Subject: [PATCH 3/3] Trim DbDirectory() comment to match repo comment convention Co-Authored-By: Claude Sonnet 5 --- Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs b/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs index 3b2a4498c1..f5717a0f42 100644 --- a/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs +++ b/Src/Common/RootSite/RootSiteTests/RealDataTestsBase.cs @@ -151,12 +151,8 @@ public virtual void TestTearDown() protected string DbDirectory(string name) { - // FwDirectoryFinder.ProjectsDirectory is a real, machine-wide location - CreateNewLangProj - // (via ILcmDirectories.ProjectsDirectory) always creates the project there and can't be - // redirected to a worktree-local path, so this must agree with EnsureSafeProjectDirectory's - // expectation below. Cross-worktree collision avoidance comes from ReusableProjectName's - // worktree-hash suffix instead: different worktrees get different subdirectory names (and - // different mutex names) under this same shared directory. + // CreateNewLangProj always creates here and can't be redirected; collision avoidance + // across worktrees comes from ReusableProjectName's worktree-hash suffix instead. return Path.Combine(FwDirectoryFinder.ProjectsDirectory, name); }