logo SERENITY
Game Systems Foundation
Search Results for

    Show / Hide Table of Contents

    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.

    Inheritance
    object
    ProjectTreeAndShippedContentResidueGuardBase
    Inherited Members
    object.ToString()
    object.Equals(object)
    object.Equals(object, object)
    object.ReferenceEquals(object, object)
    object.GetHashCode()
    object.GetType()
    object.MemberwiseClone()
    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()
    In this article
    © 2026 Serenity. All Rights Reserved