aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorShadowghost <Ghost_of_Stone@web.de>2026-09-02 07:06:08 +0200
committerShadowghost <Ghost_of_Stone@web.de>2026-09-02 07:06:08 +0200
commitd73e3d964e3ce8197ec86bdaad447072305bc0d6 (patch)
treeac6e3add8f901f4b17fe5815fbfc30832235d955 /tests
parente5dc3b8a54fdad218ba062f8adb58fb5e42d8f9d (diff)
Optimize Caches
Co-Authored-By: Cody Robibero <cody@robibe.ro>
Diffstat (limited to 'tests')
-rw-r--r--tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs131
1 files changed, 123 insertions, 8 deletions
diff --git a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
index bc03bfc33b..a550828783 100644
--- a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
+++ b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
@@ -268,10 +268,10 @@ namespace Jellyfin.Controller.Tests
[Fact]
public void GetFileSystemEntries_FarMorePathsThanTheCacheHolds_EvictsInsteadOfGrowing()
{
- // The service is a singleton, so the cache has to give entries back rather than hold every
- // path the server ever saw. Asking for far more paths than it can hold must push the first
- // one out, which shows up as the file system being read for it a second time.
- const int PathCount = 40000;
+ // The cache outlives every DirectoryService that reads it, so it has to give entries back
+ // rather than hold every path the server ever saw. Asking for more paths than it can hold
+ // must push the first one out, which shows up as the file system being read for it twice.
+ const int PathCount = 8192;
var fileSystemMock = new Mock<IFileSystem>();
fileSystemMock.Setup(f => f.GetFileSystemEntries(It.IsAny<string>()))
@@ -279,17 +279,132 @@ namespace Jellyfin.Controller.Tests
var directoryService = new DirectoryService(fileSystemMock.Object);
- var firstPath = "/music/artist0";
- directoryService.GetFileSystemEntries(firstPath);
+ const string FirstPath = "/music/artist0";
+ directoryService.GetFileSystemEntries(FirstPath);
for (var i = 1; i < PathCount; i++)
{
directoryService.GetFileSystemEntries("/music/artist" + i.ToString(CultureInfo.InvariantCulture));
}
- directoryService.GetFileSystemEntries(firstPath);
+ directoryService.GetFileSystemEntries(FirstPath);
- fileSystemMock.Verify(f => f.GetFileSystemEntries(firstPath), Times.Exactly(2));
+ fileSystemMock.Verify(f => f.GetFileSystemEntries(FirstPath), Times.Exactly(2));
+ }
+
+ [Fact]
+ public void GetFileSystemEntries_SecondServiceOverSameFileSystem_ReusesTheFirstAnswer()
+ {
+ // The library code news up a DirectoryService per item, so what one of them learned about
+ // a directory has to be worth something to the next one reading the same file system.
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(LowerCasePath))
+ .Returns(_lowerCaseFileSystemMetadata);
+
+ new DirectoryService(fileSystemMock.Object).GetFileSystemEntries(LowerCasePath);
+ var result = new DirectoryService(fileSystemMock.Object).GetFileSystemEntries(LowerCasePath);
+
+ Assert.Equal(_lowerCaseFileSystemMetadata, result);
+ fileSystemMock.Verify(f => f.GetFileSystemEntries(LowerCasePath), Times.Once);
+ }
+
+ [Fact]
+ public void GetFileSystemEntries_SeparateFileSystems_DoNotShareAnswers()
+ {
+ var firstFileSystem = new Mock<IFileSystem>();
+ firstFileSystem.Setup(f => f.GetFileSystemEntries(LowerCasePath))
+ .Returns(_lowerCaseFileSystemMetadata);
+ var secondFileSystem = new Mock<IFileSystem>();
+ secondFileSystem.Setup(f => f.GetFileSystemEntries(LowerCasePath))
+ .Returns(_upperCaseFileSystemMetadata);
+
+ var firstResult = new DirectoryService(firstFileSystem.Object).GetFileSystemEntries(LowerCasePath);
+ var secondResult = new DirectoryService(secondFileSystem.Object).GetFileSystemEntries(LowerCasePath);
+
+ Assert.Equal(_lowerCaseFileSystemMetadata, firstResult);
+ Assert.Equal(_upperCaseFileSystemMetadata, secondResult);
+ }
+
+ [Fact]
+ public void Invalidate_GivenADirectory_DropsBothTheListingAndTheFilePaths()
+ {
+ // Clearing only one of the two views of a directory leaves the other one answering from
+ // before whatever was just written there.
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.SetupSequence(f => f.GetFileSystemEntries(LowerCasePath))
+ .Returns(_lowerCaseFileSystemMetadata)
+ .Returns(_upperCaseFileSystemMetadata);
+ fileSystemMock.SetupSequence(f => f.GetFilePaths(LowerCasePath, false))
+ .Returns(new[] { LowerCasePath + "/Song 2.mp3" })
+ .Returns(new[] { LowerCasePath + "/Song 2.mp3", LowerCasePath + "/Song 2.srt" });
+
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+ directoryService.GetFileSystemEntries(LowerCasePath);
+ directoryService.GetFilePaths(LowerCasePath);
+
+ directoryService.Invalidate(LowerCasePath);
+
+ Assert.Equal(_upperCaseFileSystemMetadata, directoryService.GetFileSystemEntries(LowerCasePath));
+ Assert.Equal(2, directoryService.GetFilePaths(LowerCasePath).Count);
+ }
+
+ [Fact]
+ public void Invalidate_GivenAFile_DropsTheListingOfTheDirectoryHoldingIt()
+ {
+ // Downloading a subtitle changes what its folder contains, not just the one path.
+ const string NewFile = LowerCasePath + "/Song 2.srt";
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.SetupSequence(f => f.GetFileSystemEntries(LowerCasePath))
+ .Returns(_lowerCaseFileSystemMetadata)
+ .Returns(_upperCaseFileSystemMetadata);
+
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+ directoryService.GetFileSystemEntries(LowerCasePath);
+
+ directoryService.Invalidate(NewFile);
+
+ Assert.Equal(_upperCaseFileSystemMetadata, directoryService.GetFileSystemEntries(LowerCasePath));
+ }
+
+ [Fact]
+ public void Invalidate_OnOneService_IsSeenByAnotherOverTheSameFileSystem()
+ {
+ // Whoever writes the file and whoever refreshes the item hold different services, so
+ // invalidating has to reach the cache both of them read.
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.SetupSequence(f => f.GetFileSystemEntries(LowerCasePath))
+ .Returns(_lowerCaseFileSystemMetadata)
+ .Returns(_upperCaseFileSystemMetadata);
+
+ new DirectoryService(fileSystemMock.Object).GetFileSystemEntries(LowerCasePath);
+ new DirectoryService(fileSystemMock.Object).Invalidate(LowerCasePath + "/Song 2.srt");
+
+ var result = new DirectoryService(fileSystemMock.Object).GetFileSystemEntries(LowerCasePath);
+
+ Assert.Equal(_upperCaseFileSystemMetadata, result);
+ }
+
+ [Fact]
+ public void GetFilePaths_ClearingTheCache_KeepsTheParentDirectory()
+ {
+ // Re-reading one directory is not a reason to make the server list the library folder
+ // holding it again, which the shared cache would otherwise have to do.
+ const string ParentPath = "/music";
+
+ var fileSystemMock = new Mock<IFileSystem>();
+ fileSystemMock.Setup(f => f.GetFilePaths(LowerCasePath))
+ .Returns(new[] { LowerCasePath + "/Song 2.mp3" });
+ fileSystemMock.Setup(f => f.GetFileSystemEntries(ParentPath))
+ .Returns(_lowerCaseFileSystemMetadata);
+
+ var directoryService = new DirectoryService(fileSystemMock.Object);
+ directoryService.GetFileSystemEntries(ParentPath);
+
+ directoryService.GetFilePaths(LowerCasePath, true);
+
+ directoryService.GetFileSystemEntries(ParentPath);
+ fileSystemMock.Verify(f => f.GetFileSystemEntries(ParentPath), Times.Once);
}
[Fact]