Class ProjectTreeAndShippedContentResidueGuardBase
Reusable EditMode safety net an editor test assembly adopts by declaring one namespace-less
[SetUpFixture] subclass of this base — NUnit scopes a SetUpFixture to the
assembly holding it, keyed by the namespace of the class that carries the attribute, so a single
shared fixture in this assembly would only ever watch Serenity.Tests.Shared.Editor itself.
Every subclass gets two independent guards for free:
- Project-tree residue: a before/after snapshot of every top-level entry under
Assets/plus everything under Serenity.Global.Infrastructure.Editor.SerenityUserContentPaths.GameRoot, the default output root every wizard's own creator falls back to when a test drives it without redirecting its output. - Shipped-content tampering: a before/after snapshot of (path, length, last-write-time) for every file under Serenity.Tests.Shared.Editor.ProjectTreeAndShippedContentResidueGuardBase.WatchedShippedContentRoots — cheap and deterministic on purpose, so content is never hashed and never restored; the guard only ever reports.
A full EditMode run can trigger a mid-suite domain reload (a test that generates or recompiles a script), which discards a fixture instance and constructs a fresh one for whatever tests remain — a baseline held in an instance field would then be captured again and already include an EARLIER segment's leak, hiding it. UnityEditor.SessionState survives a domain reload within the same Unity process, so the baseline captured at the true start of the run is the one the final teardown compares against. The teardown erases that baseline, so the next run in the same editor session starts from the tree as it is then — a folder the author creates between two runs is never reported as residue. Every key is namespaced by AssemblyDisplayName so two guard subclasses running in the same Unity process (every editor test assembly gets one) never share, or clobber, each other's baseline.
The setup snapshot is silent (a leftover may predate this guard or come from an aborted run); the teardown snapshot fails loudly, naming the owning assembly and every offending entry, so a fixture that stops cleaning up — or a product code path that writes into shipped content — turns into a suite failure instead of silent working-tree pollution that a CI runner without workspace cleanup carries into its next run.
The on-disk comparison alone has a real blind spot: EditorUtility.SetDirty only marks an
object dirty in memory, and AssetDatabase.SaveAssets is a GLOBAL flush — any assembly's call
to it writes every currently-dirty object project-wide, not only the ones that assembly itself
touched. An assembly whose own tests dirty a shipped object but never explicitly save it reports
clean at its own teardown (the file on disk has not changed YET); the object then stays dirty
across the domain until something else flushes it — a later assembly's own routine
AssetDatabase.SaveAssets() hygiene call, or the editor process's own shutdown after every
guard has already run its teardown — at which point no assembly's on-disk snapshot ever sees the
write inside its own window. Serenity.Tests.Shared.Editor.ProjectTreeAndShippedContentResidueGuardBase.FindDirtyShippedContentPaths(System.Collections.Generic.IEnumerable{System.String},System.Collections.Generic.IReadOnlyCollection{System.String}) closes that gap: it checks
EditorUtility.IsDirty directly against every ALREADY-LOADED, natively-persistable object under
the watched roots, which names the assembly that actually caused the dirty state at ITS OWN teardown,
before anything flushes it — the on-disk check remains for the write that already lands inside one
assembly's own window. A dirty imported (foreign) asset — a dynamic Font rebuilding its glyph
atlas is the recurring real-world case — is excluded: AssetDatabase.SaveAssets can never write
a foreign asset's source file, so its in-memory dirtiness can never become the disk write this guard
exists to catch.
The guard never changes editor state: it only ever reads UnityEditor.EditorUtility.IsDirty(UnityEngine.Object)
and never calls EditorUtility.ClearDirty or writes anything back. In the INTERACTIVE editor's own
Test Runner this matters, because Serenity.Tests.Shared.Editor.ProjectTreeAndShippedContentResidueGuardBase.FindDirtyShippedContentPaths(System.Collections.Generic.IEnumerable{System.String},System.Collections.Generic.IReadOnlyCollection{System.String}) cannot otherwise tell a
developer's own unsaved edit to a shipped asset — made before the run even started — from dirtiness the
run itself caused. OneTimeSetUp() closes that gap by recording, alongside the tree and
shipped-content baselines in the same UnityEditor.SessionState entry, every path already dirty at that
moment; OneTimeTearDown() then reports only a path that is dirty NOW and was absent from
that baseline. A developer's pre-existing dirty asset is therefore never reported, and — because nothing
here ever clears a dirty flag — it stays exactly as dirty as the developer left it. An object one
assembly's OWN tests dirty during the run IS reported by that assembly, since it was clean at that
assembly's own set-up; the next assembly's set-up then captures it as already dirty, so it is reported
once, by the assembly that actually caused it, and never again downstream.
Inherited Members
Namespace: Serenity.Tests.Shared.Editor
Assembly: Serenity.Tests.Shared.Editor.dll
Syntax
public abstract class ProjectTreeAndShippedContentResidueGuardBase
Constructors
ProjectTreeAndShippedContentResidueGuardBase()
Declaration
protected ProjectTreeAndShippedContentResidueGuardBase()
Properties
AssemblyDisplayName
The exact asmdef name of the assembly this subclass is compiled into — used to keep this guard's UnityEditor.SessionState keys and failure messages unique per assembly.
Declaration
protected abstract string AssemblyDisplayName { get; }
Property Value
| Type | Description |
|---|---|
| string |
Methods
OneTimeSetUp()
Declaration
public void OneTimeSetUp()
OneTimeTearDown()
Declaration
public void OneTimeTearDown()