using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; using Xunit; namespace MatFileHandler.Tests { /// /// Tests of file reading API. /// public class MatFileReaderTests { private const string TestDirectory = "test-data"; /// /// Test reading all files in a given test set. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestReader(MatFileReadingMethod method) { foreach (var matFile in ReadAllTestFiles(method)) { Assert.NotEmpty(matFile.Variables); } } /// /// Test reading lower and upper limits of integer data types. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestLimits(MatFileReadingMethod method) { var matFile = ReadTestFile("limits", method); IArray array; array = matFile["int8_"].Value; CheckLimits(array as IArrayOf, CommonData.Int8Limits); Assert.Equal(new[] { -128.0, 127.0 }, array.ConvertToDoubleArray()); array = matFile["uint8_"].Value; CheckLimits(array as IArrayOf, CommonData.UInt8Limits); array = matFile["int16_"].Value; CheckLimits(array as IArrayOf, CommonData.Int16Limits); array = matFile["uint16_"].Value; CheckLimits(array as IArrayOf, CommonData.UInt16Limits); array = matFile["int32_"].Value; CheckLimits(array as IArrayOf, CommonData.Int32Limits); array = matFile["uint32_"].Value; CheckLimits(array as IArrayOf, CommonData.UInt32Limits); array = matFile["int64_"].Value; CheckLimits(array as IArrayOf, CommonData.Int64Limits); array = matFile["uint64_"].Value; CheckLimits(array as IArrayOf, CommonData.UInt64Limits); } /// /// Test writing lower and upper limits of integer-based complex data types. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestComplexLimits(MatFileReadingMethod method) { var matFile = ReadTestFile("limits_complex", method); IArray array; array = matFile["int8_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.Int8Limits); Assert.Equal( new[] { -128.0 + (127.0 * Complex.ImaginaryOne), 127.0 - (128.0 * Complex.ImaginaryOne) }, array.ConvertToComplexArray()); array = matFile["uint8_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.UInt8Limits); array = matFile["int16_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.Int16Limits); array = matFile["uint16_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.UInt16Limits); array = matFile["int32_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.Int32Limits); array = matFile["uint32_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.UInt32Limits); array = matFile["int64_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.Int64Limits); array = matFile["uint64_complex"].Value; CheckComplexLimits(array as IArrayOf>, CommonData.UInt64Limits); } /// /// Test reading an ASCII-encoded string. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestAscii(MatFileReadingMethod method) { var matFile = ReadTestFile("ascii", method); var arrayAscii = matFile["s"].Value as ICharArray; Assert.NotNull(arrayAscii); Assert.Equal(new[] { 1, 3 }, arrayAscii.Dimensions); Assert.Equal("abc", arrayAscii.String); Assert.Equal('c', arrayAscii[2]); } /// /// Test reading a Unicode string. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestUnicode(MatFileReadingMethod method) { var matFile = ReadTestFile("unicode", method); var arrayUnicode = matFile["s"].Value as ICharArray; Assert.NotNull(arrayUnicode); Assert.Equal(new[] { 1, 2 }, arrayUnicode.Dimensions); Assert.Equal("必フ", arrayUnicode.String); Assert.Equal('必', arrayUnicode[0]); Assert.Equal('フ', arrayUnicode[1]); } /// /// Test reading a wide Unicode string. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestUnicodeWide(MatFileReadingMethod method) { var matFile = ReadTestFile("unicode-wide", method); var arrayUnicodeWide = matFile["s"].Value as ICharArray; Assert.NotNull(arrayUnicodeWide); Assert.Equal(new[] { 1, 2 }, arrayUnicodeWide.Dimensions); Assert.Equal("🍆", arrayUnicodeWide.String); } /// /// Test converting a structure array to a Double array. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestConvertToDoubleArray(MatFileReadingMethod method) { var matFile = ReadTestFile("struct", method); var array = matFile.Variables[0].Value; Assert.Null(array.ConvertToDoubleArray()); } /// /// Test converting a structure array to a Complex array. /// /// Should return null. [Theory, MemberData(nameof(TestDataFactories))] public void TestConvertToComplexArray(MatFileReadingMethod method) { var matFile = ReadTestFile("struct", method); var array = matFile.Variables[0].Value; Assert.Null(array.ConvertToComplexArray()); } /// /// Test reading an enumeration. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestEnum(MatFileReadingMethod method) { var matFile = ReadTestFile("enum", method); var days = matFile["days"].Value; var enumeration = new EnumAdapter(days); Assert.Equal(5, enumeration.Values.Count); Assert.Equal("Wednesday", enumeration.ValueNames[enumeration.Values[0]]); Assert.Equal("Saturday", enumeration.ValueNames[enumeration.Values[1]]); Assert.Equal("Monday", enumeration.ValueNames[enumeration.Values[2]]); Assert.Equal("Wednesday", enumeration.ValueNames[enumeration.Values[3]]); Assert.Equal("Saturday", enumeration.ValueNames[enumeration.Values[4]]); } /// /// Test reading a structure array. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestStruct(MatFileReadingMethod method) { var matFile = ReadTestFile("struct", method); var structure = matFile["struct_"].Value as IStructureArray; Assert.NotNull(structure); Assert.Equal(new[] { "x", "y" }, structure.FieldNames); var element = structure[0, 0]; Assert.True(element.ContainsKey("x")); Assert.Equal(2, element.Count); Assert.True(element.TryGetValue("x", out var _)); Assert.False(element.TryGetValue("z", out var _)); Assert.Equal(2, element.Keys.Count()); Assert.Equal(2, element.Values.Count()); var keys = element.Select(pair => pair.Key); Assert.Equal(new[] { "x", "y" }, keys); Assert.Equal(12.345, (element["x"] as IArrayOf)?[0]); Assert.Equal(12.345, (structure["x", 0, 0] as IArrayOf)?[0]); Assert.Equal(2.0, (structure["x", 0, 1] as IArrayOf)?[0]); Assert.Equal("x", ((structure["x", 0, 2] as ICellArray)?[0] as ICharArray)?.String); Assert.Equal("yz", ((structure["x", 0, 2] as ICellArray)?[1] as ICharArray)?.String); Assert.Equal("xyz", (structure["x", 1, 0] as ICharArray)?.String); Assert.True(structure["x", 1, 1].IsEmpty); Assert.Equal(1.5f, (structure["x", 1, 2] as IArrayOf)?[0]); Assert.Equal("abc", (structure["y", 0, 0] as ICharArray)?.String); Assert.Equal(13.0, (structure["y", 0, 1] as IArrayOf)?[0]); Assert.Equal(new[] { 2, 3 }, (structure["y", 0, 2] as IArrayOf)?.Dimensions); Assert.Equal(3.0, (structure["y", 0, 2] as IArrayOf)?[0, 2]); Assert.True(structure["y", 1, 0].IsEmpty); Assert.Equal('a', (structure["y", 1, 1] as ICharArray)?[0, 0]); Assert.True(structure["y", 1, 2].IsEmpty); Assert.Equal( new double[,] { { 1, 2, 3 }, { 4, 5, 6 }, }, structure["y", 0, 2].ConvertTo2dDoubleArray()); Assert.Equal( new double[,] { { 1, 2, 3 }, { 4, 5, 6 }, }, structure["y", 0, 2].ConvertToMultidimensionalDoubleArray()); } /// /// Test reading a sparse array. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestSparse(MatFileReadingMethod method) { var matFile = ReadTestFile("sparse", method); var sparseArray = matFile["sparse_"].Value as ISparseArrayOf; Assert.NotNull(sparseArray); Assert.Equal(new[] { 4, 5 }, sparseArray.Dimensions); Assert.Equal(1.0, sparseArray.Data[(1, 1)]); Assert.Equal(1.0, sparseArray[1, 1]); Assert.Equal(2.0, sparseArray[1, 2]); Assert.Equal(3.0, sparseArray[2, 1]); Assert.Equal(4.0, sparseArray[2, 3]); Assert.Equal(0.0, sparseArray[0, 4]); Assert.Equal(0.0, sparseArray[3, 0]); Assert.Equal(0.0, sparseArray[3, 4]); Assert.Equal( new double[,] { { 0, 0, 0, 0, 0 }, { 0, 1, 2, 0, 0 }, { 0, 3, 0, 4, 0 }, { 0, 0, 0, 0, 0 }, }, sparseArray.ConvertTo2dDoubleArray()); } /// /// Test reading a logical array. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestLogical(MatFileReadingMethod method) { var matFile = ReadTestFile("logical", method); var array = matFile["logical_"].Value; var logicalArray = array as IArrayOf; Assert.NotNull(logicalArray); Assert.True(logicalArray[0, 0]); Assert.True(logicalArray[0, 1]); Assert.False(logicalArray[0, 2]); Assert.False(logicalArray[1, 0]); Assert.True(logicalArray[1, 1]); Assert.True(logicalArray[1, 2]); } /// /// Test reading a sparse logical array. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestSparseLogical(MatFileReadingMethod method) { var matFile = ReadTestFile("sparse_logical", method); var array = matFile["sparse_logical"].Value; var sparseArray = array as ISparseArrayOf; Assert.NotNull (sparseArray); Assert.True(sparseArray.Data[(0, 0)]); Assert.True(sparseArray[0, 0]); Assert.True(sparseArray[0, 1]); Assert.False(sparseArray[0, 2]); Assert.False(sparseArray[1, 0]); Assert.True(sparseArray[1, 1]); Assert.True(sparseArray[1, 2]); } /// /// Test reading a global variable. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestGlobal(MatFileReadingMethod method) { var matFile = ReadTestFile("global", method); var variable = matFile.Variables.First(); Assert.True(variable.IsGlobal); } /// /// Test reading a sparse complex array. /// [Theory, MemberData(nameof(TestDataFactories))] public void TextSparseComplex(MatFileReadingMethod method) { var matFile = ReadTestFile("sparse_complex", method); var array = matFile["sparse_complex"].Value; var sparseArray = array as ISparseArrayOf; Assert.NotNull(sparseArray); Assert.Equal(-1.5 + (2.5 * Complex.ImaginaryOne), sparseArray[0, 0]); Assert.Equal(2 - (3 * Complex.ImaginaryOne), sparseArray[1, 0]); Assert.Equal(Complex.Zero, sparseArray[0, 1]); Assert.Equal(0.5 + (1.0 * Complex.ImaginaryOne), sparseArray[1, 1]); } /// /// Test reading an object. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestObject(MatFileReadingMethod method) { var matFile = ReadTestFile("object", method); var obj = matFile["object_"].Value as IMatObject; Assert.NotNull(obj); Assert.Equal("Point", obj.ClassName); Assert.Equal(new[] { "x", "y" }, obj.FieldNames); Assert.Equal(new[] { 3.0 }, obj["x", 0].ConvertToDoubleArray()); Assert.Equal(new[] { 5.0 }, obj["y", 0].ConvertToDoubleArray()); Assert.Equal(new[] { -2.0 }, obj["x", 1].ConvertToDoubleArray()); Assert.Equal(new[] { 6.0 }, obj["y", 1].ConvertToDoubleArray()); } /// /// Test reading another object. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestObject2(MatFileReadingMethod method) { var matFile = ReadTestFile("object2", method); var obj = matFile["object2"].Value as IMatObject; Assert.NotNull(obj); Assert.Equal("Point", obj.ClassName); Assert.Equal(new[] { "x", "y" }, obj.FieldNames); Assert.Equal(new[] { 3.0 }, obj["x", 0, 0].ConvertToDoubleArray()); Assert.Equal(new[] { -2.0 }, obj["x", 0, 1].ConvertToDoubleArray()); Assert.Equal(new[] { 1.0 }, obj["x", 1, 0].ConvertToDoubleArray()); Assert.Equal(new[] { 0.0 }, obj["x", 1, 1].ConvertToDoubleArray()); Assert.Equal(new[] { 5.0 }, obj["y", 0, 0].ConvertToDoubleArray()); Assert.Equal(new[] { 6.0 }, obj["y", 0, 1].ConvertToDoubleArray()); Assert.Equal(new[] { 0.0 }, obj["y", 1, 0].ConvertToDoubleArray()); Assert.Equal(new[] { 1.0 }, obj["y", 1, 1].ConvertToDoubleArray()); Assert.Equal(new[] { -2.0 }, obj[0, 1]["x"].ConvertToDoubleArray()); Assert.Equal(new[] { -2.0 }, obj[2]["x"].ConvertToDoubleArray()); } /// /// Test reading a table. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestTable(MatFileReadingMethod method) { var matFile = ReadTestFile("table", method); var obj = matFile["table_"].Value as IMatObject; var table = new TableAdapter(obj); Assert.Equal(3, table.NumberOfRows); Assert.Equal(2, table.NumberOfVariables); Assert.Equal("Some table", table.Description); Assert.Equal(new[] { "variable1", "variable2" }, table.VariableNames); var variable1 = table["variable1"] as ICellArray; Assert.Equal("First row", (variable1[0] as ICharArray).String); Assert.Equal("Second row", (variable1[1] as ICharArray).String); Assert.Equal("Third row", (variable1[2] as ICharArray).String); var variable2 = table["variable2"]; Assert.Equal(new[] { 1.0, 3.0, 5.0, 2.0, 4.0, 6.0 }, variable2.ConvertToDoubleArray()); } /// /// Test reading a deeply nested table. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestDeepTable(MatFileReadingMethod method) { var matFile = ReadTestFile("table-deep", method); var obj = matFile["t"].Value as IMatObject; var table = new TableAdapter(obj); Assert.Equal(1, table.NumberOfRows); Assert.Equal(2, table.NumberOfVariables); Assert.Equal(new[] { "s", "another" }, table.VariableNames); var s = table["s"] as IStructureArray; Assert.Equal(new[] { "a", "b", "c" }, s.FieldNames); var c = s["c", 0]; var internalTable = new TableAdapter(c); Assert.Equal(2, internalTable.NumberOfRows); Assert.Equal(2, internalTable.NumberOfVariables); Assert.Equal(new[] { "x", "y" }, internalTable.VariableNames); var y = new StringAdapter(internalTable["y"]); Assert.Equal("3", y[0]); Assert.Equal("abc", y[1]); } /// /// Test reading a table with strings /// [Theory, MemberData(nameof(TestDataFactories))] public void TestTableWithStrings(MatFileReadingMethod method) { var matFile = ReadTestFile("table-with-strings", method); var obj = matFile["t"].Value as IMatObject; var table = new TableAdapter(obj); Assert.Equal(5, table.NumberOfRows); Assert.Equal(2, table.NumberOfVariables); Assert.Equal(new[] { "Numbers", "Names" }, table.VariableNames); var variable = table["Names"] as ICellArray; var name0 = new StringAdapter(variable[0]); Assert.Equal("One", name0[0]); var name1 = new StringAdapter(variable[1]); Assert.Equal("Two", name1[0]); var name2 = new StringAdapter(variable[2]); Assert.Equal("Three", name2[0]); var name3 = new StringAdapter(variable[3]); Assert.Equal("Four", name3[0]); var name4 = new StringAdapter(variable[4]); Assert.Equal("Five", name4[0]); } /// /// Test subobjects within objects. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestSubobjects(MatFileReadingMethod method) { var matFile = ReadTestFile("pointWithSubpoints", method); var p = matFile["p"].Value as IMatObject; Assert.Equal("Point", p.ClassName); var x = p["x"] as IMatObject; Assert.Equal("SubPoint", x.ClassName); Assert.Equal(new[] { "a", "b", "c" }, x.FieldNames); var y = p["y"] as IMatObject; Assert.Equal("SubPoint", y.ClassName); Assert.Equal(new[] { "a", "b", "c" }, y.FieldNames); Assert.Equal(new[] { 1.0 }, x["a"].ConvertToDoubleArray()); Assert.Equal(new[] { 2.0 }, x["b"].ConvertToDoubleArray()); Assert.Equal(new[] { 3.0 }, x["c"].ConvertToDoubleArray()); Assert.Equal(new[] { 14.0 }, y["a"].ConvertToDoubleArray()); Assert.Equal(new[] { 15.0 }, y["b"].ConvertToDoubleArray()); Assert.Equal(new[] { 16.0 }, y["c"].ConvertToDoubleArray()); } /// /// Test nested objects. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestNestedObjects(MatFileReadingMethod method) { var matFile = ReadTestFile("subsubPoint", method); var p = matFile["p"].Value as IMatObject; Assert.Equal("Point", p.ClassName); Assert.Equal(new[] { 1.0 }, p["x"].ConvertToDoubleArray()); var pp = p["y"] as IMatObject; Assert.True(pp.ClassName == "Point"); Assert.Equal(new[] { 10.0 }, pp["x"].ConvertToDoubleArray()); var ppp = pp["y"] as IMatObject; Assert.Equal(new[] { 100.0 }, ppp["x"].ConvertToDoubleArray()); Assert.Equal(new[] { 200.0 }, ppp["y"].ConvertToDoubleArray()); } /// /// Test datetime objects. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestDatetime(MatFileReadingMethod method) { var matFile = ReadTestFile("datetime", method); var d = matFile["d"].Value as IMatObject; var datetime = new DatetimeAdapter(d); Assert.Equal(new[] { 1, 2 }, datetime.Dimensions); Assert.Equal(new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero), datetime[0]); Assert.Equal(new DateTimeOffset(1987, 1, 2, 3, 4, 5, TimeSpan.Zero), datetime[1]); } /// /// Another test for datetime objects. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestDatetime2(MatFileReadingMethod method) { var matFile = ReadTestFile("datetime2", method); var d = matFile["d"].Value as IMatObject; var datetime = new DatetimeAdapter(d); Assert.Equal(new[] { 1, 1 }, datetime.Dimensions); var diff = new DateTimeOffset(2, 1, 1, 1, 1, 1, 235, TimeSpan.Zero); Assert.True(datetime[0] - diff < TimeSpan.FromMilliseconds(1)); Assert.True(diff - datetime[0] < TimeSpan.FromMilliseconds(1)); } /// /// Test string objects. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestString(MatFileReadingMethod method) { var matFile = ReadTestFile("string", method); var s = matFile["s"].Value as IMatObject; var str = new StringAdapter(s); Assert.Equal(new[] { 4, 1 }, str.Dimensions); Assert.Equal("abc", str[0]); Assert.Equal("defgh", str[1]); Assert.Equal("абвгд", str[2]); Assert.Equal("æøå", str[3]); } /// /// Test duration objects. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestDuration(MatFileReadingMethod method) { var matFile = ReadTestFile("duration", method); var d = matFile["d"].Value as IMatObject; var duration = new DurationAdapter(d); Assert.Equal(new[] { 1, 3 }, duration.Dimensions); Assert.Equal(TimeSpan.FromTicks(12345678L), duration[0]); Assert.Equal(new TimeSpan(0, 2, 4), duration[1]); Assert.Equal(new TimeSpan(1, 3, 5), duration[2]); } /// /// Test unrepresentable datetime. /// [Theory, MemberData(nameof(TestDataFactories))] public void TestDatetime_Unrepresentable(MatFileReadingMethod method) { var matFile = ReadTestFile("datetime-unrepresentable", method); var obj = matFile["d"].Value as IMatObject; var datetime = new DatetimeAdapter(obj); var d0 = datetime[0]; Assert.Null(d0); } /// /// Test 3-dimensional arrays. /// [Theory, MemberData(nameof(TestDataFactories))] public void Test_3DArrays(MatFileReadingMethod method) { var matFile = ReadTestFile("issue20", method); var obj = matFile["a3d"].Value; var values = obj.ConvertToDoubleArray(); Assert.Equal(Enumerable.Range(1, 24).Select(x => (double)x).ToArray(), values); var expected = new double[3, 4, 2] { { { 1, 13 }, { 4, 16 }, { 7, 19 }, { 10, 22 }, }, { { 2, 14 }, { 5, 17 }, { 8, 20 }, { 11, 23 }, }, { { 3, 15 }, { 6, 18 }, { 9, 21 }, { 12, 24 }, }, }; Assert.Equal(expected, obj.ConvertToMultidimensionalDoubleArray()); Assert.Null(obj.ConvertTo2dDoubleArray()); } /// /// Test four-dimensional arrays. /// [Theory, MemberData(nameof(TestDataFactories))] public void Test4DArrays(MatFileReadingMethod method) { var matFile = ReadTestFile("issue20", method); var obj = matFile["a4d"].Value; Assert.Equal(Enumerable.Range(1, 120).Select(x => (double)x).ToArray(), obj.ConvertToDoubleArray()); Assert.Null(obj.ConvertTo2dDoubleArray()); } /// /// Returns the factories that provide test data in various configurations. /// public static TheoryData TestDataFactories { get { return new TheoryData { MatFileReadingMethod.NormalStream, MatFileReadingMethod.PartialStream, MatFileReadingMethod.UnalignedStream, }; } } private static IMatFile ReadTestFile(string fileName, MatFileReadingMethod method) { var fullFileName = Path.Combine("test-data", "good", $"{fileName}.mat"); return MatFileReadingMethods.ReadMatFile(method, fullFileName); } private static IEnumerable ReadAllTestFiles(MatFileReadingMethod method) { foreach (var fileName in Directory.EnumerateFiles( Path.Combine("test-data", "good"), "*.mat")) { var fullFileName = fileName; yield return MatFileReadingMethods.ReadMatFile(method, fullFileName); } } private static void CheckLimits(IArrayOf array, T[] limits) where T : struct { Assert.NotNull(array); Assert.Equal(new[] { 1, 2 }, array.Dimensions); Assert.Equal(limits, array.Data); } private static void CheckComplexLimits(IArrayOf> array, T[] limits) where T : struct { Assert.NotNull(array); Assert.Equal(new[] { 1, 2 }, array.Dimensions); Assert.Equal(new ComplexOf(limits[0], limits[1]), array[0]); Assert.Equal(new ComplexOf(limits[1], limits[0]), array[1]); } } }