using System; using System.Collections.Generic; using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; using Wabbajack.Common; using Wabbajack.Lib; using Wabbajack.Lib.Downloaders; using Xunit; using Xunit.Abstractions; namespace Wabbajack.VirtualFileSystem.Test { public class VFSTests : IAsyncLifetime { private static readonly AbsolutePath VFS_TEST_DIR = "vfs_test_dir".ToPath().RelativeToEntryPoint(); private static readonly AbsolutePath TEST_ZIP = "test.zip".RelativeTo(VFS_TEST_DIR); private static readonly AbsolutePath TEST_TXT = "test.txt".RelativeTo(VFS_TEST_DIR); private static readonly AbsolutePath ARCHIVE_TEST_TXT = "archive/test.txt".RelativeTo(VFS_TEST_DIR); private Context context; private readonly ITestOutputHelper _helper; private IDisposable _unsub; private WorkQueue Queue { get; } = new WorkQueue(); public VFSTests(ITestOutputHelper helper) { _helper = helper; _unsub = Utils.LogMessages.Subscribe(f => { try { _helper.WriteLine(f.ShortDescription); } catch (Exception) { // ignored } }); context = new Context(Queue); } public async Task InitializeAsync() { await VFS_TEST_DIR.DeleteDirectory(); VFS_TEST_DIR.CreateDirectory(); } public async Task DisposeAsync() { _unsub.Dispose(); await VFS_TEST_DIR.DeleteDirectory(); } [Fact] public async Task FilesAreIndexed() { await AddFile(TEST_TXT, "This is a test"); await AddTestRoot(); var file = context.Index.ByRootPath["test.txt".ToPath().RelativeTo(VFS_TEST_DIR)]; Assert.NotNull(file); Assert.Equal(14, file.Size); Assert.Equal(file.Hash, Hash.FromBase64("qX0GZvIaTKM=")); } private async Task AddTestRoot() { await context.AddRoot(VFS_TEST_DIR); await context.WriteToFile("vfs_cache.bin".RelativeTo(VFS_TEST_DIR)); await context.IntegrateFromFile( "vfs_cache.bin".RelativeTo(VFS_TEST_DIR)); } [Fact] public async Task ArchiveContentsAreIndexed() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddTestRoot(); var absPath = "test.zip".RelativeTo(VFS_TEST_DIR); var file = context.Index.ByRootPath[absPath]; Assert.NotNull(file); Assert.Equal(128, file.Size); Assert.Equal(await absPath.FileHashAsync(), file.Hash); Assert.True(file.IsArchive); var innerFile = file.Children.First(); Assert.Equal(14, innerFile.Size); Assert.Equal(Hash.FromBase64("qX0GZvIaTKM="), innerFile.Hash); Assert.Same(file, file.Children.First().Parent); } [Fact] public async Task DuplicateFileHashes() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddFile(TEST_TXT, "This is a test"); await AddTestRoot(); var files = context.Index.ByHash[Hash.FromBase64("qX0GZvIaTKM=")]; Assert.Equal(2, files.Count()); } [Fact] public async Task DeletedFilesAreRemoved() { await AddFile(TEST_TXT, "This is a test"); await AddTestRoot(); var file = context.Index.ByRootPath[TEST_TXT]; Assert.NotNull(file); Assert.Equal(14, file.Size); Assert.Equal(Hash.FromBase64("qX0GZvIaTKM="), file.Hash); await TEST_TXT.DeleteAsync(); await AddTestRoot(); Assert.DoesNotContain(TEST_TXT, context.Index.AllFiles.Select(f => f.AbsoluteName)); } [Fact] public async Task UnmodifiedFilesAreNotReIndexed() { await AddFile(TEST_TXT, "This is a test"); await AddTestRoot(); var old_file = context.Index.ByRootPath[TEST_TXT]; var old_time = old_file.LastAnalyzed; await AddTestRoot(); var new_file = context.Index.ByRootPath[TEST_TXT]; Assert.Equal(old_time, new_file.LastAnalyzed); } [Fact] public async Task CanStageSimpleArchives() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddTestRoot(); var res = new FullPath(TEST_ZIP, new[] {(RelativePath)"test.txt"}); var files = new [] {context.Index.ByFullPath[res]}; var queue = new WorkQueue(); await context.Extract(queue, files.ToHashSet(), async (file, factory) => { await using var s = await factory.GetStream(); Assert.Equal("This is a test", await s.ReadAllTextAsync()); }); } [Fact] public async Task CanStageNestedArchives() { await AddFile(ARCHIVE_TEST_TXT, "This is a test"); await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); var inner_dir = @"archive\other\dir".RelativeTo(VFS_TEST_DIR); inner_dir.CreateDirectory(); await TEST_ZIP.MoveToAsync( @"archive\other\dir\nested.zip".RelativeTo(VFS_TEST_DIR)); await ZipUpFolder(ARCHIVE_TEST_TXT.Parent, TEST_ZIP); await AddTestRoot(); var files = context.Index.ByHash[Hash.FromBase64("qX0GZvIaTKM=")]; var queue = new WorkQueue(); await context.Extract(queue, files.ToHashSet(), async (file, factory) => { await using var s = await factory.GetStream(); Assert.Equal("This is a test", await s.ReadAllTextAsync()); }); } [Theory] [InlineData(Game.SkyrimSpecialEdition, 20035, 130759)] // Lucian public async Task CanAnalyzeMods(Game game, int modid, int fileId) { await using var tmpFolder = await TempFolder.Create(); var path = await FileExtractorTests.DownloadMod(game, modid, fileId); await path.CopyToAsync(path.FileName.RelativeTo(tmpFolder.Dir)); var context = new Context(Queue); await context.AddRoot(tmpFolder.Dir); Assert.True(context.Index.ByFullPath.Count >= 3); } private static async Task AddFile(AbsolutePath filename, string text) { filename.Parent.CreateDirectory(); await filename.WriteAllTextAsync(text); } private static async Task ZipUpFolder(AbsolutePath folder, AbsolutePath output) { ZipFile.CreateFromDirectory((string)folder, (string)output); await folder.DeleteDirectory(); } } }