Fixes for resource manager, roots and ResPath.

`ResPath.RelativeTo()` now considers non-rooted paths relative to `.`
This fixes some things like `MemoryContentRoot`'s `FindFiles()` implementation.

Fix `IContentRoot.GetEntries()` default implementation (used by all content roots except `DirLoader`) not working at all.

Made `ResourceManager.ContentGetDirectoryEntries()` report content root mount paths as directories.

Added tests for all of the above.
This commit is contained in:
Pieter-Jan Briers
2023-06-03 12:23:07 +02:00
parent 7b23729f94
commit 23abb6177d
8 changed files with 189 additions and 4 deletions

View File

@@ -44,6 +44,10 @@ END TEMPLATE-->
### Bugfixes
* Fix exception if running the `>` command (remote execute) without even a space after it.
* `ResPath.RelativeTo()` now considers non-rooted paths relative to `.`.
* This fixes some things like `MemoryContentRoot`'s `FindFiles()` implementation.
* Fix `IContentRoot.GetEntries()` default implementation (used by all content roots except `DirLoader`) not working at all.
* Made `ResourceManager.ContentGetDirectoryEntries()` report content root mount paths as directories.
### Other
@@ -51,7 +55,8 @@ END TEMPLATE-->
### Internal
*None yet*
* Made `ConfigurationManager` not-abstract anymore so we can instantiate it from tests.
* Added new tests for `ResourceManager`.
## 0.124.0.0

View File

@@ -17,7 +17,8 @@ namespace Robust.Shared.Configuration
/// <summary>
/// Stores and manages global configuration variables.
/// </summary>
internal abstract class ConfigurationManager : IConfigurationManagerInternal
[Virtual]
internal class ConfigurationManager : IConfigurationManagerInternal
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly ILogManager _logManager = default!;

View File

@@ -0,0 +1,38 @@
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
namespace Robust.Shared.Console.Commands;
public sealed class VfsListCommand : LocalizedCommands
{
[Dependency] private readonly IResourceManager _resourceManager = default!;
public override string Command => "vfs_ls";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length > 1)
{
shell.WriteError(LocalizationManager.GetString("cmd-vfs_ls-err-args"));
return;
}
var entries = _resourceManager.ContentGetDirectoryEntries(new ResPath(args[0]));
foreach (var entry in entries)
{
shell.WriteLine(entry);
}
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
{
var opts = CompletionHelper.ContentDirPath(args[0], _resourceManager);
return CompletionResult.FromHintOptions(opts, LocalizationManager.GetString("cmd-vfs_ls-hint-path"));
}
return CompletionResult.Empty;
}
}

View File

@@ -49,7 +49,7 @@ namespace Robust.Shared.ContentPack
var options = FindFiles(path).Select(c =>
{
var segment = path.CanonPath.Split('/');
var segment = c.CanonPath.Split('/');
var segCount = segment.Count();
var newPath = segment.Skip(countDirs).First();
if (segCount > countDirs + 1)

View File

@@ -263,6 +263,10 @@ namespace Robust.Shared.ContentPack
if (!path.IsRooted)
throw new ArgumentException("Path is not rooted", nameof(path));
// If we don't do this, TryRelativeTo won't work correctly.
if (!path.CanonPath.EndsWith("/"))
path = new ResPath(path.CanonPath + "/");
var entries = new HashSet<string>();
foreach (var (prefix, root) in _contentRoots)
@@ -275,6 +279,23 @@ namespace Robust.Shared.ContentPack
entries.UnionWith(root.GetEntries(relative.Value));
}
// We have to add mount points too.
// e.g. during development, /Assemblies/ is a mount point,
// and there's no explicit /Assemblies/ folder in Resources.
// So we need to manually add it since the previous pass won't catch it at all.
foreach (var (prefix, _) in _contentRoots)
{
if (!prefix.TryRelativeTo(path, out var relative))
continue;
// Return first relative segment, unless it's literally just "." (identical path).
var segments = relative.Value.EnumerateSegments();
if (segments is ["."])
continue;
entries.Add(segments[0] + "/");
}
return entries;
}

View File

@@ -395,7 +395,7 @@ public readonly struct ResPath : IEquatable<ResPath>
return relative.Value;
}
throw new ArgumentException($"{CanonPath} does not start with {basePath}.");
throw new ArgumentException($"{CanonPath} does not start with '{basePath}'.");
}
/// <summary>
@@ -413,6 +413,13 @@ public readonly struct ResPath : IEquatable<ResPath>
return true;
}
// "foo.txt" is relative to "."
if (basePath == Self && IsRelative)
{
relative = this;
return true;
}
if (CanonPath.StartsWith(basePath.CanonPath))
{
var x = CanonPath[basePath.CanonPath.Length..]

View File

@@ -0,0 +1,112 @@
using System;
using NUnit.Framework;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.UnitTesting.Shared.ContentPack;
[TestFixture]
[TestOf(typeof(ResourceManager))]
[Parallelizable(ParallelScope.All)]
public sealed class ResourceManagerTest2
{
[Test]
public void TestMemoryRootReadFiles()
{
var (_, res) = CreateRes();
var testRoot = new MemoryContentRoot();
testRoot.AddOrUpdateFile(new ResPath("/a.txt"), "a"u8.ToArray());
testRoot.AddOrUpdateFile(new ResPath("/b.txt"), "b"u8.ToArray());
testRoot.AddOrUpdateFile(new ResPath("/c.txt"), "c"u8.ToArray());
testRoot.AddOrUpdateFile(new ResPath("/d/foo.txt"), "foo"u8.ToArray());
res.AddRoot(new ResPath("/"), testRoot);
Assert.Multiple(() =>
{
Assert.That(res.ContentFileReadAllText("/a.txt"), Is.EqualTo("a"));
Assert.That(res.ContentFileReadAllText("/b.txt"), Is.EqualTo("b"));
Assert.That(res.ContentFileReadAllText("/c.txt"), Is.EqualTo("c"));
Assert.That(res.ContentFileReadAllText("/d/foo.txt"), Is.EqualTo("foo"));
});
}
[Test]
public void TestMemoryRootGetDirectoryEntries()
{
var (_, res) = CreateRes();
var testRoot = new MemoryContentRoot();
testRoot.AddOrUpdateFile(new ResPath("/a.txt"), "a"u8.ToArray());
testRoot.AddOrUpdateFile(new ResPath("/b.txt"), "b"u8.ToArray());
testRoot.AddOrUpdateFile(new ResPath("/c.txt"), "c"u8.ToArray());
testRoot.AddOrUpdateFile(new ResPath("/d/foo.txt"), "foo"u8.ToArray());
res.AddRoot(new ResPath("/"), testRoot);
Assert.Multiple(() =>
{
Assert.That(res.ContentGetDirectoryEntries(new ResPath("/")), Is.EquivalentTo(new[]
{
"a.txt",
"b.txt",
"c.txt",
"d/"
}));
// Listing should work both with and without a trailing /
Assert.That(res.ContentGetDirectoryEntries(new ResPath("/d")), Is.EquivalentTo(new[]
{
"foo.txt"
}));
Assert.That(res.ContentGetDirectoryEntries(new ResPath("/d/")), Is.EquivalentTo(new[]
{
"foo.txt"
}));
});
}
/// <summary>
/// Test that a mount path is properly shown as a directory entry.
/// </summary>
[Test]
public void TestGetDirectoryEntriesTwoMounts()
{
var (_, res) = CreateRes();
var testRoot = new MemoryContentRoot();
testRoot.AddOrUpdateFile(new ResPath("foo.txt"), Array.Empty<byte>());
testRoot.AddOrUpdateFile(new ResPath("bar/foo.txt"), Array.Empty<byte>());
res.AddRoot(new ResPath("/"), testRoot);
res.AddRoot(new ResPath("/second"), testRoot);
res.AddRoot(new ResPath("/third/fourth"), testRoot);
Assert.That(res.ContentGetDirectoryEntries(new ResPath("/")), Is.EquivalentTo(new []
{
"foo.txt",
"bar/",
"second/",
"third/"
}));
}
private static (IDependencyCollection, IResourceManagerInternal) CreateRes()
{
var deps = new DependencyCollection();
deps.Register<IResourceManager, ResourceManager>();
deps.Register<IResourceManagerInternal, ResourceManager>();
deps.Register<IConfigurationManager, ConfigurationManager>();
deps.Register<ILogManager, LogManager>();
deps.Register<IGameTiming, GameTiming>();
deps.BuildGraph();
return (deps, deps.Resolve<IResourceManagerInternal>());
}
}

View File

@@ -130,6 +130,7 @@ public sealed class ResPathTest
[TestCase("a/b", "a", ExpectedResult = "b")]
[TestCase("/bar/", "/", ExpectedResult = "bar")]
[TestCase("/Textures/Weapons/laser.png", "/Textures/", ExpectedResult = "Weapons/laser.png")]
[TestCase("foo.txt", ".", ExpectedResult = "foo.txt")]
public string RelativeToTest(string source, string baseDir)
{
var path = new ResPath(source);