Skip to content

Commit

Permalink
Fix Assembly load bug resulting in CSV class maps not getting registered
Browse files Browse the repository at this point in the history
We now force load some assemblies when we do the reflection lookup, and we've made the reflection lookop lazy so it will be evaluated later on in app boot.

Fixes CSV test `TestCsvClassMapsAreAutomaticallyRegistered`

Work done for #196
  • Loading branch information
atruskie committed Mar 18, 2020
1 parent 85c5ec5 commit cf15a27
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 46 deletions.
2 changes: 1 addition & 1 deletion src/Acoustics.Shared/Csv/Csv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public static void WriteToCsv<T>(FileInfo destination, IEnumerable<T> results)
/// <remarks>
/// IMPORTANT NOTE:
/// If I get an exception, how do I tell what line the exception is on?
/// There is a lot of information held in Exception.Data["CsvHelper"]
/// There is a lot of information held in Exception.Data["CsvHelper"].
/// </remarks>
public static IEnumerable<T> ReadFromCsv<T>(
FileInfo source,
Expand Down
4 changes: 2 additions & 2 deletions src/Acoustics.Shared/Extensions/ExtensionsString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ public static string[] SplitOnAnyNewLine(this string str)
return str.Split(newLines, StringSplitOptions.RemoveEmptyEntries);
}

public static void FormatList(this IEnumerable<string> strings, StringBuilder builder)
public static void FormatList<T>(this IEnumerable<T> strings, StringBuilder builder)
{
Contract.RequiresNotNull(builder, nameof(builder));

Expand All @@ -422,7 +422,7 @@ public static void FormatList(this IEnumerable<string> strings, StringBuilder bu
}
}

public static string FormatList(this IEnumerable<string> strings)
public static string FormatList<T>(this IEnumerable<T> strings)
{
var builder = new StringBuilder("\n", 1000);

Expand Down
38 changes: 30 additions & 8 deletions src/Acoustics.Shared/Meta.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Acoustics.Shared
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;

Expand All @@ -21,14 +22,10 @@ public static class Meta

public static readonly int NowYear = DateTime.Now.Year;

private static readonly Assembly[] OurAssemblies =
AppDomain.CurrentDomain
.GetAssemblies()
.Where(OurCodePredicate)
.ToArray();
private static Assembly[] ourAssemblies;

public static string BinaryName => AppConfigHelper.IsWindows ? Name : "AnalysisPrograms";

public static string Organization { get; } = "QUT";

public static string Website { get; } = "http://research.ecosounds.org/";
Expand All @@ -37,20 +34,45 @@ public static class Meta

public static string Repository { get; } = "https://github.com/QutBioacoustics/audio-analysis";

internal static Assembly[] QutAssemblies
{
get
{
if (ourAssemblies == null) {
// warning: there are subtle runtime races that change which assemblies are
// currently loaded when this code runs. Caching it once with static
// intializer results in us returning only some of the assemblies
// in our solution because the others have not been loaded yet.

// the current best solution is to just force load the missing assembly 🤷‍♂️
AppDomain.CurrentDomain.Load("TowseyLibrary");

ourAssemblies = AppDomain
.CurrentDomain
.GetAssemblies()
.Where(OurCodePredicate)
.ToArray();
}

return ourAssemblies;
}
}

public static string GetDocsUrl(string page)
{
return $"{Repository}/blob/master/docs/{page}";
}

public static IEnumerable<TypeInfo> GetTypesFromQutAssemblies<T>()
{
return OurAssemblies.SelectMany(x => x.DefinedTypes.Where(typeof(T).IsAssignableFrom));
var targetType = typeof(T);
return QutAssemblies.SelectMany(x => x.DefinedTypes.Where(y => targetType.IsAssignableFrom(y)));
}

public static IEnumerable<T> GetAttributesFromQutAssemblies<T>()
where T : Attribute
{
var result = OurAssemblies
var result = QutAssemblies
.SelectMany(a => a.DefinedTypes)
.SelectMany(t => t.GetCustomAttributes(typeof(T)))
.Cast<T>();
Expand Down
3 changes: 1 addition & 2 deletions tests/Acoustics.Test/RuntimesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,7 @@ public void HasSupportForLongPaths()
}

// this should fail if not supported
// ReSharper disable once ObjectCreationAsStatement
new FileInfo(longPath);
_ = new FileInfo(longPath);
}
}
}
74 changes: 41 additions & 33 deletions tests/Acoustics.Test/Shared/CsvTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,17 @@ namespace Acoustics.Test.Shared
using System.Text;
using Acoustics.Shared;
using Acoustics.Shared.Csv;
using Acoustics.Test.TestHelpers;
using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;

using global::AnalysisBase.ResultBases;
using global::AnalysisPrograms.EventStatistics;
using global::AudioAnalysisTools;
using global::AudioAnalysisTools.EventStatistics;
using global::AudioAnalysisTools.Indices;
using global::TowseyLibrary;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TestHelpers;

[TestClass]
public class CsvTests
Expand All @@ -50,8 +49,8 @@ static CsvTests()
// pump static class initializers - it seems when running multiple tests that sometimes
// these classes are not discovered.

AcousticEvent aev = new AcousticEvent();
ImportedEvent iev = new ImportedEvent();
_ = new AcousticEvent();
_ = new ImportedEvent();
}

[TestInitialize]
Expand Down Expand Up @@ -81,7 +80,7 @@ public void TestWriteSimpleMatrix()
new[] { 12, 13, 14, 15 },
new[] { 16, 17, 18, 19 });

this.AssertCsvEqual(expected, this.testFile);
AssertCsvEqual(expected, this.testFile);
}

[TestMethod]
Expand All @@ -95,7 +94,7 @@ public void TestWriteSimpleMatrixRotateAntiClockwise()
new[] { 1, 5, 9, 13, 17 },
new[] { 0, 4, 8, 12, 16 });

this.AssertCsvEqual(expected, this.testFile);
AssertCsvEqual(expected, this.testFile);
}

[TestMethod]
Expand All @@ -109,7 +108,7 @@ public void TestWriteSimpleMatrixColumnMajorAlternateName()
new[] { 2, 6, 10, 14, 18 },
new[] { 3, 7, 11, 15, 19 });

this.AssertCsvEqual(expected, this.testFile);
AssertCsvEqual(expected, this.testFile);
}

[TestMethod]
Expand All @@ -123,7 +122,7 @@ public void TestWriteSimpleMatrixColumnMajorFlippedAlternateName()
new[] { 18, 14, 10, 6, 2 },
new[] { 19, 15, 11, 7, 3 });

this.AssertCsvEqual(expected, this.testFile);
AssertCsvEqual(expected, this.testFile);
}

[TestMethod]
Expand Down Expand Up @@ -206,7 +205,8 @@ public void TestTimeSpanRoundTrip()
for (int i = 0; i < data.Length; i++)
{
var ticks = Math.Pow(i, 10) * random.NextDouble();
data[i] = new CsvTestClass {
data[i] = new CsvTestClass
{
SomeNumber = random.Next(),
SomeTimeSpan = TimeSpan.FromTicks((long)ticks),
};
Expand Down Expand Up @@ -295,9 +295,13 @@ public void TestCsvClassMapsAreAutomaticallyRegistered()
};

// test reflection is working
var actual = Meta.GetTypesFromQutAssemblies<ClassMap>().ToArray();
// Debug.WriteLine("Actual classmaps:\n" + actual.FormatList());
// Debug.WriteLine("Actual assemblies:\n" + Meta.QutAssemblies.Select(x => x.FullName).FormatList());

CollectionAssert.AreEquivalent(
partialExpected.Select(x => x.Item2).ToArray(),
Meta.GetTypesFromQutAssemblies<ClassMap>().ToArray());
actual);

foreach (var (type, classMapType) in partialExpected)
{
Expand Down Expand Up @@ -382,7 +386,7 @@ public void TestBaseTypesAreNotSerializedAsArray()
}

/// <summary>
/// For some inane reason CsvHelper does not downcast to derived types!
/// For some inane reason CsvHelper does not downcast to derived types.
/// </summary>
[TestMethod]
public void TestChildTypesAreSerializedWhenWrappedAsEnumerableParentType()
Expand All @@ -403,7 +407,7 @@ public void TestChildTypesAreSerializedWhenWrappedAsEnumerableParentType()
}

/// <summary>
/// For some inane reason CsvHelper does not downcast to derived types!
/// For some inane reason CsvHelper does not downcast to derived types.
/// </summary>
[TestMethod]
public void TestChildTypesAreSerializedWhenWrappedAsEnumerableParentType_AcousticEvent()
Expand All @@ -424,15 +428,6 @@ public void TestChildTypesAreSerializedWhenWrappedAsEnumerableParentType_Acousti
Assert.AreNotEqual(childText, baseText);
}

public class CultureDataTester
{
public double value { get; set; }
public double infinity { get; set; }
public double nan { get; set; }
public DateTime date { get; set; }
public DateTimeOffset dateOffset { get; set; }
}

[TestMethod]
public void TestInvariantCultureIsUsed()
{
Expand All @@ -441,23 +436,22 @@ public void TestInvariantCultureIsUsed()

var o = new CultureDataTester
{
value = -789123.456,
infinity = double.NegativeInfinity,
nan = double.NaN,
date = now,
dateOffset = nowOffset
Value = -789123.456,
Infinity = double.NegativeInfinity,
Nan = double.NaN,
Date = now,
DateOffset = nowOffset,
};
Csv.WriteToCsv(
this.testFile,
new CultureDataTester[] { o });

var actual = File.ReadAllText(this.testFile.FullName);
var expected = $@"value,infinity,nan,date,dateOffset
var expected = $@"{nameof(CultureDataTester.Value)},{nameof(CultureDataTester.Infinity)},{nameof(CultureDataTester.Nan)},{nameof(CultureDataTester.Date)},{nameof(CultureDataTester.DateOffset)}
-789123.456,-Infinity,NaN,3913-03-12T00:31:41.1121314,3913-03-12T00:31:41.1121314+10:00
".NormalizeToCrLf();

Assert.AreEqual(expected, actual);

}

[TestMethod]
Expand All @@ -474,10 +468,9 @@ public void TestInvariantCultureIsUsedMatrix()
".NormalizeToCrLf();

Assert.AreEqual(expected, actual);

}

private void AssertCsvEqual(string expected, FileInfo actual)
private static void AssertCsvEqual(string expected, FileInfo actual)
{
var lines = File.ReadAllText(actual.FullName);

Expand All @@ -490,12 +483,14 @@ private void AssertCsvEqual(string expected, FileInfo actual)

private static string CsvExpectedHelper(params int[][] indexes)
{
return "Index," + string.Join(",", indexes[0].Select((s, i) => "c00000" + i)) + Environment.NewLine
// per https://tools.ietf.org/html/rfc4180
const string csvNewline = "\r\n";
return "Index," + string.Join(",", indexes[0].Select((s, i) => "c00000" + i)) + csvNewline
+ string.Join(
Environment.NewLine,
csvNewline,
indexes.Select(
(row, rowIndex) => rowIndex + row.Aggregate(string.Empty, (s, i) => s + "," + GetValue(i))))
+ Environment.NewLine;
+ csvNewline;
}

private static string GetValue(int index)
Expand All @@ -509,5 +504,18 @@ public class CsvTestClass

public TimeSpan SomeTimeSpan { get; set; }
}

public class CultureDataTester
{
public double Value { get; set; }

public double Infinity { get; set; }

public double Nan { get; set; }

public DateTime Date { get; set; }

public DateTimeOffset DateOffset { get; set; }
}
}
}

0 comments on commit cf15a27

Please sign in to comment.