diff --git a/FinModelUtility/Benchmarks/CastingValues.cs b/FinModelUtility/Benchmarks/CastingValues.cs new file mode 100644 index 000000000..092d78e6a --- /dev/null +++ b/FinModelUtility/Benchmarks/CastingValues.cs @@ -0,0 +1,23 @@ +using BenchmarkDotNet.Attributes; + +namespace benchmarks { + public class CastingValues { + private const int n = 100000000; + + [Benchmark] + public unsafe void ViaPointer() { + for (var i = 0; i < n; i++) { + ulong value = 123456; + double castedValue = *(double*) (&value); + } + } + + [Benchmark] + public void ViaBitConverter() { + for (var i = 0; i < n; i++) { + ulong value = 123456; + double castedValue = BitConverter.UInt64BitsToDouble(value); + } + } + } +} \ No newline at end of file diff --git a/FinModelUtility/Benchmarks/Main.cs b/FinModelUtility/Benchmarks/Main.cs index 65e44f780..770773b17 100644 --- a/FinModelUtility/Benchmarks/Main.cs +++ b/FinModelUtility/Benchmarks/Main.cs @@ -4,7 +4,7 @@ namespace benchmarks { public class Program { public static void Main(string[] args) { - var summary = BenchmarkRunner.Run( + var summary = BenchmarkRunner.Run( ManualConfig.Create(DefaultConfig.Instance) .WithOptions(ConfigOptions .DisableOptimizationsValidator)); diff --git a/FinModelUtility/Fin/Fin/Fin.csproj b/FinModelUtility/Fin/Fin/Fin.csproj index 6b4975e49..b1a343478 100644 --- a/FinModelUtility/Fin/Fin/Fin.csproj +++ b/FinModelUtility/Fin/Fin/Fin.csproj @@ -34,6 +34,7 @@ + diff --git a/FinModelUtility/Fin/Fin/src/audio/io/exporters/AudioExporterInterfaces.cs b/FinModelUtility/Fin/Fin/src/audio/io/exporters/AudioExporterInterfaces.cs new file mode 100644 index 000000000..b9eee29a4 --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/audio/io/exporters/AudioExporterInterfaces.cs @@ -0,0 +1,7 @@ +using fin.io; + +namespace fin.audio.io.exporters { + public interface IAudioExporter { + void ExportAudio(IAudioBuffer audioBuffer, ISystemFile outputFile); + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/audio/io/exporters/ogg/OggAudioExporter.cs b/FinModelUtility/Fin/Fin/src/audio/io/exporters/ogg/OggAudioExporter.cs new file mode 100644 index 000000000..bbbdb64e8 --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/audio/io/exporters/ogg/OggAudioExporter.cs @@ -0,0 +1,108 @@ +using System; +using System.IO; + +using fin.io; +using fin.util.asserts; + +using OggVorbisEncoder; + +namespace fin.audio.io.exporters.ogg { + /// + /// Based on example at: + /// https://github.com/SteveLillis/.NET-Ogg-Vorbis-Encoder/blob/master/OggVorbisEncoder.Example/Encoder.cs + /// + public class OggAudioExporter : IAudioExporter { + private const int WRITE_BUFFER_SIZE = 512; + + public void ExportAudio(IAudioBuffer audioBuffer, + ISystemFile outputFile) { + Asserts.SequenceEqual(".ogg", outputFile.FileType.ToLower()); + + using var outputData = outputFile.OpenWrite(); + + var channelCount = audioBuffer.AudioChannelsType switch { + AudioChannelsType.MONO => 1, + AudioChannelsType.STEREO => 2, + }; + + var oggStream = new OggStream(new Random().Next()); + + // ========================================================= + // HEADER + // ========================================================= + // Vorbis streams begin with three headers; the initial header (with + // most of the codec setup parameters) which is mandated by the Ogg + // bitstream spec. The second header holds any comment fields. The + // third header holds the bitstream codebook. + var info = VorbisInfo.InitVariableBitRate(channelCount, + audioBuffer.Frequency, + 1f); + var infoPacket = HeaderPacketBuilder.BuildInfoPacket(info); + var commentsPacket + = HeaderPacketBuilder.BuildCommentsPacket(new Comments()); + var booksPacket = HeaderPacketBuilder.BuildBooksPacket(info); + + oggStream.PacketIn(infoPacket); + oggStream.PacketIn(commentsPacket); + oggStream.PacketIn(booksPacket); + + // Flush to force audio data onto its own page per the spec + FlushPages_(oggStream, outputData, true); + + // ========================================================= + // BODY (Audio Data) + // ========================================================= + var processingState = ProcessingState.Create(info); + + var lengthInSamples = audioBuffer.LengthInSamples; + + var floatSamples = new float[channelCount][]; + for (var c = 0; c < channelCount; ++c) { + var channelSamples = floatSamples[c] = new float[lengthInSamples]; + + var channel = channelCount switch { + 1 => AudioChannelType.MONO, + 2 => c switch { + 0 => AudioChannelType.STEREO_LEFT, + 1 => AudioChannelType.STEREO_RIGHT + } + }; + + for (var i = 0; i < lengthInSamples; ++i) { + var shortSample = audioBuffer.GetPcm(channel, i); + channelSamples[i] = (shortSample / (1f * short.MaxValue)); + } + } + + for (int readIndex = 0; + readIndex <= lengthInSamples; + readIndex += WRITE_BUFFER_SIZE) { + if (readIndex == lengthInSamples) { + processingState.WriteEndOfStream(); + } else { + var writeLength + = Math.Min(lengthInSamples - readIndex, WRITE_BUFFER_SIZE); + processingState.WriteData(floatSamples, writeLength, readIndex); + } + + while (!oggStream.Finished && + processingState.PacketOut(out OggPacket packet)) { + oggStream.PacketIn(packet); + + FlushPages_(oggStream, outputData, false); + } + } + + FlushPages_(oggStream, outputData, true); + } + + private static void FlushPages_(OggStream oggStream, + Stream output, + bool force) { + while (oggStream.PageOut(out OggPage page, force)) { + output.Write(page.Header, 0, page.Header.Length); + output.Write(page.Body, 0, page.Body.Length); + } + } + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/testing/BGoldenTests.cs b/FinModelUtility/Fin/Fin/src/testing/BGoldenTests.cs new file mode 100644 index 000000000..7210d193f --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/testing/BGoldenTests.cs @@ -0,0 +1,10 @@ +using fin.io; +using fin.io.bundles; + +namespace fin.testing { + public abstract class BGoldenTests + where TFileBundle : IFileBundle { + public abstract TFileBundle GetFileBundleFromDirectory( + IFileHierarchyDirectory directory); + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/testing/GoldenAssert.cs b/FinModelUtility/Fin/Fin/src/testing/GoldenAssert.cs new file mode 100644 index 000000000..e4412aa7c --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/testing/GoldenAssert.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +using CommunityToolkit.HighPerformance; + +using fin.io; +using fin.util.asserts; +using fin.util.strings; + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace fin.testing { + public static class GoldenAssert { + private const string TMP_NAME = "tmp"; + + public static ISystemDirectory GetRootGoldensDirectory( + Assembly executingAssembly) { + var assemblyName = + executingAssembly.ManifestModule.Name.SubstringUpTo(".dll"); + + var executingAssemblyDll = new FinFile(executingAssembly.Location); + var executingAssemblyDir = executingAssemblyDll.AssertGetParent(); + + var currentDir = executingAssemblyDir; + while (currentDir.Name != assemblyName) { + currentDir = currentDir.AssertGetParent(); + } + + Assert.IsNotNull(currentDir); + + var gloTestsDir = currentDir; + var goldensDirectory = gloTestsDir.AssertGetExistingSubdir("goldens"); + + return goldensDirectory; + } + + public static IEnumerable GetGoldenDirectories( + ISystemDirectory rootGoldenDirectory) { + var hierarchy = FileHierarchy.From(rootGoldenDirectory); + return hierarchy.Root.GetExistingSubdirs() + .Where( + subdir => subdir.Name != TMP_NAME); + } + + public static IEnumerable + GetGoldenInputDirectories(ISystemDirectory rootGoldenDirectory) + => GetGoldenDirectories(rootGoldenDirectory) + .Select(subdir => subdir.AssertGetExistingSubdir("input")); + + public static void RunInTestDirectory( + IFileHierarchyDirectory goldenSubdir, + Action handler) { + var tmpDirectory = goldenSubdir.Impl.GetOrCreateSubdir(TMP_NAME); + tmpDirectory.DeleteContents(); + + try { + handler(tmpDirectory); + } finally { + tmpDirectory.DeleteContents(); + tmpDirectory.Delete(); + } + } + + public static void AssertFilesInDirectoriesAreIdentical( + IReadOnlyTreeDirectory lhs, + IReadOnlyTreeDirectory rhs) { + var lhsFiles = lhs.GetExistingFiles() + .ToDictionary(file => file.Name); + var rhsFiles = rhs.GetExistingFiles() + .ToDictionary(file => file.Name); + + Assert.IsTrue(lhsFiles.Keys.ToHashSet() + .SetEquals(rhsFiles.Keys.ToHashSet())); + + foreach (var (name, lhsFile) in lhsFiles) { + var rhsFile = rhsFiles[name]; + try { + AssertFilesAreIdentical_(lhsFile, rhsFile); + } catch (Exception ex) { + throw new Exception($"Found a change in file {name}: ", ex); + } + } + } + + private static void AssertFilesAreIdentical_( + IReadOnlyTreeFile lhs, + IReadOnlyTreeFile rhs) { + using var lhsStream = lhs.OpenRead(); + using var rhsStream = rhs.OpenRead(); + + Assert.AreEqual(lhsStream.Length, rhsStream.Length); + + var bytesToRead = sizeof(long); + int iterations = + (int) Math.Ceiling((double) lhsStream.Length / bytesToRead); + + long lhsLong = 0; + long rhsLong = 0; + + var lhsSpan = new Span(ref lhsLong).AsBytes(); + var rhsSpan = new Span(ref rhsLong).AsBytes(); + + for (int i = 0; i < iterations; i++) { + lhsStream.Read(lhsSpan); + rhsStream.Read(rhsSpan); + + if (lhsLong != rhsLong) { + Asserts.Fail( + $"Files with name \"{lhs.Name}\" are different around byte #: {i * bytesToRead}"); + } + } + } + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/testing/audio/AudioGoldenAssert.cs b/FinModelUtility/Fin/Fin/src/testing/audio/AudioGoldenAssert.cs new file mode 100644 index 000000000..79d049ee3 --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/testing/audio/AudioGoldenAssert.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using fin.audio.io; +using fin.audio.io.exporters.ogg; +using fin.audio.io.importers; +using fin.model.io.exporters; +using fin.model.io.exporters.assimp.indirect; +using fin.io; +using fin.testing.audio.stubbed; + +namespace fin.testing.audio { + public static class AudioGoldenAssert { + public static IEnumerable GetGoldenAudioBundles( + ISystemDirectory rootGoldenDirectory, + Func + gatherAudioBundleFromInputDirectory) + where TAudioBundle : IAudioFileBundle { + foreach (var goldenSubdir in + GoldenAssert.GetGoldenDirectories(rootGoldenDirectory)) { + var inputDirectory = goldenSubdir.AssertGetExistingSubdir("input"); + var audioBundle = gatherAudioBundleFromInputDirectory(inputDirectory); + + yield return audioBundle; + } + } + + /// + /// Asserts model goldens. Assumes that directories will be stored as the following: + /// + /// - {goldenDirectory} + /// - {goldenName1} + /// - input + /// - {raw golden files} + /// - output + /// - {exported files} + /// - {goldenName2} + /// ... + /// + public static void AssertExportGoldens( + ISystemDirectory rootGoldenDirectory, + IAudioImporter audioImporter, + Func + gatherAudioBundleFromInputDirectory) + where TAudioBundle : IAudioFileBundle { + foreach (var goldenSubdir in + GoldenAssert.GetGoldenDirectories(rootGoldenDirectory)) { + AudioGoldenAssert.AssertGolden(goldenSubdir, + audioImporter, + gatherAudioBundleFromInputDirectory); + } + } + + private static string EXTENSION = ".ogg"; + + public static void AssertGolden( + IFileHierarchyDirectory goldenSubdir, + IAudioImporter audioImporter, + Func + gatherAudioBundleFromInputDirectory) + where TAudioBundle : IAudioFileBundle { + using var audioManager = new StubbedAudioManager(); + + var inputDirectory = goldenSubdir.AssertGetExistingSubdir("input"); + var audioBundle = gatherAudioBundleFromInputDirectory(inputDirectory); + + var outputDirectory = goldenSubdir.AssertGetExistingSubdir("output"); + var hasGoldenExport = + outputDirectory.GetFilesWithFileType(EXTENSION).Any(); + + GoldenAssert.RunInTestDirectory( + goldenSubdir, + tmpDirectory => { + var targetDirectory = + hasGoldenExport ? tmpDirectory : outputDirectory.Impl; + + var audioBuffer + = audioImporter.ImportAudio(audioManager, audioBundle); + new OggAudioExporter() + .ExportAudio( + audioBuffer, + new FinFile( + Path.Combine(targetDirectory.FullPath, + $"{audioBundle.MainFile.NameWithoutExtension}{EXTENSION}"))); + + if (hasGoldenExport) { + GoldenAssert.AssertFilesInDirectoriesAreIdentical( + tmpDirectory, + outputDirectory.Impl); + } + }); + } + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/testing/audio/BAudioGoldenTests.cs b/FinModelUtility/Fin/Fin/src/testing/audio/BAudioGoldenTests.cs new file mode 100644 index 000000000..a6de5dcfa --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/testing/audio/BAudioGoldenTests.cs @@ -0,0 +1,15 @@ +using fin.audio.io; +using fin.audio.io.importers; +using fin.io; + +namespace fin.testing.audio { + public abstract class BAudioGoldenTests + : BGoldenTests + where TAudioFileBundle : IAudioFileBundle + where TAudioImporter : IAudioImporter, new() { + public void AssertGolden(IFileHierarchyDirectory goldenDirectory) + => AudioGoldenAssert.AssertGolden(goldenDirectory, + new TAudioImporter(), + this.GetFileBundleFromDirectory); + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/testing/audio/stubbed/StubbedAudioBuffer.cs b/FinModelUtility/Fin/Fin/src/testing/audio/stubbed/StubbedAudioBuffer.cs new file mode 100644 index 000000000..0007f858a --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/testing/audio/stubbed/StubbedAudioBuffer.cs @@ -0,0 +1,60 @@ +using System; + +using fin.audio; +using fin.util.asserts; + +namespace fin.testing.audio.stubbed { + public partial class StubbedAudioManager { + public IAudioBuffer CreateAudioBuffer() + => new StubbedAudioBuffer(); + + private class StubbedAudioBuffer : IAudioBuffer { + private short[][] channels_; + + public AudioChannelsType AudioChannelsType { get; private set; } + + public int Frequency { get; set; } + + public int LengthInSamples { get; private set; } + + public void SetPcm(short[][] channelSamples) { + switch (channelSamples.Length) { + case 1: { + this.SetMonoPcm(channelSamples[0]); + break; + } + case 2: { + this.SetStereoPcm(channelSamples[0], channelSamples[1]); + break; + } + default: throw new NotFiniteNumberException(); + } + } + + + public void SetMonoPcm(short[] samples) { + this.AudioChannelsType = AudioChannelsType.MONO; + this.LengthInSamples = samples.Length; + this.channels_ = [samples]; + } + + public void SetStereoPcm(short[] leftChannelSamples, + short[] rightChannelSamples) { + Asserts.Equal(leftChannelSamples.Length, + rightChannelSamples.Length, + "Expected the left/right channels to have the same number of samples!"); + + this.AudioChannelsType = AudioChannelsType.STEREO; + this.LengthInSamples = leftChannelSamples.Length; + this.channels_ = [leftChannelSamples, rightChannelSamples]; + } + + public short GetPcm(AudioChannelType channelType, int sampleOffset) + => this.channels_[channelType switch { + AudioChannelType.MONO => 0, + AudioChannelType.STEREO_LEFT => 0, + AudioChannelType.STEREO_RIGHT => 1 + }][sampleOffset]; + } + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/testing/audio/stubbed/StubbedAudioManager.cs b/FinModelUtility/Fin/Fin/src/testing/audio/stubbed/StubbedAudioManager.cs new file mode 100644 index 000000000..54d7d64ef --- /dev/null +++ b/FinModelUtility/Fin/Fin/src/testing/audio/stubbed/StubbedAudioManager.cs @@ -0,0 +1,27 @@ +using System; + +using fin.audio; + +namespace fin.testing.audio.stubbed { + public partial class StubbedAudioManager : IAudioManager { + public bool IsDisposed { get; private set; } + public IAudioPlayer AudioPlayer { get; } + + ~StubbedAudioManager() => this.ReleaseUnmanagedResources_(); + + public void Dispose() { + this.ReleaseUnmanagedResources_(); + GC.SuppressFinalize(this); + } + + private void ReleaseUnmanagedResources_() { + this.IsDisposed = true; + this.AudioPlayer?.Dispose(); + } + + public IJitAudioDataSource CreateJitAudioDataSource( + AudioChannelsType audioChannelsType, + int frequency) + => throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/FinModelUtility/Fin/Fin/src/testing/model/IModelGoldenTests.cs b/FinModelUtility/Fin/Fin/src/testing/model/BModelGoldenTests.cs similarity index 73% rename from FinModelUtility/Fin/Fin/src/testing/model/IModelGoldenTests.cs rename to FinModelUtility/Fin/Fin/src/testing/model/BModelGoldenTests.cs index 51a94329a..48d4ee79f 100644 --- a/FinModelUtility/Fin/Fin/src/testing/model/IModelGoldenTests.cs +++ b/FinModelUtility/Fin/Fin/src/testing/model/BModelGoldenTests.cs @@ -1,15 +1,8 @@ using fin.io; -using fin.io.bundles; using fin.model.io; using fin.model.io.importers; namespace fin.testing.model { - public abstract class BGoldenTests - where TFileBundle : IFileBundle { - public abstract TFileBundle GetFileBundleFromDirectory( - IFileHierarchyDirectory directory); - } - public abstract class BModelGoldenTests : BGoldenTests where TModelFileBundle : IModelFileBundle diff --git a/FinModelUtility/Fin/Fin/src/testing/model/ModelGoldenAssert.cs b/FinModelUtility/Fin/Fin/src/testing/model/ModelGoldenAssert.cs index 9b1beb866..28c6edbf7 100644 --- a/FinModelUtility/Fin/Fin/src/testing/model/ModelGoldenAssert.cs +++ b/FinModelUtility/Fin/Fin/src/testing/model/ModelGoldenAssert.cs @@ -2,64 +2,22 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; - -using CommunityToolkit.HighPerformance; using fin.model.io.exporters; using fin.model.io.exporters.assimp.indirect; using fin.io; using fin.model.io; using fin.model.io.importers; -using fin.util.asserts; -using fin.util.strings; - -using Microsoft.VisualStudio.TestTools.UnitTesting; namespace fin.testing.model { public static class ModelGoldenAssert { - private const string TMP_NAME = "tmp"; - - public static ISystemDirectory GetRootGoldensDirectory( - Assembly executingAssembly) { - var assemblyName = - executingAssembly.ManifestModule.Name.SubstringUpTo(".dll"); - - var executingAssemblyDll = new FinFile(executingAssembly.Location); - var executingAssemblyDir = executingAssemblyDll.AssertGetParent(); - - var currentDir = executingAssemblyDir; - while (currentDir.Name != assemblyName) { - currentDir = currentDir.AssertGetParent(); - } - - Assert.IsNotNull(currentDir); - - var gloTestsDir = currentDir; - var goldensDirectory = gloTestsDir.AssertGetExistingSubdir("goldens"); - - return goldensDirectory; - } - - public static IEnumerable GetGoldenDirectories( - ISystemDirectory rootGoldenDirectory) { - var hierarchy = FileHierarchy.From(rootGoldenDirectory); - return hierarchy.Root.GetExistingSubdirs() - .Where( - subdir => subdir.Name != TMP_NAME); - } - - public static IEnumerable - GetGoldenInputDirectories(ISystemDirectory rootGoldenDirectory) - => GetGoldenDirectories(rootGoldenDirectory) - .Select(subdir => subdir.AssertGetExistingSubdir("input")); - public static IEnumerable GetGoldenModelBundles( ISystemDirectory rootGoldenDirectory, Func gatherModelBundleFromInputDirectory) where TModelBundle : IModelFileBundle { - foreach (var goldenSubdir in GetGoldenDirectories(rootGoldenDirectory)) { + foreach (var goldenSubdir in + GoldenAssert.GetGoldenDirectories(rootGoldenDirectory)) { var inputDirectory = goldenSubdir.AssertGetExistingSubdir("input"); var modelBundle = gatherModelBundleFromInputDirectory(inputDirectory); @@ -86,7 +44,7 @@ public static void AssertExportGoldens( gatherModelBundleFromInputDirectory) where TModelBundle : IModelFileBundle { foreach (var goldenSubdir in - GetGoldenDirectories(rootGoldenDirectory)) { + GoldenAssert.GetGoldenDirectories(rootGoldenDirectory)) { ModelGoldenAssert.AssertGolden(goldenSubdir, modelImporter, gatherModelBundleFromInputDirectory); @@ -101,9 +59,6 @@ public static void AssertGolden( Func gatherModelBundleFromInputDirectory) where TModelBundle : IModelFileBundle { - var tmpDirectory = goldenSubdir.Impl.GetOrCreateSubdir(TMP_NAME); - tmpDirectory.DeleteContents(); - var inputDirectory = goldenSubdir.AssertGetExistingSubdir("input"); var modelBundle = gatherModelBundleFromInputDirectory(inputDirectory); @@ -112,81 +67,32 @@ public static void AssertGolden( outputDirectory.GetExistingFiles() .Any(file => EXTENSIONS.Contains(file.FileType)); - var targetDirectory = - hasGoldenExport ? tmpDirectory : outputDirectory.Impl; - - var model = modelImporter.Import(modelBundle); - new AssimpIndirectModelExporter() { - LowLevel = modelBundle.UseLowLevelExporter, - ForceGarbageCollection = modelBundle.ForceGarbageCollection, - }.ExportExtensions( - new ModelExporterParams { - Model = model, - OutputFile = - new FinFile(Path.Combine(targetDirectory.FullPath, - $"{modelBundle.MainFile.NameWithoutExtension}.foo")), - }, - EXTENSIONS, - true); - - if (hasGoldenExport) { - AssertFilesInDirectoriesAreIdentical_( - tmpDirectory, - outputDirectory.Impl); - } - - tmpDirectory.DeleteContents(); - tmpDirectory.Delete(); - } - - private static void AssertFilesInDirectoriesAreIdentical_( - ISystemDirectory lhs, - ISystemDirectory rhs) { - var lhsFiles = lhs.GetExistingFiles() - .ToDictionary(file => (string) file.Name); - var rhsFiles = rhs.GetExistingFiles() - .ToDictionary(file => (string) file.Name); - - Assert.IsTrue(lhsFiles.Keys.ToHashSet() - .SetEquals(rhsFiles.Keys.ToHashSet())); - - foreach (var (name, lhsFile) in lhsFiles) { - var rhsFile = rhsFiles[name]; - try { - AssertFilesAreIdentical_(lhsFile, rhsFile); - } catch (Exception ex) { - throw new Exception($"Found a change in file {name}: ", ex); - } - } - } - - private static void AssertFilesAreIdentical_( - IReadOnlyTreeFile lhs, - IReadOnlyTreeFile rhs) { - using var lhsStream = lhs.OpenRead(); - using var rhsStream = rhs.OpenRead(); - - Assert.AreEqual(lhsStream.Length, rhsStream.Length); - - var bytesToRead = sizeof(long); - int iterations = - (int) Math.Ceiling((double) lhsStream.Length / bytesToRead); - - long lhsLong = 0; - long rhsLong = 0; - - var lhsSpan = new Span(ref lhsLong).AsBytes(); - var rhsSpan = new Span(ref rhsLong).AsBytes(); - - for (int i = 0; i < iterations; i++) { - lhsStream.Read(lhsSpan); - rhsStream.Read(rhsSpan); - - if (lhsLong != rhsLong) { - Asserts.Fail( - $"Files with name \"{lhs.Name}\" are different around byte #: {i * bytesToRead}"); - } - } + GoldenAssert.RunInTestDirectory( + goldenSubdir, + tmpDirectory => { + var targetDirectory = + hasGoldenExport ? tmpDirectory : outputDirectory.Impl; + + var model = modelImporter.Import(modelBundle); + new AssimpIndirectModelExporter() { + LowLevel = modelBundle.UseLowLevelExporter, + ForceGarbageCollection = modelBundle.ForceGarbageCollection, + }.ExportExtensions( + new ModelExporterParams { + Model = model, + OutputFile = + new FinFile(Path.Combine(targetDirectory.FullPath, + $"{modelBundle.MainFile.NameWithoutExtension}.foo")), + }, + EXTENSIONS, + true); + + if (hasGoldenExport) { + GoldenAssert.AssertFilesInDirectoriesAreIdentical( + tmpDirectory, + outputDirectory.Impl); + } + }); } } } \ No newline at end of file diff --git a/FinModelUtility/Formats/Ast/Ast Tests/Ast Tests.csproj b/FinModelUtility/Formats/Ast/Ast Tests/Ast Tests.csproj index 8eb5537f7..03eca73f4 100644 --- a/FinModelUtility/Formats/Ast/Ast Tests/Ast Tests.csproj +++ b/FinModelUtility/Formats/Ast/Ast Tests/Ast Tests.csproj @@ -2,7 +2,7 @@ net8.0 - Ast_Tests + ast enable enable diff --git a/FinModelUtility/Formats/Ast/Ast Tests/AstGoldenTests.cs b/FinModelUtility/Formats/Ast/Ast Tests/AstGoldenTests.cs new file mode 100644 index 000000000..884bdc781 --- /dev/null +++ b/FinModelUtility/Formats/Ast/Ast Tests/AstGoldenTests.cs @@ -0,0 +1,32 @@ +using System.Reflection; + +using ast.api; + +using fin.io; +using fin.testing; +using fin.testing.audio; + +namespace ast { + public class AstGoldenTests + : BAudioGoldenTests { + [Test] + [TestCaseSource(nameof(GetGoldenDirectories_))] + public void TestExportsGoldenAsExpected( + IFileHierarchyDirectory goldenDirectory) + => this.AssertGolden(goldenDirectory); + + public override AstAudioFileBundle GetFileBundleFromDirectory( + IFileHierarchyDirectory directory) + => new() { + GameName = directory.Parent.Parent.Name, + AstFile = directory.FilesWithExtension(".ast").Single(), + }; + + private static IFileHierarchyDirectory[] GetGoldenDirectories_() + => GoldenAssert + .GetGoldenDirectories( + GoldenAssert + .GetRootGoldensDirectory(Assembly.GetExecutingAssembly())) + .ToArray(); + } +} \ No newline at end of file diff --git a/FinModelUtility/Formats/Ast/Ast Tests/PlaceholderTests.cs b/FinModelUtility/Formats/Ast/Ast Tests/PlaceholderTests.cs deleted file mode 100644 index 54e5d35c9..000000000 --- a/FinModelUtility/Formats/Ast/Ast Tests/PlaceholderTests.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ast_Tests { - public class Tests { - [Test] - public void Test1() { - Assert.Pass(); - } - } -} \ No newline at end of file diff --git a/FinModelUtility/Formats/Ast/Ast Tests/goldens/BATTLE3_0/input/BATTLE3_0.ast b/FinModelUtility/Formats/Ast/Ast Tests/goldens/BATTLE3_0/input/BATTLE3_0.ast new file mode 100644 index 000000000..6799eb5f1 Binary files /dev/null and b/FinModelUtility/Formats/Ast/Ast Tests/goldens/BATTLE3_0/input/BATTLE3_0.ast differ diff --git a/FinModelUtility/Formats/Ast/Ast Tests/goldens/BATTLE3_0/output/BATTLE3_0.ogg b/FinModelUtility/Formats/Ast/Ast Tests/goldens/BATTLE3_0/output/BATTLE3_0.ogg new file mode 100644 index 000000000..fb16dc433 Binary files /dev/null and b/FinModelUtility/Formats/Ast/Ast Tests/goldens/BATTLE3_0/output/BATTLE3_0.ogg differ diff --git a/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CmbModelGoldenTests.cs b/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CmbModelGoldenTests.cs index 964d61bee..9fb65f910 100644 --- a/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CmbModelGoldenTests.cs +++ b/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CmbModelGoldenTests.cs @@ -3,6 +3,7 @@ using cmb.api; using fin.io; +using fin.testing; using fin.testing.model; namespace cmb { @@ -10,7 +11,8 @@ public class CmbModelGoldenTests : BModelGoldenTests { [Test] [TestCaseSource(nameof(GetGoldenDirectories_))] - public void TestExportsGoldenAsExpected(IFileHierarchyDirectory goldenDirectory) + public void TestExportsGoldenAsExpected( + IFileHierarchyDirectory goldenDirectory) => this.AssertGolden(goldenDirectory); public override CmbModelFileBundle GetFileBundleFromDirectory( @@ -26,12 +28,12 @@ public override CmbModelFileBundle GetFileBundleFromDirectory( private static IFileHierarchyDirectory[] GetGoldenDirectories_() { var rootGoldenDirectory - = ModelGoldenAssert + = GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()) .AssertGetExistingSubdir("cmb"); - return ModelGoldenAssert.GetGoldenDirectories(rootGoldenDirectory) - .SelectMany(dir => dir.GetExistingSubdirs()) - .ToArray(); + return GoldenAssert.GetGoldenDirectories(rootGoldenDirectory) + .SelectMany(dir => dir.GetExistingSubdirs()) + .ToArray(); } } } \ No newline at end of file diff --git a/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CtxbGoldenTests.cs b/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CtxbGoldenTests.cs index 4aada5084..ccb68b3d3 100644 --- a/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CtxbGoldenTests.cs +++ b/FinModelUtility/Formats/Cmb/Cmb Tests/tst/CtxbGoldenTests.cs @@ -4,6 +4,7 @@ using cmb.schema.ctxb; using fin.io; +using fin.testing; using fin.testing.model; using schema.binary; @@ -15,7 +16,8 @@ namespace cmb { public class CtxbGoldenTests { [Test] [TestCaseSource(nameof(GetGoldenFiles_))] - public async Task TestExportsGoldenAsExpected(IReadOnlySystemFile goldenFile) { + public async Task TestExportsGoldenAsExpected( + IReadOnlySystemFile goldenFile) { var goldenGameDir = goldenFile.AssertGetParent(); CmbHeader.Version = goldenGameDir.Name switch { @@ -30,7 +32,7 @@ public async Task TestExportsGoldenAsExpected(IReadOnlySystemFile goldenFile) { private static IReadOnlySystemFile[] GetGoldenFiles_() { var rootGoldenDirectory - = ModelGoldenAssert + = GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()) .AssertGetExistingSubdir("ctxb"); return rootGoldenDirectory.GetExistingSubdirs() diff --git a/FinModelUtility/Formats/Cmb/Cmb Tests/tst/ShpaGoldenTests.cs b/FinModelUtility/Formats/Cmb/Cmb Tests/tst/ShpaGoldenTests.cs index 9e8769002..c9362c7a1 100644 --- a/FinModelUtility/Formats/Cmb/Cmb Tests/tst/ShpaGoldenTests.cs +++ b/FinModelUtility/Formats/Cmb/Cmb Tests/tst/ShpaGoldenTests.cs @@ -15,7 +15,8 @@ namespace cmb { public class ShpaGoldenTests { [Test] [TestCaseSource(nameof(GetGoldenFiles_))] - public async Task TestExportsGoldenAsExpected(IReadOnlySystemFile goldenFile) { + public async Task TestExportsGoldenAsExpected( + IReadOnlySystemFile goldenFile) { var goldenGameDir = goldenFile.AssertGetParent(); CmbHeader.Version = goldenGameDir.Name switch { @@ -30,7 +31,7 @@ await SchemaTesting.ReadsAndWritesIdentically( private static IReadOnlySystemFile[] GetGoldenFiles_() { var rootGoldenDirectory - = ModelGoldenAssert + = GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()) .AssertGetExistingSubdir("shpa"); return rootGoldenDirectory.GetExistingSubdirs() diff --git a/FinModelUtility/Formats/Dat/Dat Tests/DatModelGoldenTests.cs b/FinModelUtility/Formats/Dat/Dat Tests/DatModelGoldenTests.cs index 24c1aeb85..687fdf8ae 100644 --- a/FinModelUtility/Formats/Dat/Dat Tests/DatModelGoldenTests.cs +++ b/FinModelUtility/Formats/Dat/Dat Tests/DatModelGoldenTests.cs @@ -5,6 +5,8 @@ using dat.api; +using fin.testing; + namespace dat { public class DatModelGoldenTests : BModelGoldenTests { @@ -22,9 +24,9 @@ public override DatModelFileBundle GetFileBundleFromDirectory( }; private static IFileHierarchyDirectory[] GetGoldenDirectories_() - => ModelGoldenAssert + => GoldenAssert .GetGoldenDirectories( - ModelGoldenAssert + GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly())) .SelectMany(dir => dir.GetExistingSubdirs()) .ToArray(); diff --git a/FinModelUtility/Formats/Glo/Glo Tests/tst/GloModelGoldenTests.cs b/FinModelUtility/Formats/Glo/Glo Tests/tst/GloModelGoldenTests.cs index e021d8195..f318a267e 100644 --- a/FinModelUtility/Formats/Glo/Glo Tests/tst/GloModelGoldenTests.cs +++ b/FinModelUtility/Formats/Glo/Glo Tests/tst/GloModelGoldenTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using fin.io; +using fin.testing; using fin.testing.model; using glo.api; @@ -13,7 +14,8 @@ namespace glo { public class - GloModelGoldenTests : BModelGoldenTests { [Test] [TestCaseSource(nameof(GetGoldenDirectories_))] @@ -29,7 +31,8 @@ public async Task TestReadsAndWritesIdentically( [Test] [TestCaseSource(nameof(GetGoldenDirectories_))] - public void TestExportsGoldenAsExpected(IFileHierarchyDirectory goldenDirectory) + public void TestExportsGoldenAsExpected( + IFileHierarchyDirectory goldenDirectory) => this.AssertGolden(goldenDirectory); public override GloModelFileBundle GetFileBundleFromDirectory( @@ -38,10 +41,10 @@ public override GloModelFileBundle GetFileBundleFromDirectory( new[] { directory }); private static IFileHierarchyDirectory[] GetGoldenDirectories_() { - var rootGoldenDirectory = ModelGoldenAssert.GetRootGoldensDirectory( + var rootGoldenDirectory = GoldenAssert.GetRootGoldensDirectory( Assembly.GetExecutingAssembly()); - return ModelGoldenAssert.GetGoldenDirectories(rootGoldenDirectory) - .ToArray(); + return GoldenAssert.GetGoldenDirectories(rootGoldenDirectory) + .ToArray(); } } } \ No newline at end of file diff --git a/FinModelUtility/Formats/JSystem/JSystem Tests/BmdModelGoldenTests.cs b/FinModelUtility/Formats/JSystem/JSystem Tests/BmdModelGoldenTests.cs index 60d3fd08b..5eacc38e6 100644 --- a/FinModelUtility/Formats/JSystem/JSystem Tests/BmdModelGoldenTests.cs +++ b/FinModelUtility/Formats/JSystem/JSystem Tests/BmdModelGoldenTests.cs @@ -50,10 +50,10 @@ public override BmdModelFileBundle GetFileBundleFromDirectory( private static IFileHierarchyDirectory[] GetGoldenDirectories_() { var rootGoldenDirectory - = ModelGoldenAssert + = GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()); - return ModelGoldenAssert.GetGoldenDirectories(rootGoldenDirectory) - .ToArray(); + return GoldenAssert.GetGoldenDirectories(rootGoldenDirectory) + .ToArray(); } } } \ No newline at end of file diff --git a/FinModelUtility/Formats/Mod/Mod Tests/ModModelGoldenTests.cs b/FinModelUtility/Formats/Mod/Mod Tests/ModModelGoldenTests.cs index 17242c47f..a8989376e 100644 --- a/FinModelUtility/Formats/Mod/Mod Tests/ModModelGoldenTests.cs +++ b/FinModelUtility/Formats/Mod/Mod Tests/ModModelGoldenTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using fin.io; +using fin.testing; using fin.testing.model; using mod.api; @@ -27,10 +28,10 @@ public override ModModelFileBundle GetFileBundleFromDirectory( private static IFileHierarchyDirectory[] GetGoldenDirectories_() { var rootGoldenDirectory - = ModelGoldenAssert + = GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()); - return ModelGoldenAssert.GetGoldenDirectories(rootGoldenDirectory) - .ToArray(); + return GoldenAssert.GetGoldenDirectories(rootGoldenDirectory) + .ToArray(); } } } \ No newline at end of file diff --git a/FinModelUtility/Formats/Modl/Modl Tests/ModlModelGoldenTests.cs b/FinModelUtility/Formats/Modl/Modl Tests/ModlModelGoldenTests.cs index 1cf0e9d49..4740e9aac 100644 --- a/FinModelUtility/Formats/Modl/Modl Tests/ModlModelGoldenTests.cs +++ b/FinModelUtility/Formats/Modl/Modl Tests/ModlModelGoldenTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using fin.io; +using fin.testing; using fin.testing.model; using modl.api; @@ -27,9 +28,9 @@ public override ModlModelFileBundle GetFileBundleFromDirectory( }; private static IFileHierarchyDirectory[] GetGoldenDirectories_() - => ModelGoldenAssert + => GoldenAssert .GetGoldenDirectories( - ModelGoldenAssert + GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()) .AssertGetExistingSubdir("modl")) .SelectMany(dir => dir.GetExistingSubdirs()) diff --git a/FinModelUtility/Formats/Modl/Modl Tests/OutModelGoldenTests.cs b/FinModelUtility/Formats/Modl/Modl Tests/OutModelGoldenTests.cs index e91e5cfa4..67f63c962 100644 --- a/FinModelUtility/Formats/Modl/Modl Tests/OutModelGoldenTests.cs +++ b/FinModelUtility/Formats/Modl/Modl Tests/OutModelGoldenTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using fin.io; +using fin.testing; using fin.testing.model; using fin.util.enumerables; @@ -28,9 +29,9 @@ public override OutModelFileBundle GetFileBundleFromDirectory( }; private static IFileHierarchyDirectory[] GetGoldenDirectories_() - => ModelGoldenAssert + => GoldenAssert .GetGoldenDirectories( - ModelGoldenAssert + GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()) .AssertGetExistingSubdir("out")) .SelectMany(dir => dir.GetExistingSubdirs()) diff --git a/FinModelUtility/Games/HaloWars/HaloWars Tests/XtdModelGoldenTests.cs b/FinModelUtility/Games/HaloWars/HaloWars Tests/XtdModelGoldenTests.cs index 360ee0872..80ce3b077 100644 --- a/FinModelUtility/Games/HaloWars/HaloWars Tests/XtdModelGoldenTests.cs +++ b/FinModelUtility/Games/HaloWars/HaloWars Tests/XtdModelGoldenTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using fin.io; +using fin.testing; using fin.testing.model; using hw.api; @@ -20,9 +21,9 @@ public override XtdModelFileBundle GetFileBundleFromDirectory( directory.FilesWithExtension(".xtt").Single()); private static IFileHierarchyDirectory[] GetGoldenDirectories_() - => ModelGoldenAssert + => GoldenAssert .GetGoldenDirectories( - ModelGoldenAssert + GoldenAssert .GetRootGoldensDirectory(Assembly.GetExecutingAssembly()) .AssertGetExistingSubdir("xtd")) .ToArray();