diff options
| author | Shadowghost <Shadowghost@users.noreply.github.com> | 2026-09-15 11:15:49 -0400 |
|---|---|---|
| committer | Cody Robibero <cody@robibe.ro> | 2026-09-15 11:15:49 -0400 |
| commit | a2aa3d96d2a75b01f94354b541fe890b78e7c8bd (patch) | |
| tree | e6e60d6eb900b74f59c82dcef4fdd7690a8bfe9f /tests/Jellyfin.Controller.Tests | |
| parent | 51abf6f503dae58428f3558e77805f2937335d8f (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')
| -rw-r--r-- | tests/Jellyfin.Controller.Tests/Entities/FolderChildCacheTests.cs | 97 |
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; + } + } +} |
