test(secrets): serialize env-var-mutating test classes into one xunit collection

Environment.SetEnvironmentVariable/GetEnvironmentVariable mutate the
process-wide environ; concurrent setenv/getenv across parallel xunit test
classes is a documented crash/flake source on glibc even with unique
variable names. Group the six classes that touch process env vars into a
DisableParallelization=true collection so they run serially against each
other while everything else keeps running in parallel.
This commit is contained in:
Joseph Doherty
2026-07-19 10:27:09 -04:00
parent 04018aab6e
commit 1b65e820fb
7 changed files with 16 additions and 0 deletions
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.Secrets.Tests.Cli.Interactive;
/// KEK-upgrade prompt, and the error-containment panel. The shell is the only UI-owning class, so these /// KEK-upgrade prompt, and the error-containment panel. The shell is the only UI-owning class, so these
/// tests assert on rendered <see cref="TestConsole.Output"/> and on what a fake flow observed. /// tests assert on rendered <see cref="TestConsole.Output"/> and on what a fake flow observed.
/// </summary> /// </summary>
[Collection("Process environment")]
public sealed class InteractiveShellTests : IDisposable public sealed class InteractiveShellTests : IDisposable
{ {
private readonly string _dir = private readonly string _dir =
@@ -11,6 +11,7 @@ namespace ZB.MOM.WW.Secrets.Tests.Cli.Interactive;
/// <see cref="IMasterKeyProvider.KekId"/> derived identically to the configured providers so a key /// <see cref="IMasterKeyProvider.KekId"/> derived identically to the configured providers so a key
/// entered by hand can decrypt (and re-wrap) rows another provider kind sealed. /// entered by hand can decrypt (and re-wrap) rows another provider kind sealed.
/// </summary> /// </summary>
[Collection("Process environment")]
public sealed class LiteralMasterKeyProviderTests public sealed class LiteralMasterKeyProviderTests
{ {
private static string NewKeyBase64() private static string NewKeyBase64()
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.Secrets.Tests.Cli.Interactive;
/// (present/decryptable → Ok, absent → Missing, tombstoned → Tombstoned, wrong-KEK → Undecryptable), /// (present/decryptable → Ok, absent → Missing, tombstoned → Tombstoned, wrong-KEK → Undecryptable),
/// and the degraded-session presence-only downgrade (present → PresentUnverified). /// and the degraded-session presence-only downgrade (present → PresentUnverified).
/// </summary> /// </summary>
[Collection("Process environment")]
public sealed class ReferenceAuditorTests : IDisposable public sealed class ReferenceAuditorTests : IDisposable
{ {
private readonly string _dir = private readonly string _dir =
@@ -13,6 +13,7 @@ namespace ZB.MOM.WW.Secrets.Tests.Cli.Interactive;
/// (KEK unresolvable → degraded session that still lists metadata), the operator-override upgrade, and /// (KEK unresolvable → degraded session that still lists metadata), the operator-override upgrade, and
/// the SQL-Server-with-unresolved-secret-ref fall-back to the local store. /// the SQL-Server-with-unresolved-secret-ref fall-back to the local store.
/// </summary> /// </summary>
[Collection("Process environment")]
public sealed class SecretsSessionFactoryTests : IDisposable public sealed class SecretsSessionFactoryTests : IDisposable
{ {
private readonly string _dir = private readonly string _dir =
@@ -12,6 +12,7 @@ namespace ZB.MOM.WW.Secrets.Tests.DependencyInjection;
/// core: every seam resolves, a real secret round-trips through the composed graph, and a missing /// core: every seam resolves, a real secret round-trips through the composed graph, and a missing
/// master key fails closed. /// master key fails closed.
/// </summary> /// </summary>
[Collection("Process environment")]
public sealed class AddZbSecretsTests public sealed class AddZbSecretsTests
{ {
private static string NewTempDbPath() => private static string NewTempDbPath() =>
@@ -4,6 +4,7 @@ using ZB.MOM.WW.Secrets.MasterKey;
namespace ZB.MOM.WW.Secrets.Tests.MasterKey; namespace ZB.MOM.WW.Secrets.Tests.MasterKey;
[Collection("Process environment")]
public class MasterKeyProviderTests public class MasterKeyProviderTests
{ {
// ---- Environment ---------------------------------------------------------------------- // ---- Environment ----------------------------------------------------------------------
@@ -0,0 +1,10 @@
namespace ZB.MOM.WW.Secrets.Tests;
/// <summary>
/// Serializes every test class that mutates process environment variables. setenv/getenv operate
/// on the single process-wide environ, which is not safe under concurrent mutation on glibc —
/// unique variable names do not remove the race, only serialization does. Classes that never
/// touch the environment stay fully parallel.
/// </summary>
[CollectionDefinition("Process environment", DisableParallelization = true)]
public sealed class ProcessEnvironmentCollection;