Browse Source

+Progress improving unit tests.

Vicente Penades 5 years ago
parent
commit
f04d1db4b2

+ 10 - 7
src/SharpGLTF.Core/IO/BinarySerialization.cs

@@ -63,7 +63,7 @@ namespace SharpGLTF.Schema2
 
 
                 var chunks = new Dictionary<uint, Byte[]>();
                 var chunks = new Dictionary<uint, Byte[]>();
 
 
-                // keep reading until EndOfFile exception
+                // keep reading until EndOfFile
                 while (true)
                 while (true)
                 {
                 {
                     if (binaryReader.PeekChar() < 0) break;
                     if (binaryReader.PeekChar() < 0) break;
@@ -72,16 +72,21 @@ namespace SharpGLTF.Schema2
 
 
                     if ((chunkLength & 3) != 0)
                     if ((chunkLength & 3) != 0)
                     {
                     {
-                        throw new InvalidDataException($"The chunk must be padded to 4 bytes: {chunkLength}");
+                        throw new Validation.SchemaException(null, $"The chunk must be padded to 4 bytes: {chunkLength}");
                     }
                     }
 
 
                     uint chunkId = binaryReader.ReadUInt32();
                     uint chunkId = binaryReader.ReadUInt32();
 
 
+                    if (chunks.ContainsKey(chunkId)) throw new Validation.SchemaException(null, $"Duplicated chunk found {chunkId}");
+
                     var data = binaryReader.ReadBytes((int)chunkLength);
                     var data = binaryReader.ReadBytes((int)chunkLength);
 
 
                     chunks[chunkId] = data;
                     chunks[chunkId] = data;
                 }
                 }
 
 
+                if (!chunks.ContainsKey(CHUNKJSON)) throw new Validation.SchemaException(null, "JSON Chunk chunk not found");
+                // if (!chunks.ContainsKey(CHUNKBIN)) throw new Validation.SchemaException(null, "BIN Chunk chunk not found");
+
                 return chunks;
                 return chunks;
             }
             }
         }
         }
@@ -91,17 +96,15 @@ namespace SharpGLTF.Schema2
             Guard.NotNull(binaryReader, nameof(binaryReader));
             Guard.NotNull(binaryReader, nameof(binaryReader));
 
 
             uint magic = binaryReader.ReadUInt32();
             uint magic = binaryReader.ReadUInt32();
-
-            Guard.IsTrue(magic == GLTFHEADER, nameof(magic), $"Unexpected magic number: {magic}");
+            if (magic != GLTFHEADER) throw new Validation.SchemaException(null, $"Unexpected magic number: {magic}");
 
 
             uint version = binaryReader.ReadUInt32();
             uint version = binaryReader.ReadUInt32();
-
-            Guard.IsTrue(version == GLTFVERSION2, nameof(version), $"Unknown version number: {version}");
+            if (version != GLTFVERSION2) throw new Validation.SchemaException(null, $"Unknown version number: {version}");
 
 
             uint length = binaryReader.ReadUInt32();
             uint length = binaryReader.ReadUInt32();
             long fileLength = binaryReader.BaseStream.Length;
             long fileLength = binaryReader.BaseStream.Length;
 
 
-            Guard.IsTrue(length == fileLength, nameof(length), $"The specified length of the file ({length}) is not equal to the actual length of the file ({fileLength}).");
+            if (length != fileLength) throw new Validation.SchemaException(null, $"The specified length of the file ({length}) is not equal to the actual length of the file ({fileLength}).");
         }
         }
 
 
         #endregion
         #endregion

+ 3 - 4
src/SharpGLTF.Core/IO/JsonCollections.cs

@@ -8,16 +8,15 @@ namespace SharpGLTF.IO
 {
 {
     static class JsonUtils
     static class JsonUtils
     {
     {
-        public static ArraySegment<Byte> ReadBytesToEnd(this System.IO.Stream s)
+        public static Memory<Byte> ReadBytesToEnd(this System.IO.Stream s)
         {
         {
             using (var m = new System.IO.MemoryStream())
             using (var m = new System.IO.MemoryStream())
             {
             {
                 s.CopyTo(m);
                 s.CopyTo(m);
-                if (m.TryGetBuffer(out ArraySegment<Byte> segment)) return segment;
 
 
-                var array = m.ToArray();
+                if (m.TryGetBuffer(out ArraySegment<Byte> segment)) return segment;
 
 
-                return new ArraySegment<byte>(array);
+                return m.ToArray();
             }
             }
         }
         }
 
 

+ 36 - 12
src/SharpGLTF.Core/IO/ReadContext.cs

@@ -191,30 +191,52 @@ namespace SharpGLTF.IO
         {
         {
             Guard.NotNull(stream, nameof(stream));
             Guard.NotNull(stream, nameof(stream));
 
 
-            var chunks = BinarySerialization.ReadBinaryFile(stream);
+            IReadOnlyDictionary<uint, Byte[]> chunks = null;
+
+            try
+            {
+                chunks = BinarySerialization.ReadBinaryFile(stream);
+            }
+            catch (System.IO.EndOfStreamException ex)
+            {
+                var vr = new Validation.ValidationResult(null, this.Validation);
+                vr.AddError(new Validation.SchemaException(null, "Unexpected EOF"));
+                return (null, vr);
+            }
+            catch (Validation.SchemaException ex)
+            {
+                var vr = new Validation.ValidationResult(null, this.Validation);
+                vr.AddError(ex);
+                return (null, vr);
+            }
 
 
             var context = this;
             var context = this;
 
 
             if (chunks.ContainsKey(BinarySerialization.CHUNKBIN))
             if (chunks.ContainsKey(BinarySerialization.CHUNKBIN))
             {
             {
-                context = new ReadContext(context); // clone instance
-                context._BinaryChunk = chunks[BinarySerialization.CHUNKBIN];
+                // clone self
+                var binChunk = chunks[BinarySerialization.CHUNKBIN];
+                context = new ReadContext(context);
+                context._BinaryChunk = binChunk;
             }
             }
 
 
             var jsonChunk = chunks[BinarySerialization.CHUNKJSON];
             var jsonChunk = chunks[BinarySerialization.CHUNKJSON];
 
 
-            return context._Read(new BYTES(jsonChunk));
+            return context._Read(jsonChunk);
         }
         }
 
 
-        private (SCHEMA2 Model, Validation.ValidationResult Validation) _Read(BYTES jsonUtf8Bytes)
+        private (SCHEMA2 Model, Validation.ValidationResult Validation) _Read(ReadOnlyMemory<Byte> jsonUtf8Bytes)
         {
         {
-            Guard.NotNull(jsonUtf8Bytes, nameof(jsonUtf8Bytes));
-
             var root = new SCHEMA2();
             var root = new SCHEMA2();
 
 
             var vcontext = new Validation.ValidationResult(root, this.Validation);
             var vcontext = new Validation.ValidationResult(root, this.Validation);
 
 
-            var reader = new Utf8JsonReader(jsonUtf8Bytes);
+            if (jsonUtf8Bytes.IsEmpty)
+            {
+                vcontext.AddError(new Validation.SchemaException(null, "JSon is empty."));
+            }
+
+            var reader = new Utf8JsonReader(jsonUtf8Bytes.Span);
 
 
             try
             try
             {
             {
@@ -233,6 +255,10 @@ namespace SharpGLTF.IO
                 return (null, vcontext);
                 return (null, vcontext);
             }
             }
 
 
+            // binary chunk check
+
+            foreach (var b in root.LogicalBuffers) b.OnValidateBinaryChunk(vcontext.GetContext(root), this._BinaryChunk);
+
             // schema validation
             // schema validation
 
 
             root.ValidateReferences(vcontext.GetContext());
             root.ValidateReferences(vcontext.GetContext());
@@ -278,7 +304,7 @@ namespace SharpGLTF.IO
             }
             }
         }
         }
 
 
-        public static BYTES ReadJsonBytes(Stream stream)
+        public static ReadOnlyMemory<Byte> ReadJsonBytes(Stream stream)
         {
         {
             Guard.NotNull(stream, nameof(stream));
             Guard.NotNull(stream, nameof(stream));
 
 
@@ -288,9 +314,7 @@ namespace SharpGLTF.IO
             {
             {
                 var chunks = BinarySerialization.ReadBinaryFile(stream);
                 var chunks = BinarySerialization.ReadBinaryFile(stream);
 
 
-                var jsonChunk = chunks[BinarySerialization.CHUNKJSON];
-
-                return new BYTES(jsonChunk);
+                return chunks[BinarySerialization.CHUNKJSON];
             }
             }
 
 
             return stream.ReadBytesToEnd();
             return stream.ReadBytesToEnd();

+ 11 - 0
src/SharpGLTF.Core/Memory/MemoryImage.cs

@@ -25,10 +25,21 @@ namespace SharpGLTF.Memory
         const string MIME_DDS = "image/vnd-ms.dds";
         const string MIME_DDS = "image/vnd-ms.dds";
         const string MIME_WEBP = "image/webp";
         const string MIME_WEBP = "image/webp";
 
 
+        /// <summary>
+        /// Represents a 4x4 white PNG image.
+        /// </summary>
         private const string DEFAULT_PNG_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAHXpUWHRUaXRsZQAACJlzSU1LLM0pCUmtKCktSgUAKVIFt/VCuZ8AAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAANElEQVQoz2O8cuUKAwxoa2vD2VevXsUqzsRAIqC9Bsb///8TdDey+CD0Awsx7h6NB5prAADPsx0VAB8VRQAAAABJRU5ErkJggg==";
         private const string DEFAULT_PNG_IMAGE = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAHXpUWHRUaXRsZQAACJlzSU1LLM0pCUmtKCktSgUAKVIFt/VCuZ8AAAAoelRYdEF1dGhvcgAACJkLy0xOzStJVQhIzUtMSS1WcCzKTc1Lzy8BAG89CQyAoFAQAAAANElEQVQoz2O8cuUKAwxoa2vD2VevXsUqzsRAIqC9Bsb///8TdDey+CD0Awsx7h6NB5prAADPsx0VAB8VRQAAAABJRU5ErkJggg==";
 
 
         internal static Byte[] DefaultPngImage => Convert.FromBase64String(DEFAULT_PNG_IMAGE);
         internal static Byte[] DefaultPngImage => Convert.FromBase64String(DEFAULT_PNG_IMAGE);
 
 
+        internal static readonly string[] _EmbeddedHeaders =
+                { EMBEDDED_OCTET_STREAM
+                , EMBEDDED_GLTF_BUFFER
+                , EMBEDDED_JPEG_BUFFER
+                , EMBEDDED_PNG_BUFFER
+                , EMBEDDED_DDS_BUFFER
+                , EMBEDDED_WEBP_BUFFER };
+
         public static MemoryImage Empty => default;
         public static MemoryImage Empty => default;
 
 
         #endregion
         #endregion

+ 17 - 1
src/SharpGLTF.Core/Schema2/_Extensions.cs

@@ -18,7 +18,23 @@ namespace SharpGLTF.Schema2
             if (!uri.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null;
             if (!uri.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) return null;
 
 
             var content = uri.Substring(prefix.Length);
             var content = uri.Substring(prefix.Length);
-            return Convert.FromBase64String(content);
+
+            if (content.StartsWith(";base64,", StringComparison.OrdinalIgnoreCase))
+            {
+                content = content.Substring(";base64,".Length);
+                return Convert.FromBase64String(content);
+            }
+
+            if (content.StartsWith(",", StringComparison.OrdinalIgnoreCase))
+            {
+                content = content.Substring(",".Length);
+
+                if (content.Length == 1) return new Byte[] { Byte.Parse(content,System.Globalization.NumberStyles.HexNumber) };
+
+                throw new NotImplementedException();
+            }
+
+            throw new NotImplementedException();
         }
         }
 
 
         #endregion
         #endregion

+ 15 - 5
src/SharpGLTF.Core/Schema2/gltf.Buffer.cs

@@ -3,7 +3,7 @@ using System.Linq;
 
 
 namespace SharpGLTF.Schema2
 namespace SharpGLTF.Schema2
 {
 {
-    [System.Diagnostics.DebuggerDisplay("Buffer[{LogicalIndex}] {Name} Bytes:{_Data.Length}")]
+    [System.Diagnostics.DebuggerDisplay("Buffer[{LogicalIndex}] {Name} Bytes:{_Content?.Length ?? 0}")]
     public sealed partial class Buffer
     public sealed partial class Buffer
     {
     {
         #region lifecycle
         #region lifecycle
@@ -41,13 +41,15 @@ namespace SharpGLTF.Schema2
 
 
         #region binary read
         #region binary read
 
 
-        const string EMBEDDEDOCTETSTREAM = "data:application/octet-stream;base64,";
-        const string EMBEDDEDGLTFBUFFER = "data:application/gltf-buffer;base64,";
+        const string EMBEDDEDOCTETSTREAM = "data:application/octet-stream";
+        const string EMBEDDEDGLTFBUFFER = "data:application/gltf-buffer";
 
 
         internal void _ResolveUri(IO.ReadContext context)
         internal void _ResolveUri(IO.ReadContext context)
         {
         {
             _Content = _LoadBinaryBufferUnchecked(_uri, context);
             _Content = _LoadBinaryBufferUnchecked(_uri, context);
 
 
+            // if (_uri == null) _byteLength = _Content.Length; // fixes "valid_placeholder.glb" case
+
             _uri = null; // When _Data is not empty, clear URI
             _uri = null; // When _Data is not empty, clear URI
         }
         }
 
 
@@ -112,12 +114,20 @@ namespace SharpGLTF.Schema2
 
 
         #region validation
         #region validation
 
 
+        internal void OnValidateBinaryChunk(Validation.ValidationContext result, Byte[] binaryChunk)
+        {
+            if (_uri == null)
+            {
+                if (binaryChunk == null) { result.GetContext(this).AddSchemaError("Binary chunk not found"); return; }
+                if (_byteLength > binaryChunk.Length) result.GetContext(this).AddSchemaError("Buffer length larger than Binary chunk");
+            }
+        }
+
         protected override void OnValidateReferences(Validation.ValidationContext result)
         protected override void OnValidateReferences(Validation.ValidationContext result)
         {
         {
             base.OnValidateReferences(result);
             base.OnValidateReferences(result);
 
 
-            result.CheckSchemaIsValidURI("Uri", this._uri);
-
+            result.CheckSchemaIsValidURI("Uri", this._uri, EMBEDDEDGLTFBUFFER, EMBEDDEDOCTETSTREAM);
             result.CheckSchemaIsInRange("ByteLength", _byteLength, _byteLengthMinimum, int.MaxValue);
             result.CheckSchemaIsInRange("ByteLength", _byteLength, _byteLengthMinimum, int.MaxValue);
             // result.CheckSchemaIsMultipleOf("ByteLength", _byteLength, 4);
             // result.CheckSchemaIsMultipleOf("ByteLength", _byteLength, 4);
         }
         }

+ 2 - 0
src/SharpGLTF.Core/Schema2/gltf.BufferView.cs

@@ -101,6 +101,8 @@ namespace SharpGLTF.Schema2
             }
             }
         }
         }
 
 
+        internal int LogicalBufferIndex => this._buffer;
+
         #endregion
         #endregion
 
 
         #region API
         #region API

+ 2 - 2
src/SharpGLTF.Core/Schema2/gltf.Images.cs

@@ -45,7 +45,7 @@ namespace SharpGLTF.Schema2
         public bool IsSatelliteFile => _SatelliteImageContent != null;
         public bool IsSatelliteFile => _SatelliteImageContent != null;
 
 
         /// <summary>
         /// <summary>
-        /// Returns the in-memory representation of the image file.
+        /// Gets the in-memory representation of the image file.
         /// </summary>
         /// </summary>
         public Memory.MemoryImage MemoryImage => new Memory.MemoryImage(GetImageContent());
         public Memory.MemoryImage MemoryImage => new Memory.MemoryImage(GetImageContent());
 
 
@@ -274,7 +274,7 @@ namespace SharpGLTF.Schema2
         {
         {
             base.OnValidateReferences(result);
             base.OnValidateReferences(result);
 
 
-            result.CheckSchemaIsValidURI("Uri", this._uri);
+            result.CheckSchemaIsValidURI("Uri", this._uri, Memory.MemoryImage._EmbeddedHeaders);
 
 
             result.CheckArrayIndexAccess("BufferView", _bufferView, this.LogicalParent.LogicalBufferViews);
             result.CheckArrayIndexAccess("BufferView", _bufferView, this.LogicalParent.LogicalBufferViews);
         }
         }

+ 2 - 2
src/SharpGLTF.Core/Schema2/gltf.Serialization.Read.cs

@@ -148,7 +148,7 @@ namespace SharpGLTF.Schema2
         {
         {
             Guard.FilePathMustExist(filePath, nameof(filePath));
             Guard.FilePathMustExist(filePath, nameof(filePath));
 
 
-            BYTES json = default;
+            var json = ReadOnlyMemory<byte>.Empty;
 
 
             using (var s = File.OpenRead(filePath))
             using (var s = File.OpenRead(filePath))
             {
             {
@@ -158,7 +158,7 @@ namespace SharpGLTF.Schema2
             return ParseSatellitePaths(json);
             return ParseSatellitePaths(json);
         }
         }
 
 
-        private static string[] ParseSatellitePaths(BYTES json)
+        private static string[] ParseSatellitePaths(ReadOnlyMemory<Byte> json)
         {
         {
             var uris = new HashSet<string>();
             var uris = new HashSet<string>();
 
 

+ 3 - 4
src/SharpGLTF.Core/Validation/ValidationContext.cs

@@ -175,14 +175,13 @@ namespace SharpGLTF.Validation
 
 
         #pragma warning disable CA1054 // Uri parameters should not be strings
         #pragma warning disable CA1054 // Uri parameters should not be strings
 
 
-        public void CheckSchemaIsValidURI(ValueLocation location, string gltfURI)
+        public void CheckSchemaIsValidURI(ValueLocation location, string gltfURI, params string[] validHeaders)
         {
         {
             if (string.IsNullOrEmpty(gltfURI)) return;
             if (string.IsNullOrEmpty(gltfURI)) return;
 
 
-            if (gltfURI.StartsWith("data:", StringComparison.Ordinal))
+            foreach (var hdr in validHeaders)
             {
             {
-                // check decoding
-                return;
+                if (gltfURI.StartsWith(hdr)) return;
             }
             }
 
 
             if (Uri.TryCreate(gltfURI, UriKind.Relative, out Uri xuri)) return;
             if (Uri.TryCreate(gltfURI, UriKind.Relative, out Uri xuri)) return;

+ 4 - 1
tests/SharpGLTF.NUnit/TestFiles.cs

@@ -145,8 +145,11 @@ namespace SharpGLTF
         {
         {
             _Check();
             _Check();
 
 
-            var files = GetModelPathsInDirectory(_ValidationDir, "test");
+            var skip = new string[] { "misplaced_bin_chunk.glb", "valid_placeholder.glb" };
 
 
+            var files = GetModelPathsInDirectory(_ValidationDir, "test")
+                .Where(item => skip.All(f=>!item.EndsWith(f)));
+            
             return files
             return files
                 .OrderBy(item => item)                
                 .OrderBy(item => item)                
                 .ToList();
                 .ToList();

+ 69 - 0
tests/SharpGLTF.NUnit/ValidationResult.cs

@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace SharpGLTF
+{
+    /// <summary>
+    /// Represents the resulting report of a glTF validation.
+    /// </summary>
+    public sealed class ValidationReport
+    {
+        public static ValidationReport Load(string filePath)
+        {
+            var json = System.IO.File.ReadAllText(filePath);
+            return Parse(json);
+        }
+
+        public static ValidationReport Parse(string json)
+        {
+            var options = new JsonSerializerOptions();
+            options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+
+            return JsonSerializer.Deserialize<ValidationReport>(json, options);
+        }
+
+        public string Uri { get; set; }
+        public string MimeType { get; set; }
+        public string ValidatorVersion { get; set; }
+        public ValidationIssues Issues { get; set; }
+        public ValidationInfo Info { get; set; }
+    }
+
+    public sealed class ValidationIssues
+    {
+        public int NumErrors { get; set; }
+        public int NumWarnings { get; set; }
+        public int NumInfos { get; set; }
+        public int NumHints { get; set; }
+        public ValidationMessage[] Messages { get; set; }
+        public bool Truncated { get; set; }
+    }
+
+    public sealed class ValidationMessage
+    {
+        public string Code { get; set; }
+        public string Message { get; set; }
+        public int Severity { get; set; }
+        public string Pointer { get; set; }
+    }
+
+    public sealed class ValidationInfo
+    {
+        public string Version { get; set; }
+        public string Generator { get; set; }
+        public int AnimationCount { get; set; }
+        public int MaterialCount { get; set; }
+        public bool HasMorphTargets { get; set; }
+        public bool HasSkins { get; set; }
+        public bool HasTextures { get; set; }
+        public bool HasDefaultScene { get; set; }
+        public int DrawCallCount { get; set; }
+        public int TotalTriangleCount { get; set; }
+        public int MaxUVs { get; set; }
+        public int MaxInfluences { get; set; }
+        public int MaxAttributes { get; set; }
+    }
+}

+ 27 - 3
tests/SharpGLTF.Tests/Validation/InvalidFilesTests.cs

@@ -7,7 +7,7 @@ using NUnit.Framework;
 
 
 namespace SharpGLTF.Validation
 namespace SharpGLTF.Validation
 {
 {
-    [Category("Invalid files")]
+    [Category("glTF-Validator Files")]
     public class InvalidFilesTests
     public class InvalidFilesTests
     {
     {
         [Test]
         [Test]
@@ -20,12 +20,34 @@ namespace SharpGLTF.Validation
 
 
             foreach (var f in files)
             foreach (var f in files)
             {
             {
+                var report = ValidationReport.Load(f + ".report.json");
+
                 TestContext.Progress.WriteLine($"{f}...");
                 TestContext.Progress.WriteLine($"{f}...");
                 TestContext.Write($"{f}...");
                 TestContext.Write($"{f}...");
 
 
                 var result = Schema2.ModelRoot.Validate(f);
                 var result = Schema2.ModelRoot.Validate(f);
 
 
-                Assert.IsTrue(result.HasErrors);                
+                Assert.IsTrue(result.HasErrors == report.Issues.NumErrors > 0);
+            }
+        }
+
+        [Test]
+        public void CheckInvalidBinaryFiles()
+        {
+            var files = TestFiles
+                .GetKhronosValidationPaths()
+                .Where(item => item.EndsWith(".glb"));          
+
+            foreach (var f in files)
+            {
+                var report = ValidationReport.Load(f + ".report.json");
+
+                TestContext.Progress.WriteLine($"{f}...");
+                TestContext.WriteLine($"{f}...");
+
+                var result = Schema2.ModelRoot.Validate(f);
+
+                Assert.IsTrue(result.HasErrors == report.Issues.NumErrors > 0);
             }
             }
         }
         }
 
 
@@ -38,6 +60,8 @@ namespace SharpGLTF.Validation
 
 
             foreach (var f in files)
             foreach (var f in files)
             {
             {
+                var report = ValidationReport.Load(f + ".report.json");
+
                 TestContext.Progress.WriteLine($"{f}...");
                 TestContext.Progress.WriteLine($"{f}...");
 
 
                 TestContext.Write($"{f}...");
                 TestContext.Write($"{f}...");
@@ -47,7 +71,7 @@ namespace SharpGLTF.Validation
 
 
                     var result = Schema2.ModelRoot.Validate(f);
                     var result = Schema2.ModelRoot.Validate(f);
 
 
-                    TestContext.WriteLine($"{result.HasErrors}");
+                    TestContext.WriteLine($"{result.HasErrors == report.Issues.NumErrors > 0}");
                 }
                 }
                 catch(Exception ex)
                 catch(Exception ex)
                 {
                 {