73 lines
3.9 KiB
C#
73 lines
3.9 KiB
C#
using Xunit;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
/// <summary>
|
|
/// Serializes every test class that shares the camera/render family of
|
|
/// <b>process-global mutable statics</b>. 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.
|
|
///
|
|
/// <para>
|
|
/// The per-class <c>try</c>/<c>finally</c> save/restore blocks are correct
|
|
/// <i>within</i> a class and remain necessary — but they are not sufficient.
|
|
/// A <c>finally</c> 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 <c>finally</c> sufficient.
|
|
/// </para>
|
|
///
|
|
/// <para>The shared globals below are covered, and every one of them is an
|
|
/// edge between at least two classes in this collection:</para>
|
|
/// <list type="bullet">
|
|
/// <item><description><c>AcDream.Core.Rendering.CameraDiagnostics</c> —
|
|
/// <c>AlignToSlope</c>, <c>CollideCamera</c>, <c>TranslationStiffness</c>,
|
|
/// <c>RotationStiffness</c>, <c>UseRetailChaseCamera</c>. Read by
|
|
/// <c>RetailChaseCamera.Update</c>, <c>CameraController.Active</c>,
|
|
/// <c>CameraFrameController</c>, <c>WorldRenderFrameBuilder</c> and
|
|
/// <c>MouseLookController</c>.</description></item>
|
|
/// <item><description><c>AcDream.Core.Rendering.RenderingDiagnostics.DumpWalkTranscriptEnabled</c>
|
|
/// (Campaign OVERHAUL S3 chunk 1) — written by
|
|
/// <c>WalkFrameDriverTests</c>' transcript-emitter tests
|
|
/// (<c>WalkFrameDriverTranscriptTests.cs</c>).</description></item>
|
|
/// <item><description><c>System.Console.Out</c> — redirected via
|
|
/// <c>Console.SetOut</c> by the transcript tests to capture output.
|
|
/// Interleaved redirection can restore a DISPOSED <c>StringWriter</c> as the
|
|
/// process-wide <c>Console.Out</c>, which then throws in unrelated
|
|
/// tests. Even classes that only write to the console can race a capture's
|
|
/// <c>StringWriter.ToString</c> snapshot; Console's synchronized writer
|
|
/// does not synchronize reads of the underlying StringBuilder.</description></item>
|
|
/// </list>
|
|
///
|
|
/// <para>
|
|
/// 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 (<c>RetailChaseCameraTests</c> sets
|
|
/// <c>AlignToSlope</c> and <c>CollideCamera</c> to <see langword="false"/>;
|
|
/// the <c>UseRetailChaseCamera</c> writers set it to <see langword="false"/>).
|
|
/// Classes that merely construct a <c>CameraController</c> without a retail
|
|
/// chase camera are NOT members — their reads are insensitive.
|
|
/// </para>
|
|
///
|
|
/// <para>
|
|
/// Marker only, no collection fixture: each member still needs its own
|
|
/// per-test values (several are <c>[Theory]</c> 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 <c>finally</c> blocks already
|
|
/// return the defaults. <c>DisableParallelization</c> additionally excludes
|
|
/// OTHER collections for this collection's duration: member-only serialization
|
|
/// cannot protect process-wide console captures or prevent unrelated walk tests
|
|
/// from observing the temporary transcript flag. Ordinary collections can still
|
|
/// run in parallel with each other. Follows the
|
|
/// <c>ThreadSchedulingCollection</c> exclusivity precedent.
|
|
/// </para>
|
|
///
|
|
/// <para>Evidence + history: <c>docs/ISSUES.md</c> #251.</para>
|
|
/// </summary>
|
|
[CollectionDefinition(Name, DisableParallelization = true)]
|
|
public sealed class CameraDiagnosticsCollection
|
|
{
|
|
public const string Name = "Camera diagnostics globals";
|
|
}
|