diff --git a/.gitignore b/.gitignore index 25dd304..ef7150b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ doc/html/** doc/latex/** +**.sln.DotSettings.user diff --git a/ReFuel.Gltf.sln b/ReFuel.Gltf.sln index 80cc54e..cba67c0 100644 --- a/ReFuel.Gltf.sln +++ b/ReFuel.Gltf.sln @@ -5,18 +5,49 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReFuel.Gltf", "ReFuel.Gltf\ReFuel.Gltf.csproj", "{7DDA6046-BC8D-4969-BF6F-8450D515E8AE}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReFuel.Gltf.Tests", "tests\ReFuel.Gltf.Tests\ReFuel.Gltf.Tests.csproj", "{5170E91E-B9F9-48B9-AEDB-A426AAB939B2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|x64.Build.0 = Debug|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Debug|x86.Build.0 = Debug|Any CPU {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|Any CPU.ActiveCfg = Release|Any CPU {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|Any CPU.Build.0 = Release|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|x64.ActiveCfg = Release|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|x64.Build.0 = Release|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|x86.ActiveCfg = Release|Any CPU + {7DDA6046-BC8D-4969-BF6F-8450D515E8AE}.Release|x86.Build.0 = Release|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Debug|x64.ActiveCfg = Debug|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Debug|x64.Build.0 = Debug|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Debug|x86.ActiveCfg = Debug|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Debug|x86.Build.0 = Debug|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Release|Any CPU.Build.0 = Release|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Release|x64.ActiveCfg = Release|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Release|x64.Build.0 = Release|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Release|x86.ActiveCfg = Release|Any CPU + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {5170E91E-B9F9-48B9-AEDB-A426AAB939B2} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection EndGlobal diff --git a/tests/ReFuel.Gltf.Tests/GltfTestDocument.cs b/tests/ReFuel.Gltf.Tests/GltfTestDocument.cs new file mode 100644 index 0000000..06fc78e --- /dev/null +++ b/tests/ReFuel.Gltf.Tests/GltfTestDocument.cs @@ -0,0 +1,85 @@ + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using YamlDotNet.Core; +using YamlDotNet.RepresentationModel; +using YamlDotNet.Serialization; + +namespace ReFuel.Gltf.Tests; + +[DebuggerDisplay("{Kind,nq} path={DocumentUnderTestPath}")] +public class GltfTestDocument(FileInfo documentUnderTestPath) : IDisposable +{ + public FileInfo DocumentUnderTestPath { get; } = documentUnderTestPath; + public FileInfo? SummaryDocumentFile { get; init; } = FindSummary(documentUnderTestPath); + public DirectoryInfo WorkingDirectory { get; init; } = documentUnderTestPath.Directory ?? new DirectoryInfo(Environment.CurrentDirectory); + public GltfDocumentKind Kind { get; init; } = documentUnderTestPath.Extension.ToLowerInvariant() switch + { + ".gltf" => GltfDocumentKind.Json, + ".glb" => GltfDocumentKind.Binary, + _ => GltfDocumentKind.Any, + }; + + public FileStream? Stream { get; private set; } + public GltfDocument? DocumentUnderTest { get; private set; } + public YamlDocument? SummaryDocument { get; private set; } + public ValidationDocument? ValidationDocument { get; private set; } + + public bool HasValidation => SummaryDocumentFile != null; + + public GltfDocument Parse() + { + Stream = DocumentUnderTestPath.OpenRead(); + return DocumentUnderTest = GltfDocument.Open(Stream); + } + + [MemberNotNullWhen(true, nameof(ValidationDocument))] + public bool LoadValidationDocument() + { + if (SummaryDocumentFile == null) + return false; + + using StreamReader reader = new StreamReader(SummaryDocumentFile.OpenRead()); + ValidationDocument = Deserializer.Deserialize(reader.ReadToEnd()); + + return true; + } + + public void Dispose() + { + DocumentUnderTest?.Dispose(); + DocumentUnderTest = null; + + Stream?.Dispose(); + Stream = null; + } + + private static FileInfo? FindSummary(FileInfo documentUnderTest) + { + return + documentUnderTest.Directory? + .EnumerateFiles() + .FirstOrDefault(x => string.Equals(x.Name, "conversion.yaml", StringComparison.InvariantCultureIgnoreCase)); + } + + public override string ToString() + { + string kind = Kind switch + { + GltfDocumentKind.Json => "Gltf JSON", + GltfDocumentKind.Binary => "Gltf Binary", + _ => "Unknown Gltf", + }; + + return $"{kind} \"{DocumentUnderTestPath.Name}\" HasValidation={(HasValidation ? "Yes" : "No")}"; + } + + private static IDeserializer Deserializer { get; } + + static GltfTestDocument() + { + Deserializer = new DeserializerBuilder() + .IgnoreUnmatchedProperties() + .Build(); + } +} diff --git a/tests/ReFuel.Gltf.Tests/MSTestSettings.cs b/tests/ReFuel.Gltf.Tests/MSTestSettings.cs new file mode 100644 index 0000000..aaf278c --- /dev/null +++ b/tests/ReFuel.Gltf.Tests/MSTestSettings.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/tests/ReFuel.Gltf.Tests/ParseCompliantDocuments.cs b/tests/ReFuel.Gltf.Tests/ParseCompliantDocuments.cs new file mode 100644 index 0000000..1d87960 --- /dev/null +++ b/tests/ReFuel.Gltf.Tests/ParseCompliantDocuments.cs @@ -0,0 +1,75 @@ +using System.Diagnostics; +using Microsoft.Extensions.FileSystemGlobbing; +using Microsoft.Extensions.FileSystemGlobbing.Abstractions; + +namespace ReFuel.Gltf.Tests; + +[TestClass] +public sealed class ParseCompliantDocuments +{ + public static string DocumentsDirectory { get; } = "../../../assets/gltf-research-scenes/scenes"; + public static string CoreDocumentGlob { get; } = "**/gltf/*_core.gltf"; + public static string ExtendedDocumentGlob { get; } = "**/gltf/*_extended.gltf"; + + public static IEnumerable FetchTestDocuments(string glob) + { + Matcher matcher = new Matcher(); + matcher.AddInclude(glob); + + PatternMatchingResult result = matcher.Execute(new DirectoryInfoWrapper(new DirectoryInfo(DocumentsDirectory))); + return result.Files.Select(x => new GltfTestDocument(new FileInfo(Path.Combine(DocumentsDirectory, x.Path)))); + } + + public static IEnumerable Documents => FetchTestDocuments(ExtendedDocumentGlob); + + // [TestMethod] + // [TestCategory("Core")] + // [DynamicData(nameof(CoreDocuments))] + // public void ParseCoreDocuments(GltfTestDocument doc) + // { + // doc.Parse().Dispose(); + // } + + [TestMethod] + [DynamicData(nameof(Documents))] + public void ParseDocuments(GltfTestDocument doc) + { + doc.Parse().Dispose(); + } + + [TestMethod] + [DynamicData(nameof(Documents))] + public void AssertExtensionCount(GltfTestDocument doc) + { + GltfDocument gltf = doc.Parse(); + + if (!doc.LoadValidationDocument()) + Assert.Inconclusive(); + + Assert.HasCount(doc.ValidationDocument.Outputs.ExtendedExtensions.Count, gltf.Extensions); + } + + [TestMethod] + [DynamicData(nameof(Documents))] + public void AssertMaterialCount(GltfTestDocument doc) + { + GltfDocument gltf = doc.Parse(); + + if (!doc.LoadValidationDocument()) + Assert.Inconclusive(); + + Assert.HasCount(doc.ValidationDocument.Summary.Materials, gltf.Materials); + } + + [TestMethod] + [DynamicData(nameof(Documents))] + public void AssertTexturesCount(GltfTestDocument doc) + { + GltfDocument gltf = doc.Parse(); + + if (!doc.LoadValidationDocument()) + Assert.Inconclusive(); + + Assert.HasCount(doc.ValidationDocument.Summary.Textures, gltf.Images); + } +} diff --git a/tests/ReFuel.Gltf.Tests/ReFuel.Gltf.Tests.csproj b/tests/ReFuel.Gltf.Tests/ReFuel.Gltf.Tests.csproj new file mode 100644 index 0000000..72ffc73 --- /dev/null +++ b/tests/ReFuel.Gltf.Tests/ReFuel.Gltf.Tests.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + latest + enable + enable + + + + + + + + + + + + + + + + + diff --git a/tests/ReFuel.Gltf.Tests/ValidationDocument.cs b/tests/ReFuel.Gltf.Tests/ValidationDocument.cs new file mode 100644 index 0000000..e3ab3df --- /dev/null +++ b/tests/ReFuel.Gltf.Tests/ValidationDocument.cs @@ -0,0 +1,41 @@ +using YamlDotNet.Serialization; + +namespace ReFuel.Gltf.Tests +{ + [YamlSerializable] + public class ValidationDocument + { + [YamlMember(Alias = "summary")] public ValidationSummary Summary { get; set; } = new ValidationSummary(); + [YamlMember(Alias = "outputs")] public ValidationOutputs Outputs { get; set; } = new ValidationOutputs(); + + [YamlSerializable] + public class ValidationSummary + { + [YamlMember(Alias = "meshes")] + public int Meshes { get; set; } + + [YamlMember(Alias = "triangles")] + public int Triangles { get; set; } + + [YamlMember(Alias = "materials")] + public int Materials { get; set; } + + [YamlMember(Alias = "textures")] + public int Textures { get; set; } + + [YamlMember(Alias = "lights")] + public int Lights { get; set; } + } + + [YamlSerializable] + public class ValidationOutputs + { + [YamlMember(Alias = "core")] + public string Core { get; set; } = ""; + [YamlMember(Alias = "extended")] + public string Extended { get; set; } = ""; + [YamlMember(Alias = "extended_extensions")] + public List ExtendedExtensions { get; set; } = new List(); + } + } +} \ No newline at end of file