// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq.Expressions; using Xunit; namespace Microsoft.AspNetCore.Mvc.ViewFeatures { public class CachedExpressionCompilerTest { [Fact] public void Process_IdentityExpression() { // Arrange var model = new TestModel(); var expression = GetTestModelExpression(m => m); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Same(model, result); } [Fact] public void Process_CachesIdentityExpression() { // Arrange var expression1 = GetTestModelExpression(m => m); var expression2 = GetTestModelExpression(m => m); // Act var func1 = CachedExpressionCompiler.Process(expression1); var func2 = CachedExpressionCompiler.Process(expression2); // Assert Assert.NotNull(func1); Assert.Same(func1, func2); } [Fact] public void Process_ConstLookup() { // Arrange var model = new TestModel(); var differentModel = new DifferentModel(); var expression = GetTestModelExpression(m => differentModel); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Same(differentModel, result); } [Fact] public void Process_ConstLookup_ReturningNull() { // Arrange var model = new TestModel(); var expression = GetTestModelExpression(m => (DifferentModel)null); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ConstLookup_WithNullModel() { // Arrange var differentModel = new DifferentModel(); var expression = GetTestModelExpression(m => differentModel); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(null); Assert.Same(differentModel, result); } [Fact] public void Process_ConstLookup_UsingCachedValue() { // Arrange var model = new TestModel(); var differentModel = new DifferentModel(); var expression1 = GetTestModelExpression(m => differentModel); var expression2 = GetTestModelExpression(m => differentModel); // Act - 1 var func1 = CachedExpressionCompiler.Process(expression1); // Assert - 1 var result1 = func1(null); Assert.Same(differentModel, result1); // Act - 2 var func2 = CachedExpressionCompiler.Process(expression2); // Assert - 2 var result2 = func1(null); Assert.Same(differentModel, result2); } [Fact] public void Process_ConstLookup_WhenCapturedLocalChanges() { // Arrange var model = new TestModel(); var differentModel = new DifferentModel(); var expression = GetTestModelExpression(m => differentModel); // Act var func = CachedExpressionCompiler.Process(expression); // Assert - 1 var result1 = func(null); Assert.Same(differentModel, result1); // Act - 2 differentModel = new DifferentModel(); // Assert - 2 var result2 = func(null); Assert.NotSame(differentModel, result1); Assert.Same(differentModel, result2); } [Fact] public void Process_ConstLookup_WithPrimitiveConstant() { // Arrange var model = new TestModel(); var expression = GetTestModelExpression(m => 10); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(10, result); } [Fact] public void Process_StaticFieldAccess() { // Arrange var model = new TestModel(); var expression = GetTestModelExpression(m => TestModel.StaticField); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal("StaticValue", result); } [Fact] public void Process_CachesStaticFieldAccess() { // Arrange var expression1 = GetTestModelExpression(m => TestModel.StaticField); var expression2 = GetTestModelExpression(m => TestModel.StaticField); // Act var func1 = CachedExpressionCompiler.Process(expression1); var func2 = CachedExpressionCompiler.Process(expression2); // Assert Assert.NotNull(func1); Assert.Same(func1, func2); } [Fact] public void Process_StaticPropertyAccess() { // Arrange var expected = "TestValue"; TestModel.StaticProperty = expected; var model = new TestModel(); var expression = GetTestModelExpression(m => TestModel.StaticProperty); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(expected, result); } [Fact] public void Process_CachesStaticPropertyAccess() { // Arrange var expression1 = GetTestModelExpression(m => TestModel.StaticProperty); var expression2 = GetTestModelExpression(m => TestModel.StaticProperty); // Act var func1 = CachedExpressionCompiler.Process(expression1); var func2 = CachedExpressionCompiler.Process(expression2); // Assert Assert.NotNull(func1); Assert.Same(func1, func2); } [Fact] public void Process_StaticPropertyAccess_WithNullModel() { // Arrange var expected = "TestValue"; TestModel.StaticProperty = expected; var expression = GetTestModelExpression(m => TestModel.StaticProperty); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(null); Assert.Equal(expected, result); } [Fact] public void Process_ConstFieldLookup() { // Arrange var model = new TestModel(); var expression = GetTestModelExpression(m => DifferentModel.Constant); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(10, result); } [Fact] public void Process_ConstFieldLookup_WthNullModel() { // Arrange var expression = GetTestModelExpression(m => DifferentModel.Constant); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(null); Assert.Equal(10, result); } [Fact] public void Process_SimpleMemberAccess() { // Arrange var model = new TestModel { Name = "Test" }; var expression = GetTestModelExpression(m => m.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal("Test", result); } [Fact] public void Process_CachesSimpleMemberAccess() { // Arrange var expression1 = GetTestModelExpression(m => m.Name); var expression2 = GetTestModelExpression(m => m.Name); // Act var func1 = CachedExpressionCompiler.Process(expression1); var func2 = CachedExpressionCompiler.Process(expression2); // Assert Assert.NotNull(func1); Assert.Same(func1, func2); } [Fact] public void Process_SimpleMemberAccess_ToPrimitive() { // Arrange var model = new TestModel { Age = 12 }; var expression = GetTestModelExpression(m => m.Age); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(12, result); } [Fact] public void Process_SimpleMemberAccess_WithNullModel() { // Arrange var model = (TestModel)null; var expression = GetTestModelExpression(m => m.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_SimpleMemberAccess_ToPrimitive_WithNullModel() { // Arrange var model = (TestModel)null; var expression = GetTestModelExpression(m => m.Age); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer() { // Arrange var model = new BadEqualityModel { Id = 7 }; var expression = GetExpression(m => m.Id); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(7, result); } [Fact] public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer_WithNullModel() { // Arrange var model = (BadEqualityModel)null; var expression = GetExpression(m => m.Id); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_SimpleMemberAccess_OnValueTypeWithBadEqualityComparer() { // Arrange var model = new BadEqualityValueTypeModel { Id = 7 }; var expression = GetExpression(m => m.Id); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(7, result); } [Fact] public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer_WithDefaultValue() { // Arrange var model = (BadEqualityValueTypeModel)default; var expression = GetExpression(m => m.Id); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(model.Id, result); } [Fact] public void Process_SimpleMemberAccess_OnValueType() { // Arrange var model = new DateTime(2000, 1, 1); var expression = GetExpression(m => m.Year); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(2000, result); } [Fact] public void Process_SimpleMemberAccess_OnValueType_WithDefaultValue() { // Arrange var model = default(DateTime); var expression = GetExpression(m => m.Year); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(1, result); } [Fact] public void Process_SimpleMemberAccess_OnNullableValueType() { // Arrange var model = new DateTime(2000, 1, 1); var nullableModel = (DateTime?)model; var expression = GetExpression(m => m.Value); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(nullableModel); Assert.Equal(model, result); } [Fact] public void Process_SimpleMemberAccess_OnNullableValueType_WithNullValue() { // Arrange var nullableModel = (DateTime?)null; var expression = GetExpression(m => m.Value); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(nullableModel); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_ToValueType() { // Arrange var dateTime = new DateTime(2000, 1, 1); var model = new TestModel { Date = dateTime }; var expression = GetTestModelExpression(m => m.Date.Year); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(2000, result); } [Fact] public void Process_ChainedMemberAccess_ToValueType_WithNullModel() { // Arrange var model = (TestModel)null; var expression = GetTestModelExpression(m => m.Date.Year); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_ToReferenceType() { // Arrange var expected = "Test1"; var model = new TestModel { DifferentModel = new DifferentModel { Name = expected } }; var expression = GetTestModelExpression(m => m.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(expected, result); } [Fact] public void Process_CachesChainedMemberAccess() { // Arrange var expression1 = GetTestModelExpression(m => m.DifferentModel.Name); var expression2 = GetTestModelExpression(m => m.DifferentModel.Name); // Act var func1 = CachedExpressionCompiler.Process(expression1); var func2 = CachedExpressionCompiler.Process(expression2); // Assert Assert.NotNull(func1); Assert.Same(func1, func2); } [Fact] public void Process_CachesChainedMemberAccess_ToValueType() { // Arrange var expression1 = GetTestModelExpression(m => m.Date.Year); var expression2 = GetTestModelExpression(m => m.Date.Year); // Act var func1 = CachedExpressionCompiler.Process(expression1); var func2 = CachedExpressionCompiler.Process(expression2); // Assert Assert.NotNull(func1); Assert.Same(func1, func2); } [Fact] public void Process_ChainedMemberAccess_LongChain_WithReferenceType() { // Arrange var expected = "TestVal"; var model = new Chain0Model { Chain1 = new Chain1Model { ValueTypeModel = new ValueType1 { TestModel = new TestModel { DifferentModel = new DifferentModel { Name = expected } } } } }; var expression = GetExpression(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(expected, result); } [Fact] public void Process_ChainedMemberAccess_LongChain_WithNullIntermediary() { // Arrange var model = new Chain0Model { Chain1 = new Chain1Model { ValueTypeModel = new ValueType1 { TestModel = null }, } }; var expression = GetExpression(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_LongChain_WithNullValueTypeAccessor() { // Arrange // Chain2 is a value type var model = new Chain0Model { Chain1 = null }; var expression = GetExpression(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_LongChain_WithNullableValueType() { // Arrange var expected = "TestVal"; var model = new Chain0Model { Chain1 = new Chain1Model { NullableValueTypeModel = new ValueType1 { TestModel = new TestModel { DifferentModel = new DifferentModel { Name = expected } } } } }; var expression = GetExpression(m => m.Chain1.NullableValueTypeModel.Value.TestModel.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(expected, result); } [Fact] public void Process_ChainedMemberAccess_LongChain_WithNullValuedNullableValueType() { // Arrange var model = new Chain0Model { Chain1 = new Chain1Model { NullableValueTypeModel = null } }; var expression = GetExpression(m => m.Chain1.NullableValueTypeModel.Value.TestModel.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_ToReferenceType_WithNullIntermediary() { // Arrange var model = new TestModel { DifferentModel = null }; var expression = GetTestModelExpression(m => m.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_ToReferenceType_WithNullModel() { // Arrange var model = (TestModel)null; var expression = GetTestModelExpression(m => m.DifferentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_OfValueTypes_ReturningReferenceTypeMember() { // Arrange var expected = "TestName"; var model = new ValueType1 { ValueType2 = new ValueType2 { Name = expected }, }; var expression = GetExpression(m => m.ValueType2.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(expected, result); } [Fact] public void Process_ChainedMemberAccess_OfValueTypes_ReturningValueType() { // Arrange var expected = new DateTime(2001, 1, 1); var model = new ValueType1 { ValueType2 = new ValueType2 { Date = expected }, }; var expression = GetExpression(m => m.ValueType2.Date); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(expected, result); } [Fact] public void Process_ChainedMemberAccess_OfValueTypes_IncludingNullableType() { // Arrange var expected = "TestName"; var model = new ValueType1 { NullableValueType2 = new ValueType2 { Name = expected }, }; var expression = GetExpression(m => m.NullableValueType2.Value.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Equal(expected, result); } [Fact] public void Process_ChainedMemberAccess_OfValueTypes_WithNullValuedNullable() { // Arrange var model = new ValueType1 { NullableValueType2 = null }; var expression = GetExpression(m => m.NullableValueType2.Value.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_ChainedMemberAccess_OfValueTypes_WithNullValuedNullable_ReturningValueType() { // Arrange var model = new ValueType1 { NullableValueType2 = null }; var expression = GetExpression(m => m.NullableValueType2.Value.Date); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Null(result); } [Fact] public void Process_MemberAccessOnCapturedVariable_ReturnsNull() { // Arrange var differentModel = new DifferentModel { Name = "Test" }; var expression = GetTestModelExpression(m => differentModel.Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.Null(func); } [Fact] public void Process_CapturedVariable() { // Arrange var differentModel = new DifferentModel(); var model = new TestModel(); var expression = GetTestModelExpression(m => differentModel); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Same(differentModel, result); } [Fact] public void Process_CapturedVariable_WithNullModel() { // Arrange var differentModel = new DifferentModel(); var model = (TestModel)null; var expression = GetTestModelExpression(m => differentModel); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.NotNull(func); var result = func(model); Assert.Same(differentModel, result); } [Fact] public void Process_MemberAccess_OnCapturedVariable_ReturnsNull() { // Arrange var differentModel = "Hello world"; var expression = GetTestModelExpression(m => differentModel.Length); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.Null(func); } [Fact] public void Process_ComplexChainedMemberAccess_ReturnsNull() { // Arrange var expected = "SomeName"; var model = new TestModel { DifferentModels = new[] { new DifferentModel { Name = expected } } }; var expression = GetTestModelExpression(m => m.DifferentModels[0].Name); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.Null(func); } [Fact] public void Process_ArrayMemberAccess_ReturnsNull() { // Arrange var expression = GetTestModelExpression(m => m.Sizes[1]); // Act var func = CachedExpressionCompiler.Process(expression); // Assert Assert.Null(func); } private static Expression> GetExpression(Expression> expression) => expression; private static Expression> GetTestModelExpression(Expression> expression) => GetExpression(expression); public class TestModel { public static readonly string StaticField = "StaticValue"; public static string StaticProperty { get; set; } public int Age { get; set; } public string Name { get; set; } public DateTime Date { get; set; } public DifferentModel DifferentModel { get; set; } public int[] Sizes { get; set; } public DifferentModel[] DifferentModels { get; set; } } public class DifferentModel { public const int Constant = 10; public string Name { get; set; } } public class Chain0Model { public Chain1Model Chain1 { get; set; } } public class Chain1Model { public ValueType1 ValueTypeModel { get; set; } public ValueType1? NullableValueTypeModel { get; set; } } public struct ValueType1 { public TestModel TestModel { get; set; } public ValueType2 ValueType2 { get; set; } public ValueType2? NullableValueType2 { get; set; } } public struct ValueType2 { public string Name { get; set; } public DateTime Date { get; set; } } public class BadEqualityModel { public int Id { get; set; } public override bool Equals(object obj) { return this == obj; } public static bool operator ==(BadEqualityModel a, object b) { if (a is null || b is null) { throw new TimeZoneNotFoundException(); } return true; } public static bool operator !=(BadEqualityModel a, object b) { return !(a == b); } public override int GetHashCode() => 0; } public struct BadEqualityValueTypeModel { public int Id { get; set; } public override bool Equals(object obj) { return this == obj; } public static bool operator ==(BadEqualityValueTypeModel a, object b) { if (b is null) { throw new TimeZoneNotFoundException(); } return true; } public static bool operator !=(BadEqualityValueTypeModel a, object b) { return !(a == b); } public override int GetHashCode() => 0; } } }