aboutsummaryrefslogtreecommitdiff
path: root/tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs
diff options
context:
space:
mode:
authorShadowghost <Shadowghost@users.noreply.github.com>2026-09-15 11:15:49 -0400
committerCody Robibero <cody@robibe.ro>2026-09-15 11:15:49 -0400
commita2aa3d96d2a75b01f94354b541fe890b78e7c8bd (patch)
treee6e60d6eb900b74f59c82dcef4fdd7690a8bfe9f /tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs
parent51abf6f503dae58428f3558e77805f2937335d8f (diff)
Backport pull request #17884 from jellyfin/release-12.z
Release a folder's children once its subtree has been scanned Original-merge: 2f2adae7d99537e53417f6d92aa3d40345a17b99 Merged-by: crobibero <cody@robibe.ro> Backported-by: Cody Robibero <cody@robibe.ro>
Diffstat (limited to 'tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs')
-rw-r--r--tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs97
1 files changed, 97 insertions, 0 deletions
diff --git a/tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs b/tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs
new file mode 100644
index 0000000000..705238317a
--- /dev/null
+++ b/tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using MediaBrowser.Controller.Entities;
+using Xunit;
+
+namespace Jellyfin.Controller.Tests.Entities;
+
+/// <summary>
+/// Covers <see cref="Folder.ReleaseCachedChildren"/>, which a recursive scan calls as it unwinds so
+/// the folders it walked do not keep the whole item graph of the library alive behind it.
+/// </summary>
+public class FolderChildCacheTests
+{
+ [Fact]
+ public void ReleaseCachedChildren_MakesTheNextAccessReload()
+ {
+ var folder = new TrackingFolder();
+
+ Assert.Empty(folder.Children);
+ Assert.Equal(1, folder.LoadCount);
+
+ // Second access is served from the cache on the instance.
+ Assert.Empty(folder.Children);
+ Assert.Equal(1, folder.LoadCount);
+
+ folder.ReleaseCachedChildren();
+
+ Assert.Empty(folder.Children);
+ Assert.Equal(2, folder.LoadCount);
+ }
+
+ [Fact]
+ public void ReleaseCachedChildren_ReachesEveryLevelBelow()
+ {
+ var leaf = new TrackingFolder();
+ var middle = new TrackingFolder { Source = [leaf] };
+ var root = new TrackingFolder { Source = [middle] };
+
+ // Walk the whole tree, as a recursive scan does, so every level holds its children.
+ Assert.Single(root.Children);
+ Assert.Single(middle.Children);
+ Assert.Empty(leaf.Children);
+ Assert.Equal(1, root.LoadCount);
+ Assert.Equal(1, middle.LoadCount);
+ Assert.Equal(1, leaf.LoadCount);
+
+ root.ReleaseCachedChildren();
+
+ Assert.Single(root.Children);
+ Assert.Single(middle.Children);
+ Assert.Empty(leaf.Children);
+ Assert.Equal(2, root.LoadCount);
+ Assert.Equal(2, middle.LoadCount);
+ Assert.Equal(2, leaf.LoadCount);
+ }
+
+ [Fact]
+ public void ReleaseCachedChildren_LoadsNothingThatIsNotAlreadyHeld()
+ {
+ var leaf = new TrackingFolder();
+ var root = new TrackingFolder { Source = [leaf] };
+
+ root.ReleaseCachedChildren();
+
+ Assert.Equal(0, root.LoadCount);
+ Assert.Equal(0, leaf.LoadCount);
+ }
+
+ [Fact]
+ public void ReleaseCachedChildren_TerminatesOnACycle()
+ {
+ var first = new TrackingFolder();
+ var second = new TrackingFolder { Source = [first] };
+ first.Source = [second];
+
+ Assert.Single(first.Children);
+ Assert.Single(second.Children);
+
+ // Clearing before descending is what stops this from recursing forever.
+ first.ReleaseCachedChildren();
+
+ Assert.Equal(1, first.LoadCount);
+ Assert.Equal(1, second.LoadCount);
+ }
+
+ private sealed class TrackingFolder : Folder
+ {
+ public int LoadCount { get; private set; }
+
+ public IReadOnlyList<BaseItem> Source { get; set; } = [];
+
+ protected override IReadOnlyList<BaseItem> LoadChildren()
+ {
+ LoadCount++;
+ return Source;
+ }
+ }
+}