using System; using System.IO; using System.Numerics; using Xunit; namespace MatFileHandler.Tests { /// /// Tests of file writing API. /// public class MatFileWriterTests { private const string TestDirectory = "test-data"; /// /// Test writing a simple Double array. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestWrite(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var array = builder.NewArray(1, 2); array[0] = -13.5; array[1] = 17.0; var variable = builder.NewVariable("test", array); var actual = builder.NewFile(new[] { variable }); MatCompareWithTestData("good", "double-array", actual, method, options); } /// /// Test writing a large file. /// [Fact] public void TestHuge() { var builder = new DataBuilder(); var array = builder.NewArray(1000, 10000); array[0] = -13.5; array[1] = 17.0; var variable = builder.NewVariable("test", array); var matFile = builder.NewFile(new[] { variable }); using (var stream = new MemoryStream()) { var writer = new MatFileWriter(stream); writer.Write(matFile); } } /// /// Test writing lower and upper limits of integer data types. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestLimits(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var int8 = builder.NewVariable("int8_", builder.NewArray(CommonData.Int8Limits, 1, 2)); var uint8 = builder.NewVariable("uint8_", builder.NewArray(CommonData.UInt8Limits, 1, 2)); var int16 = builder.NewVariable("int16_", builder.NewArray(CommonData.Int16Limits, 1, 2)); var uint16 = builder.NewVariable("uint16_", builder.NewArray(CommonData.UInt16Limits, 1, 2)); var int32 = builder.NewVariable("int32_", builder.NewArray(CommonData.Int32Limits, 1, 2)); var uint32 = builder.NewVariable("uint32_", builder.NewArray(CommonData.UInt32Limits, 1, 2)); var int64 = builder.NewVariable("int64_", builder.NewArray(CommonData.Int64Limits, 1, 2)); var uint64 = builder.NewVariable("uint64_", builder.NewArray(CommonData.UInt64Limits, 1, 2)); var actual = builder.NewFile(new[] { int16, int32, int64, int8, uint16, uint32, uint64, uint8 }); MatCompareWithTestData("good", "limits", actual, method, options); } /// /// Test writing lower and upper limits of integer-based complex data types. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestLimitsComplex(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var int8Complex = builder.NewVariable( "int8_complex", builder.NewArray(CreateComplexLimits(CommonData.Int8Limits), 1, 2)); var uint8Complex = builder.NewVariable( "uint8_complex", builder.NewArray(CreateComplexLimits(CommonData.UInt8Limits), 1, 2)); var int16Complex = builder.NewVariable( "int16_complex", builder.NewArray(CreateComplexLimits(CommonData.Int16Limits), 1, 2)); var uint16Complex = builder.NewVariable( "uint16_complex", builder.NewArray(CreateComplexLimits(CommonData.UInt16Limits), 1, 2)); var int32Complex = builder.NewVariable( "int32_complex", builder.NewArray(CreateComplexLimits(CommonData.Int32Limits), 1, 2)); var uint32Complex = builder.NewVariable( "uint32_complex", builder.NewArray(CreateComplexLimits(CommonData.UInt32Limits), 1, 2)); var int64Complex = builder.NewVariable( "int64_complex", builder.NewArray(CreateComplexLimits(CommonData.Int64Limits), 1, 2)); var uint64Complex = builder.NewVariable( "uint64_complex", builder.NewArray(CreateComplexLimits(CommonData.UInt64Limits), 1, 2)); var actual = builder.NewFile(new[] { int16Complex, int32Complex, int64Complex, int8Complex, uint16Complex, uint32Complex, uint64Complex, uint8Complex, }); MatCompareWithTestData("good", "limits_complex", actual, method, options); } /// /// Test writing a wide-Unicode symbol. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestUnicodeWide(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var s = builder.NewVariable("s", builder.NewCharArray("🍆")); var actual = builder.NewFile(new[] { s }); MatCompareWithTestData("good", "unicode-wide", actual, method, options); } /// /// Test writing a sparse array. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestSparseArray(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var sparseArray = builder.NewSparseArray(4, 5); sparseArray[1, 1] = 1; sparseArray[1, 2] = 2; sparseArray[2, 1] = 3; sparseArray[2, 3] = 4; var sparse = builder.NewVariable("sparse_", sparseArray); var actual = builder.NewFile(new[] { sparse }); MatCompareWithTestData("good", "sparse", actual, method, options); } /// /// Test writing a structure array. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestStructure(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var structure = builder.NewStructureArray(new[] { "x", "y" }, 2, 3); structure["x", 0, 0] = builder.NewArray(new[] { 12.345 }, 1, 1); structure["y", 0, 0] = builder.NewCharArray("abc"); structure["x", 1, 0] = builder.NewCharArray("xyz"); structure["y", 1, 0] = builder.NewEmpty(); structure["x", 0, 1] = builder.NewArray(new[] { 2.0 }, 1, 1); structure["y", 0, 1] = builder.NewArray(new[] { 13.0 }, 1, 1); structure["x", 1, 1] = builder.NewEmpty(); structure["y", 1, 1] = builder.NewCharArray("acbd", 2, 2); var cellArray = builder.NewCellArray(1, 2); cellArray[0] = builder.NewCharArray("x"); cellArray[1] = builder.NewCharArray("yz"); structure["x", 0, 2] = cellArray; structure["y", 0, 2] = builder.NewArray(new[] { 1.0, 4.0, 2.0, 5.0, 3.0, 6.0 }, 2, 3); structure["x", 1, 2] = builder.NewArray(new[] { 1.5f }, 1, 1); structure["y", 1, 2] = builder.NewEmpty(); var struct_ = builder.NewVariable("struct_", structure); var actual = builder.NewFile(new[] { struct_ }); MatCompareWithTestData("good", "struct", actual, method, options); } /// /// Test writing a logical array. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestLogical(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var logical = builder.NewArray(new[] { true, false, true, true, false, true }, 2, 3); var logicalVariable = builder.NewVariable("logical_", logical); var actual = builder.NewFile(new[] { logicalVariable }); MatCompareWithTestData("good", "logical", actual, method, options); } /// /// Test writing a sparse logical array. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestSparseLogical(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var array = builder.NewSparseArray(2, 3); array[0, 0] = true; array[0, 1] = true; array[1, 1] = true; array[1, 2] = true; var sparseLogical = builder.NewVariable("sparse_logical", array); var actual = builder.NewFile(new[] { sparseLogical }); MatCompareWithTestData("good", "sparse_logical", actual, method, options); } /// /// Test writing a sparse complex array. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestSparseComplex(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var array = builder.NewSparseArray(2, 2); array[0, 0] = -1.5 + (2.5 * Complex.ImaginaryOne); array[1, 0] = 2 - (3 * Complex.ImaginaryOne); array[1, 1] = 0.5 + Complex.ImaginaryOne; var sparseComplex = builder.NewVariable("sparse_complex", array); var actual = builder.NewFile(new[] { sparseComplex }); MatCompareWithTestData("good", "sparse_complex", actual, method, options); } /// /// Test writing a global variable. /// [Theory, MemberData(nameof(MatFileWritingTestData))] public void TestGlobal(MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var builder = new DataBuilder(); var array = builder.NewArray(new double[] { 1, 3, 5 }, 1, 3); var global = builder.NewVariable("global_", array, true); var actual = builder.NewFile(new[] { global }); MatCompareWithTestData("good", "global", actual, method, options); } /// /// Various writing methods for testing writing of .mat files. /// public static TheoryData MatFileWritingTestData { get { var always = new MatFileWriterOptions { UseCompression = CompressionUsage.Always}; var never = new MatFileWriterOptions { UseCompression = CompressionUsage.Never }; var data = new TheoryData { { MatFileWritingMethod.NormalStream, MatFileWriterOptionsForTests.None }, { MatFileWritingMethod.NormalStream, MatFileWriterOptionsForTests.Always }, { MatFileWritingMethod.NormalStream, MatFileWriterOptionsForTests.Never }, { MatFileWritingMethod.UnseekableStream, MatFileWriterOptionsForTests.None }, { MatFileWritingMethod.UnseekableStream, MatFileWriterOptionsForTests.Always }, { MatFileWritingMethod.UnseekableStream, MatFileWriterOptionsForTests.Never }, { MatFileWritingMethod.UnalignedStream, MatFileWriterOptionsForTests.None }, { MatFileWritingMethod.UnalignedStream, MatFileWriterOptionsForTests.Always }, { MatFileWritingMethod.UnalignedStream, MatFileWriterOptionsForTests.Never }, }; return data; } } private static void CompareSparseArrays(ISparseArrayOf expected, ISparseArrayOf actual) where T : struct { Assert.NotNull(actual); Assert.Equal(expected.Dimensions, actual.Dimensions); Assert.Equal(expected.Data, actual.Data); } private static void CompareStructureArrays(IStructureArray expected, IStructureArray actual) { Assert.NotNull(actual); Assert.Equal(expected.Dimensions, actual.Dimensions); Assert.Equal(expected.FieldNames, actual.FieldNames); foreach (var name in expected.FieldNames) { for (var i = 0; i < expected.Count; i++) { CompareMatArrays(expected[name, i], actual[name, i]); } } } private static void CompareCellArrays(ICellArray expected, ICellArray actual) { Assert.NotNull(actual); Assert.Equal(expected.Dimensions, actual.Dimensions); for (var i = 0; i < expected.Count; i++) { CompareMatArrays(expected[i], actual[i]); } } private static void CompareNumericalArrays(IArrayOf expected, IArrayOf actual) { Assert.NotNull(actual); Assert.Equal(expected.Dimensions, actual.Dimensions); Assert.Equal(expected.Data, actual.Data); } private static void CompareCharArrays(ICharArray expected, ICharArray actual) { Assert.NotNull(actual); Assert.Equal(expected.Dimensions, actual.Dimensions); Assert.Equal(expected.String, actual.String); } private static void CompareMatArrays(IArray expected, IArray actual) { switch (expected) { case ISparseArrayOf expectedSparseArrayOfDouble: CompareSparseArrays(expectedSparseArrayOfDouble, actual as ISparseArrayOf); return; case ISparseArrayOf expectedSparseArrayOfComplex: CompareSparseArrays(expectedSparseArrayOfComplex, actual as ISparseArrayOf); return; case IStructureArray expectedStructureArray: CompareStructureArrays(expectedStructureArray, actual as IStructureArray); return; case ICharArray expectedCharArray: CompareCharArrays(expectedCharArray, actual as ICharArray); return; case IArrayOf byteArray: CompareNumericalArrays(byteArray, actual as IArrayOf); return; case IArrayOf sbyteArray: CompareNumericalArrays(sbyteArray, actual as IArrayOf); return; case IArrayOf shortArray: CompareNumericalArrays(shortArray, actual as IArrayOf); return; case IArrayOf ushortArray: CompareNumericalArrays(ushortArray, actual as IArrayOf); return; case IArrayOf intArray: CompareNumericalArrays(intArray, actual as IArrayOf); return; case IArrayOf uintArray: CompareNumericalArrays(uintArray, actual as IArrayOf); return; case IArrayOf longArray: CompareNumericalArrays(longArray, actual as IArrayOf); return; case IArrayOf ulongArray: CompareNumericalArrays(ulongArray, actual as IArrayOf); return; case IArrayOf floatArray: CompareNumericalArrays(floatArray, actual as IArrayOf); return; case IArrayOf doubleArray: CompareNumericalArrays(doubleArray, actual as IArrayOf); return; case IArrayOf> byteArray: CompareNumericalArrays(byteArray, actual as IArrayOf>); return; case IArrayOf> sbyteArray: CompareNumericalArrays(sbyteArray, actual as IArrayOf>); return; case IArrayOf> shortArray: CompareNumericalArrays(shortArray, actual as IArrayOf>); return; case IArrayOf> ushortArray: CompareNumericalArrays(ushortArray, actual as IArrayOf>); return; case IArrayOf> intArray: CompareNumericalArrays(intArray, actual as IArrayOf>); return; case IArrayOf> uintArray: CompareNumericalArrays(uintArray, actual as IArrayOf>); return; case IArrayOf> longArray: CompareNumericalArrays(longArray, actual as IArrayOf>); return; case IArrayOf> ulongArray: CompareNumericalArrays(ulongArray, actual as IArrayOf>); return; case IArrayOf> floatArray: CompareNumericalArrays(floatArray, actual as IArrayOf>); return; case IArrayOf doubleArray: CompareNumericalArrays(doubleArray, actual as IArrayOf); return; case IArrayOf boolArray: CompareNumericalArrays(boolArray, actual as IArrayOf); return; case ICellArray cellArray: CompareCellArrays(cellArray, actual as ICellArray); return; } if (expected.IsEmpty) { Assert.True(actual.IsEmpty); return; } throw new NotSupportedException(); } private static void CompareMatFiles(IMatFile expected, IMatFile actual) { Assert.Equal(expected.Variables.Length, actual.Variables.Length); for (var i = 0; i < expected.Variables.Length; i++) { var expectedVariable = expected.Variables[i]; var actualVariable = actual.Variables[i]; Assert.Equal(expectedVariable.Name, actualVariable.Name); Assert.Equal(expectedVariable.IsGlobal, actualVariable.IsGlobal); CompareMatArrays(expectedVariable.Value, actualVariable.Value); } } private static void MatCompareWithTestData( string factoryName, string testName, IMatFile actual, MatFileWritingMethod method, MatFileWriterOptionsForTests options) { var fullFileName = Path.Combine("test-data", "good", $"{testName}.mat"); var expected = MatFileReadingMethods.ReadMatFile( MatFileReadingMethod.NormalStream, fullFileName); var buffer = MatFileWritingMethods.WriteMatFile(method, options, actual); using var stream = new MemoryStream(buffer); var reader = new MatFileReader(stream); var actualRead = reader.Read(); CompareMatFiles(expected, actualRead); } private static ComplexOf[] CreateComplexLimits(T[] limits) where T : struct { return new[] { new ComplexOf(limits[0], limits[1]), new ComplexOf(limits[1], limits[0]) }; } } }