From 7a4a2dde45aedb7773a89da1aec744a75a92f888 Mon Sep 17 00:00:00 2001 From: Skorinn <42702903+Skorinn@users.noreply.github.com> Date: Wed, 9 Sep 2026 07:13:16 -0500 Subject: [PATCH 1/2] Let go of the form when a test that built one finishes DeviceList_SetProperty_UpdatesDataSource failed on the release runner with "BindingSource cannot be its own data source", having passed everywhere else, including on the runner for v1.0.0 with the same code. Building a GeneratorForm points the device search at it and queues a search on the thread pool. The search reports back by assigning the device list to whichever form is the parent when it finishes, and the parent is static, so a search started by one test reports into a later test's form. It guards that assignment with InvokeRequired, which is the right guard for a window: a form that was never shown has no handle, and InvokeRequired on a handleless control is false rather than true, so the report is made from the pool thread rather than marshalled onto the one that owns the control. Two threads then write the binding source at once. Thirty-five tests in this file build a form and none of them let go of it. DeviceUpdateThreadTests already clears the parent for exactly this reason and says so; this file now does the same. With no parent, a search that outlives the test that started it finds nothing to report to. Locally the search finishes between tests and nothing collides. The runner is slower at WMI, which is why it showed there and why it comes and goes. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w --- RandomNumberGenerator.Test/GeneratorForm.Test.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/RandomNumberGenerator.Test/GeneratorForm.Test.cs b/RandomNumberGenerator.Test/GeneratorForm.Test.cs index 7273045..ba1f34f 100644 --- a/RandomNumberGenerator.Test/GeneratorForm.Test.cs +++ b/RandomNumberGenerator.Test/GeneratorForm.Test.cs @@ -100,6 +100,16 @@ public TestContext TestContext [TestCleanup] public void Cleanup() { + // Let go of the form this test built, the way DeviceUpdateThreadTests does and for the same + // reason. Building a form points the device search at it and queues a search on the thread pool, + // and that search reports back by assigning the device list to whichever form is the parent when + // it finishes - which is a later test's form, or one that is being written to at that moment. + // The search guards that assignment with InvokeRequired, but a form that was never shown has no + // window handle, and InvokeRequired on a handleless control is false rather than true, so the + // report is made from the pool thread instead of being marshalled. Leaving the parent set is + // what let a search outlive the test that started it. + DeviceUpdateThread.Parent = null; + // Delete test files if they exist to ensure clean state for next test string[] testFiles = { m_sBASELINE_TEST_FILE, From aabee20488c86b92d8dc2d2a0f7952614a8186dc Mon Sep 17 00:00:00 2001 From: Skorinn <42702903+Skorinn@users.noreply.github.com> Date: Wed, 9 Sep 2026 07:22:43 -0500 Subject: [PATCH 2/2] Put the unit tests under tests, beside the manual ones The manual suites went into tests/manual when they were added, which left the unit tests sitting beside the tests directory rather than in it. Both kinds are tests, so both live under tests now. The project moved with git mv, so the history of all nineteen files follows it. What had to change with it: every relative path in the test project goes up one more level, which is fifteen package paths and the reference to the application; the solution entry; and the path the release workflow runs vstest against, which would otherwise have looked for the assembly where it no longer is. The namespace is untouched. It was RandomNumberGenerator.Test before the move and the guidelines call for that regardless of where the folder sits, so the two mentions of that name in the guidelines are about the namespace and are left alone. Verified by rebuilding both configurations from clean and running the whole suite against each - 243 tests, and the manual chart suite against the built application, so the post build copy of the native DLL still lands where the tests look for it. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_0153VVkWg7DQmdaNLcvtY37w --- .github/workflows/release.yml | 2 +- CLAUDE.md | 15 +++++++----- README.md | 7 +++--- RandomNumberGenerator.sln | 2 +- .../CODING_GUIDELINES_TESTS.md | 0 .../DeviceUpdateThread.Test.cs | 0 .../GeneratorForm.Test.cs | 0 .../HistogramChart.Test.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../RNGChart.Test.cs | 0 .../RNGDevice.Test.cs | 0 .../RNGDeviceTimer.Test.cs | 0 .../RNGSessionData.Test.cs | 0 .../RNGSessionDataFile.Test.cs | 0 .../RNGSessionTimer.Test.cs | 0 .../RNGXMLReader.Test.cs | 0 .../RNGXMLWriter.Test.cs | 0 .../RandomNumberGenerator.Test.csproj | 24 +++++++++---------- .../RandomNumberGenerator.sln | 1 + .../SignificanceTest.Test.cs | 0 .../StatisticalAnalysis.Test.cs | 0 .../TargetValues.Test.cs | 0 .../XMLDataPoint.Test.cs | 0 .../packages.config | 0 tests/manual/README.md | 2 +- 25 files changed, 29 insertions(+), 24 deletions(-) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/CODING_GUIDELINES_TESTS.md (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/DeviceUpdateThread.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/GeneratorForm.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/HistogramChart.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/Properties/AssemblyInfo.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGChart.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGDevice.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGDeviceTimer.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGSessionData.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGSessionDataFile.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGSessionTimer.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGXMLReader.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RNGXMLWriter.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/RandomNumberGenerator.Test.csproj (76%) create mode 100644 tests/RandomNumberGenerator.Test/RandomNumberGenerator.sln rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/SignificanceTest.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/StatisticalAnalysis.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/TargetValues.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/XMLDataPoint.Test.cs (100%) rename {RandomNumberGenerator.Test => tests/RandomNumberGenerator.Test}/packages.config (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 434f467..6772790 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -147,7 +147,7 @@ jobs: exit 1 } - & $vstest "RandomNumberGenerator.Test\bin\Release\Random Number Generator.Test.dll" /Logger:"console;verbosity=minimal" + & $vstest "tests\RandomNumberGenerator.Test\bin\Release\Random Number Generator.Test.dll" /Logger:"console;verbosity=minimal" if ($LASTEXITCODE -ne 0) { Write-Host "::error::The tests did not pass, so nothing is released." diff --git a/CLAUDE.md b/CLAUDE.md index 27a9f6e..265c721 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,10 @@ the results. The device access layer is a native C++ DLL (`TruRNGpro`) called th Three projects in `RandomNumberGenerator.sln`: - `RandomNumberGenerator.csproj` — WinForms app, AnyCPU (64-bit; `Prefer32Bit=false`) - `TruRNGpro/TruRNGpro.vcxproj` — native C++ DLL, **x64 only** (solution maps `Any CPU` → `x64`) -- `RandomNumberGenerator.Test/` — MSTest + Moq unit tests +- `tests/RandomNumberGenerator.Test/` — MSTest + Moq unit tests + +Both kinds of test live under `tests/`: the unit tests above, and `tests/manual/`, which drives the built +application and is described in its own README. ## Build and test @@ -28,12 +31,12 @@ nuget restore RandomNumberGenerator.sln & $msbuild RandomNumberGenerator.sln /p:Configuration=Debug /p:Platform="Any CPU" # Run all tests -& $vstest "RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" +& $vstest "tests\RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" # Run a single test / class / category -& $vstest "RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" /Tests:WriteDataPoint_ValidWriter_Success -& $vstest "RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" /TestCaseFilter:"FullyQualifiedName~RNGXMLWriterTests" -& $vstest "RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" /TestCaseFilter:"TestCategory=Component" +& $vstest "tests\RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" /Tests:WriteDataPoint_ValidWriter_Success +& $vstest "tests\RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" /TestCaseFilter:"FullyQualifiedName~RNGXMLWriterTests" +& $vstest "tests\RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" /TestCaseFilter:"TestCategory=Component" ``` Build ordering matters. The native project copies `TruRNGpro.dll` to `\bin\`, and both the app and the @@ -241,7 +244,7 @@ here (`DeviceInterfaces` supplies the `USBDeviceNotification` constants used in ## Coding conventions -`CODING_GUIDELINES.md` (app), `RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md` (tests), and +`CODING_GUIDELINES.md` (app), `tests/RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md` (tests), and `TruRNGpro/CODING_GUIDELINES_CPP.md` (native) are authoritative and are actually followed throughout. The non-obvious rules new code is expected to match: diff --git a/README.md b/README.md index 3e89578..c86af0b 100644 --- a/README.md +++ b/README.md @@ -135,7 +135,8 @@ stopping the recording. |---|---| | `RandomNumberGenerator.csproj` | The application. Windows Forms, .NET Framework 4.8, AnyCPU | | `TruRNGpro/TruRNGpro.vcxproj` | Native DLL holding the device interface and the simulator. x64 | -| `RandomNumberGenerator.Test/` | Unit tests. MSTest and Moq | +| `tests/RandomNumberGenerator.Test/` | Unit tests. MSTest and Moq | +| `tests/manual/` | Suites that drive the built application, some needing the device attached | The application talks to the native DLL through two exported functions, `Initialize` and `GetRandomBitAverage`, which sit behind an interface implemented by both the real device and the simulator. @@ -143,7 +144,7 @@ The application talks to the native DLL through two exported functions, `Initial ## Tests ``` -vstest.console.exe "RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" +vstest.console.exe "tests\RandomNumberGenerator.Test\bin\Debug\Random Number Generator.Test.dll" ``` `vstest.console.exe` is under the Visual Studio installation, in @@ -162,7 +163,7 @@ in place. The coding standards are written down and are followed throughout: - `CODING_GUIDELINES.md` for the application -- `RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md` for the tests +- `tests/RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md` for the tests - `TruRNGpro/CODING_GUIDELINES_CPP.md` for the native code `CLAUDE.md` describes the architecture and the build for anyone, or anything, new to the codebase. diff --git a/RandomNumberGenerator.sln b/RandomNumberGenerator.sln index d5cf5e6..17b103d 100644 --- a/RandomNumberGenerator.sln +++ b/RandomNumberGenerator.sln @@ -10,7 +10,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomNumberGenerator", "Ra EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TruRNGpro", "TruRNGpro\TruRNGpro.vcxproj", "{983F2AF3-825E-4283-B55C-D0401CDA780B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomNumberGenerator.Test", "RandomNumberGenerator.Test\RandomNumberGenerator.Test.csproj", "{0507DC94-C63E-4C0A-8659-B18BC37DDE25}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RandomNumberGenerator.Test", "tests\RandomNumberGenerator.Test\RandomNumberGenerator.Test.csproj", "{0507DC94-C63E-4C0A-8659-B18BC37DDE25}" ProjectSection(ProjectDependencies) = postProject {983F2AF3-825E-4283-B55C-D0401CDA780B} = {983F2AF3-825E-4283-B55C-D0401CDA780B} EndProjectSection diff --git a/RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md b/tests/RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md similarity index 100% rename from RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md rename to tests/RandomNumberGenerator.Test/CODING_GUIDELINES_TESTS.md diff --git a/RandomNumberGenerator.Test/DeviceUpdateThread.Test.cs b/tests/RandomNumberGenerator.Test/DeviceUpdateThread.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/DeviceUpdateThread.Test.cs rename to tests/RandomNumberGenerator.Test/DeviceUpdateThread.Test.cs diff --git a/RandomNumberGenerator.Test/GeneratorForm.Test.cs b/tests/RandomNumberGenerator.Test/GeneratorForm.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/GeneratorForm.Test.cs rename to tests/RandomNumberGenerator.Test/GeneratorForm.Test.cs diff --git a/RandomNumberGenerator.Test/HistogramChart.Test.cs b/tests/RandomNumberGenerator.Test/HistogramChart.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/HistogramChart.Test.cs rename to tests/RandomNumberGenerator.Test/HistogramChart.Test.cs diff --git a/RandomNumberGenerator.Test/Properties/AssemblyInfo.cs b/tests/RandomNumberGenerator.Test/Properties/AssemblyInfo.cs similarity index 100% rename from RandomNumberGenerator.Test/Properties/AssemblyInfo.cs rename to tests/RandomNumberGenerator.Test/Properties/AssemblyInfo.cs diff --git a/RandomNumberGenerator.Test/RNGChart.Test.cs b/tests/RandomNumberGenerator.Test/RNGChart.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGChart.Test.cs rename to tests/RandomNumberGenerator.Test/RNGChart.Test.cs diff --git a/RandomNumberGenerator.Test/RNGDevice.Test.cs b/tests/RandomNumberGenerator.Test/RNGDevice.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGDevice.Test.cs rename to tests/RandomNumberGenerator.Test/RNGDevice.Test.cs diff --git a/RandomNumberGenerator.Test/RNGDeviceTimer.Test.cs b/tests/RandomNumberGenerator.Test/RNGDeviceTimer.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGDeviceTimer.Test.cs rename to tests/RandomNumberGenerator.Test/RNGDeviceTimer.Test.cs diff --git a/RandomNumberGenerator.Test/RNGSessionData.Test.cs b/tests/RandomNumberGenerator.Test/RNGSessionData.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGSessionData.Test.cs rename to tests/RandomNumberGenerator.Test/RNGSessionData.Test.cs diff --git a/RandomNumberGenerator.Test/RNGSessionDataFile.Test.cs b/tests/RandomNumberGenerator.Test/RNGSessionDataFile.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGSessionDataFile.Test.cs rename to tests/RandomNumberGenerator.Test/RNGSessionDataFile.Test.cs diff --git a/RandomNumberGenerator.Test/RNGSessionTimer.Test.cs b/tests/RandomNumberGenerator.Test/RNGSessionTimer.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGSessionTimer.Test.cs rename to tests/RandomNumberGenerator.Test/RNGSessionTimer.Test.cs diff --git a/RandomNumberGenerator.Test/RNGXMLReader.Test.cs b/tests/RandomNumberGenerator.Test/RNGXMLReader.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGXMLReader.Test.cs rename to tests/RandomNumberGenerator.Test/RNGXMLReader.Test.cs diff --git a/RandomNumberGenerator.Test/RNGXMLWriter.Test.cs b/tests/RandomNumberGenerator.Test/RNGXMLWriter.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/RNGXMLWriter.Test.cs rename to tests/RandomNumberGenerator.Test/RNGXMLWriter.Test.cs diff --git a/RandomNumberGenerator.Test/RandomNumberGenerator.Test.csproj b/tests/RandomNumberGenerator.Test/RandomNumberGenerator.Test.csproj similarity index 76% rename from RandomNumberGenerator.Test/RandomNumberGenerator.Test.csproj rename to tests/RandomNumberGenerator.Test/RandomNumberGenerator.Test.csproj index 8faf58b..89000f4 100644 --- a/RandomNumberGenerator.Test/RandomNumberGenerator.Test.csproj +++ b/tests/RandomNumberGenerator.Test/RandomNumberGenerator.Test.csproj @@ -1,6 +1,6 @@ - + Debug @@ -43,29 +43,29 @@ - ..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll + ..\..\packages\Castle.Core.5.1.1\lib\net462\Castle.Core.dll - ..\packages\MathNet.Numerics.5.0.0\lib\net48\MathNet.Numerics.dll + ..\..\packages\MathNet.Numerics.5.0.0\lib\net48\MathNet.Numerics.dll - ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll + ..\..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll - ..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + ..\..\packages\MSTest.TestFramework.2.2.10\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll - ..\packages\Moq.4.20.70\lib\net462\Moq.dll + ..\..\packages\Moq.4.20.70\lib\net462\Moq.dll - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + ..\..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll @@ -94,7 +94,7 @@ - + {58f5fb77-99e5-4212-a46f-af3a18d46b17} RandomNumberGenerator @@ -105,10 +105,10 @@ This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - + + - + xcopy /Y "$(SolutionDir)bin\TruRNGpro.dll" "$(TargetDir)" if errorlevel 1 goto end diff --git a/tests/RandomNumberGenerator.Test/RandomNumberGenerator.sln b/tests/RandomNumberGenerator.Test/RandomNumberGenerator.sln new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/tests/RandomNumberGenerator.Test/RandomNumberGenerator.sln @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/RandomNumberGenerator.Test/SignificanceTest.Test.cs b/tests/RandomNumberGenerator.Test/SignificanceTest.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/SignificanceTest.Test.cs rename to tests/RandomNumberGenerator.Test/SignificanceTest.Test.cs diff --git a/RandomNumberGenerator.Test/StatisticalAnalysis.Test.cs b/tests/RandomNumberGenerator.Test/StatisticalAnalysis.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/StatisticalAnalysis.Test.cs rename to tests/RandomNumberGenerator.Test/StatisticalAnalysis.Test.cs diff --git a/RandomNumberGenerator.Test/TargetValues.Test.cs b/tests/RandomNumberGenerator.Test/TargetValues.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/TargetValues.Test.cs rename to tests/RandomNumberGenerator.Test/TargetValues.Test.cs diff --git a/RandomNumberGenerator.Test/XMLDataPoint.Test.cs b/tests/RandomNumberGenerator.Test/XMLDataPoint.Test.cs similarity index 100% rename from RandomNumberGenerator.Test/XMLDataPoint.Test.cs rename to tests/RandomNumberGenerator.Test/XMLDataPoint.Test.cs diff --git a/RandomNumberGenerator.Test/packages.config b/tests/RandomNumberGenerator.Test/packages.config similarity index 100% rename from RandomNumberGenerator.Test/packages.config rename to tests/RandomNumberGenerator.Test/packages.config diff --git a/tests/manual/README.md b/tests/manual/README.md index ed02f62..7fe0fe4 100644 --- a/tests/manual/README.md +++ b/tests/manual/README.md @@ -1,6 +1,6 @@ # Manual test suites -These drive the built application rather than its classes. The unit tests in `RandomNumberGenerator.Test` +These drive the built application rather than its classes. The unit tests in `tests/RandomNumberGenerator.Test` cover the layers underneath the window; nothing in them builds the form's event wiring, and nothing in them can reach the device at all. Everything here exists because a fault got past a clean unit run.