using Xunit; namespace AcDream.App.Tests.Rendering; /// /// Serializes every test class that shares the camera/render family of /// process-global mutable statics. xUnit runs distinct test classes in /// PARALLEL by default, and these statics live on the process, not on the test /// — so two classes running concurrently observe each other's writes. /// /// /// The per-class try/finally save/restore blocks are correct /// within a class and remain necessary — but they are not sufficient. /// A finally only bounds the mutation in TIME along its own thread; it /// cannot stop another class from reading the static inside that window. Worse, /// two overlapping save/restore pairs can interleave so the second restore /// writes back the FIRST one's temporary value, leaving the global permanently /// wrong for the rest of the run. Serializing the sharers is what actually makes /// each class's finally sufficient. /// /// /// Three distinct globals are covered, and every one of them is a shared /// edge between at least two classes in this collection: /// /// AcDream.Core.Rendering.CameraDiagnostics — /// AlignToSlope, CollideCamera, TranslationStiffness, /// RotationStiffness, UseRetailChaseCamera. Read by /// RetailChaseCamera.Update, CameraController.Active, /// CameraFrameController, WorldRenderFrameBuilder and /// MouseLookController. /// AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled /// — written by CornerFloodReplayTests and /// Issue181WallPressEquilibriumTests. /// System.Console.Out — redirected via /// Console.SetOut by those same two classes to capture probe output. /// Interleaved redirection can restore a DISPOSED StringWriter as the /// process-wide Console.Out, which then throws in unrelated /// tests. /// /// /// /// Membership is deliberately narrow: only classes that write one of these /// globals, or that drive production code which READS a knob another member /// mutates away from its default (RetailChaseCameraTests sets /// AlignToSlope and CollideCamera to ; /// the UseRetailChaseCamera writers set it to ). /// Classes that merely construct a CameraController without a retail /// chase camera are NOT members — their reads are insensitive. /// /// /// /// Marker only, no collection fixture: each member still needs its own /// per-test values (several are [Theory] cases that set different knobs /// per case), so a fixture cannot own the save/restore without rewriting every /// member's internals. The existing per-test finally blocks already /// return the defaults; this attribute supplies the mutual exclusion they were /// missing. Follows the WorldEnvironmentControllerCollection precedent. /// /// /// Evidence + history: docs/ISSUES.md #251. /// [CollectionDefinition(Name)] public sealed class CameraDiagnosticsCollection { public const string Name = "Camera diagnostics globals"; }