// Copyright 2017-2018 Alexander Luzgarev
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]) };
}
}
}