Skip to content

Commit

Permalink
Enable Native AOT compatibility (trimming support)
Browse files Browse the repository at this point in the history
This is a squashed merge of PR #1070.
  • Loading branch information
atifaziz committed Jul 6, 2024
1 parent bb478ee commit 2bedad6
Show file tree
Hide file tree
Showing 10 changed files with 456 additions and 56 deletions.
32 changes: 32 additions & 0 deletions MoreLinq.Test.Aot/MoreLinq.Test.Aot.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<OutputType>exe</OutputType>
<IsPackable>false</IsPackable>
<PublishAot>true</PublishAot>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest.Engine" Version="1.0.0-alpha.24266.1" />
<PackageReference Include="MSTest.SourceGeneration" Version="1.0.0-alpha.24266.1" />
<PackageReference Include="Microsoft.CodeCoverage.MSBuild" Version="17.10.4" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" Version="17.10.4" />
<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" Version="1.2.0" />
<PackageReference Include="Microsoft.Testing.Platform.MSBuild" Version="1.2.0" />
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
<PackageReference Include="MSTest.Analyzers" Version="3.3.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\MoreLinq\MoreLinq.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
235 changes: 235 additions & 0 deletions MoreLinq.Test.Aot/ToDataTableTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2024 Atif Aziz. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq.Test
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;

[TestClass]
public class ToDataTableTest
{
sealed class TestObject(int key)
{
public int KeyField = key;
public Guid? ANullableGuidField = Guid.NewGuid();

public string AString { get; } = "ABCDEFGHIKKLMNOPQRSTUVWXYSZ";
public decimal? ANullableDecimal { get; } = key / 3;
public object Unreadable { set => throw new NotImplementedException(); }

public object this[int index] { get => new(); set { } }

public override string ToString() => nameof(TestObject);
}

readonly IReadOnlyCollection<TestObject> testObjects;

public ToDataTableTest() =>
this.testObjects = Enumerable.Range(0, 3)
.Select(i => new TestObject(i))
.ToArray();

[TestMethod]
public void ToDataTableNullMemberExpressionMethod()
{
Expression<Func<TestObject, object?>>? expression = null;

[UnconditionalSuppressMessage("Aot", "IL2026")]
void Act() => _ = this.testObjects.ToDataTable(expression!);

var e = Assert.ThrowsException<ArgumentException>(Act);
Assert.AreEqual("expressions", e.ParamName);
}

[TestMethod]
public void ToDataTableTableWithWrongColumnNames()
{
using var dt = new DataTable();
_ = dt.Columns.Add("Test");

[UnconditionalSuppressMessage("Aot", "IL2026")]
void Act() => _ = this.testObjects.ToDataTable(dt);

var e = Assert.ThrowsException<ArgumentException>(Act);
Assert.AreEqual("table", e.ParamName);
}

[TestMethod]
public void ToDataTableTableWithWrongColumnDataType()
{
using var dt = new DataTable();
_ = dt.Columns.Add("AString", typeof(int));

[UnconditionalSuppressMessage("Aot", "IL2026")]
void Act() => _ = this.testObjects.ToDataTable(dt, t => t.AString);

var e = Assert.ThrowsException<ArgumentException>(Act);
Assert.AreEqual("table", e.ParamName);
}

void TestDataTableMemberExpression(Expression<Func<TestObject, object?>> expression)
{
[UnconditionalSuppressMessage("Aot", "IL2026")]
void Act() => _ = this.testObjects.ToDataTable(expression);

var e = Assert.ThrowsException<ArgumentException>(Act);
Assert.AreEqual("expressions", e.ParamName);
var innerException = e.InnerException;
Assert.IsNotNull(innerException);
Assert.IsInstanceOfType<ArgumentException>(innerException);
Assert.AreEqual("lambda", ((ArgumentException)innerException).ParamName);
}

[TestMethod]
public void ToDataTableMemberExpressionMethod()
{
TestDataTableMemberExpression(t => t.ToString());
}

[TestMethod]
public void ToDataTableMemberExpressionNonMember()
{
TestDataTableMemberExpression(t => t.ToString().Length);
}

[TestMethod]
public void ToDataTableMemberExpressionIndexer()
{
TestDataTableMemberExpression(t => t[0]);
}

[TestMethod]
public void ToDataTableMemberExpressionStatic()
{
TestDataTableMemberExpression(_ => DateTime.Now);
}

[TestMethod]
public void ToDataTableSchemaInDeclarationOrder()
{
[UnconditionalSuppressMessage("Aot", "IL2026")]
DataTable Act() => this.testObjects.ToDataTable();

var dt = Act();

// Assert properties first, then fields, then in declaration order

Assert.AreEqual("KeyField", dt.Columns[2].Caption);
Assert.AreEqual(typeof(int), dt.Columns[2].DataType);

Assert.AreEqual("ANullableGuidField", dt.Columns[3].Caption);
Assert.AreEqual(typeof(Guid), dt.Columns[3].DataType);
Assert.IsTrue(dt.Columns[3].AllowDBNull);

Assert.AreEqual("AString", dt.Columns[0].Caption);
Assert.AreEqual(typeof(string), dt.Columns[0].DataType);

Assert.AreEqual("ANullableDecimal", dt.Columns[1].Caption);
Assert.AreEqual(typeof(decimal), dt.Columns[1].DataType);

Assert.AreEqual(4, dt.Columns.Count);
}

[TestMethod]
public void ToDataTableContainsAllElements()
{
[UnconditionalSuppressMessage("Aot", "IL2026")]
DataTable Act() => this.testObjects.ToDataTable();

var dt = Act();

Assert.AreEqual(this.testObjects.Count, dt.Rows.Count);
}

[TestMethod]
public void ToDataTableWithExpression()
{
[UnconditionalSuppressMessage("Aot", "IL2026")]
DataTable Act() => this.testObjects.ToDataTable(t => t.AString);

var dt = Act();

Assert.AreEqual("AString", dt.Columns[0].Caption);
Assert.AreEqual(typeof(string), dt.Columns[0].DataType);

Assert.AreEqual(1, dt.Columns.Count);
}

[TestMethod]
public void ToDataTableWithSchema()
{
using var dt = new DataTable();
var columns = dt.Columns;
_ = columns.Add("Column1", typeof(int));
_ = columns.Add("Value", typeof(string));
_ = columns.Add("Column3", typeof(int));
_ = columns.Add("Name", typeof(string));

var vars = Environment.GetEnvironmentVariables()
.Cast<DictionaryEntry>()
.ToArray();

[UnconditionalSuppressMessage("Aot", "IL2026")]
void Act() => _ = vars.Select(e => new { Name = e.Key.ToString(), Value = e.Value!.ToString() })
.ToDataTable(dt, e => e.Name, e => e.Value);

Act();

var rows = dt.Rows.Cast<DataRow>().ToArray();
Assert.AreEqual(vars.Length, rows.Length);
CollectionAssert.AreEqual(vars.Select(e => e.Key).ToArray(), rows.Select(r => r["Name"]).ToArray());
CollectionAssert.AreEqual(vars.Select(e => e.Value).ToArray(), rows.Select(r => r["Value"]).ToArray());
}

readonly struct Point(int x, int y)
{
#pragma warning disable CA1805 // Do not initialize unnecessarily (avoids CS0649)
public static Point Empty = new();
#pragma warning restore CA1805 // Do not initialize unnecessarily
public bool IsEmpty => X == 0 && Y == 0;
public int X { get; } = x;
public int Y { get; } = y;
}

[TestMethod]
public void ToDataTableIgnoresStaticMembers()
{
[UnconditionalSuppressMessage("Aot", "IL2026")]
static DataTable Act() => new[] { new Point(12, 34) }.ToDataTable();

var points = Act();

Assert.AreEqual(3, points.Columns.Count);
var x = points.Columns["X"];
var y = points.Columns["Y"];
var empty = points.Columns["IsEmpty"];
Assert.IsNotNull(x);
Assert.IsNotNull(y);
Assert.IsNotNull(empty);
var row = points.Rows.Cast<DataRow>().Single();
Assert.AreEqual(12, row[x]);
Assert.AreEqual(34, row[y]);
Assert.AreEqual(row[empty], false);
}
}
}
9 changes: 6 additions & 3 deletions MoreLinq.Test/Throws.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ public static ExactTypeConstraint TypeOf<T>()
NUnit.Framework.Throws.TypeOf<T>();

public static EqualConstraint ArgumentException(string expectedParamName) =>
NUnit.Framework.Throws.ArgumentException.With.ParamName().EqualTo(expectedParamName);
NUnit.Framework.Throws.ArgumentException.With.ParamName(expectedParamName);

public static EqualConstraint ArgumentNullException(string expectedParamName) =>
NUnit.Framework.Throws.ArgumentNullException.With.ParamName().EqualTo(expectedParamName);
NUnit.Framework.Throws.ArgumentNullException.With.ParamName(expectedParamName);

public static ExactTypeConstraint ArgumentOutOfRangeException() =>
TypeOf<ArgumentOutOfRangeException>();

public static EqualConstraint ArgumentOutOfRangeException(string expectedParamName) =>
ArgumentOutOfRangeException().With.ParamName().EqualTo(expectedParamName);
ArgumentOutOfRangeException().With.ParamName(expectedParamName);

public static EqualConstraint ParamName(this ConstraintExpression constraint, string expectedParamName) =>
constraint.ParamName().EqualTo(expectedParamName);

static ResolvableConstraintExpression ParamName(this ConstraintExpression constraint) =>
constraint.Property(nameof(System.ArgumentException.ParamName));
Expand Down
32 changes: 17 additions & 15 deletions MoreLinq.Test/ToDataTableTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,33 +77,35 @@ public void ToDataTableTableWithWrongColumnDataType()
Throws.ArgumentException("table"));
}

void TestDataTableMemberExpression(Expression<Func<TestObject, object?>> expression)
{
Assert.That(() => this.testObjects.ToDataTable(expression),
Throws.ArgumentException("expressions")
.And.InnerException.With.ParamName("lambda"));
}

[Test]
public void ToDataTableMemberExpressionMethod()
{
Assert.That(() => this.testObjects.ToDataTable(t => t.ToString()),
Throws.ArgumentException("lambda"));
TestDataTableMemberExpression(t => t.ToString());
}


[Test]
public void ToDataTableMemberExpressionNonMember()
{
Assert.That(() => this.testObjects.ToDataTable(t => t.ToString().Length),
Throws.ArgumentException("lambda"));
TestDataTableMemberExpression(t => t.ToString().Length);
}

[Test]
public void ToDataTableMemberExpressionIndexer()
{
Assert.That(() => this.testObjects.ToDataTable(t => t[0]),
Throws.ArgumentException("lambda"));
TestDataTableMemberExpression(t => t[0]);
}

[Test]
public void ToDataTableMemberExpressionStatic()
{
Assert.That(() => _ = this.testObjects.ToDataTable(_ => DateTime.Now),
Throws.ArgumentException("lambda"));
TestDataTableMemberExpression(_ => DateTime.Now);
}

[Test]
Expand All @@ -113,19 +115,19 @@ public void ToDataTableSchemaInDeclarationOrder()

// Assert properties first, then fields, then in declaration order

Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString"));
Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string)));

Assert.That(dt.Columns[1].Caption, Is.EqualTo("ANullableDecimal"));
Assert.That(dt.Columns[1].DataType, Is.EqualTo(typeof(decimal)));

Assert.That(dt.Columns[2].Caption, Is.EqualTo("KeyField"));
Assert.That(dt.Columns[2].DataType, Is.EqualTo(typeof(int)));

Assert.That(dt.Columns[3].Caption, Is.EqualTo("ANullableGuidField"));
Assert.That(dt.Columns[3].DataType, Is.EqualTo(typeof(Guid)));
Assert.That(dt.Columns[3].AllowDBNull, Is.True);

Assert.That(dt.Columns[0].Caption, Is.EqualTo("AString"));
Assert.That(dt.Columns[0].DataType, Is.EqualTo(typeof(string)));

Assert.That(dt.Columns[1].Caption, Is.EqualTo("ANullableDecimal"));
Assert.That(dt.Columns[1].DataType, Is.EqualTo(typeof(decimal)));

Assert.That(dt.Columns.Count, Is.EqualTo(4));
}

Expand Down
6 changes: 6 additions & 0 deletions MoreLinq.sln
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoreLinq.Test", "MoreLinq.T
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MoreLinq.ExtensionsGenerator", "bld\ExtensionsGenerator\MoreLinq.ExtensionsGenerator.csproj", "{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MoreLinq.Test.Aot", "MoreLinq.Test.Aot\MoreLinq.Test.Aot.csproj", "{776973A3-AC2E-423E-8106-B4E296CE7752}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -43,6 +45,10 @@ Global
{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FA8F0E8-648A-4C4F-B1BB-B0C46959A36E}.Release|Any CPU.Build.0 = Release|Any CPU
{776973A3-AC2E-423E-8106-B4E296CE7752}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{776973A3-AC2E-423E-8106-B4E296CE7752}.Debug|Any CPU.Build.0 = Debug|Any CPU
{776973A3-AC2E-423E-8106-B4E296CE7752}.Release|Any CPU.ActiveCfg = Release|Any CPU
{776973A3-AC2E-423E-8106-B4E296CE7752}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Loading

0 comments on commit 2bedad6

Please sign in to comment.