diff --git a/src/System.Runtime/tests/System/LazyTests.cs b/src/System.Runtime/tests/System/LazyTests.cs index 1dc7bdcf5546..6badc2072456 100644 --- a/src/System.Runtime/tests/System/LazyTests.cs +++ b/src/System.Runtime/tests/System/LazyTests.cs @@ -2,7 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; using System.Threading; +using System.Reflection; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Runtime.Serialization.Formatters.Tests; using Xunit; namespace System.Tests @@ -103,17 +108,278 @@ public static void ToString_DoesntForceAllocation() Assert.Equal("1", lazy.ToString()); } + private static void Value_Invalid_Impl(ref Lazy x, Lazy lazy) + { + x = lazy; + Assert.Throws(() => lazy.Value); + } + [Fact] public static void Value_Invalid() { - string lazilyAllocatedValue = "abc"; + Lazy x = null; + Func f = () => x.Value; + + Value_Invalid_Impl(ref x, new Lazy(f)); + Value_Invalid_Impl(ref x, new Lazy(f, true)); + Value_Invalid_Impl(ref x, new Lazy(f, false)); + Value_Invalid_Impl(ref x, new Lazy(f, LazyThreadSafetyMode.ExecutionAndPublication)); + Value_Invalid_Impl(ref x, new Lazy(f, LazyThreadSafetyMode.None)); + + // When used with LazyThreadSafetyMode.PublicationOnly this causes a stack overflow + // Value_Invalid_Impl(ref x, new Lazy(f, LazyThreadSafetyMode.PublicationOnly)); + } + + public class InitiallyExceptionThrowingCtor + { + public static int counter = 0; + public static int getValue() + { + if (++counter < 5) + throw new Exception(); + else + return counter; + } + + public int Value { get; } + + public InitiallyExceptionThrowingCtor() + { + Value = getValue(); + } + } + + public static IEnumerable Ctor_ExceptionRecovery_MemberData() + { + yield return new object[] { new Lazy(), 5 }; + yield return new object[] { new Lazy(true), 5 }; + yield return new object[] { new Lazy(false), 5 }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.ExecutionAndPublication), 5 }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.None), 5 }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.PublicationOnly), 5 }; + } + + [Theory] + [MemberData(nameof(Ctor_ExceptionRecovery_MemberData))] + public static void Ctor_ExceptionRecovery(Lazy lazy, int expected) + { + InitiallyExceptionThrowingCtor.counter = 0; + InitiallyExceptionThrowingCtor result = null; + for (var i = 0; i < 10; ++i) + { + try { result = lazy.Value; } catch (Exception) { } + } + Assert.Equal(result.Value, expected); + } + + private static void Value_ExceptionRecovery_IntImpl(Lazy lazy, ref int counter, int expected) + { + counter = 0; + int result = 0; + for (var i = 0; i < 10; ++i) + { + try { result = lazy.Value; } catch (Exception) { } + } + Assert.Equal(result, expected); + } + + private static void Value_ExceptionRecovery_StringImpl(Lazy lazy, ref int counter, string expected) + { + counter = 0; + var result = default(string); + for (var i = 0; i < 10; ++i) + { + try { result = lazy.Value; } catch (Exception) { } + } + Assert.Equal(expected, result); + } + + [Fact] + public static void Value_ExceptionRecovery() + { + int counter = 0; // set in test function + + var fint = new Func (() => { if (++counter < 5) throw new Exception(); else return counter; }); + var fobj = new Func(() => { if (++counter < 5) throw new Exception(); else return counter.ToString(); }); + + Value_ExceptionRecovery_IntImpl(new Lazy(fint), ref counter, 0); + Value_ExceptionRecovery_IntImpl(new Lazy(fint, true), ref counter, 0); + Value_ExceptionRecovery_IntImpl(new Lazy(fint, false), ref counter, 0); + Value_ExceptionRecovery_IntImpl(new Lazy(fint, LazyThreadSafetyMode.ExecutionAndPublication), ref counter, 0); + Value_ExceptionRecovery_IntImpl(new Lazy(fint, LazyThreadSafetyMode.None), ref counter, 0); + Value_ExceptionRecovery_IntImpl(new Lazy(fint, LazyThreadSafetyMode.PublicationOnly), ref counter, 5); + + Value_ExceptionRecovery_StringImpl(new Lazy(fobj), ref counter, null); + Value_ExceptionRecovery_StringImpl(new Lazy(fobj, true), ref counter, null); + Value_ExceptionRecovery_StringImpl(new Lazy(fobj, false), ref counter, null); + Value_ExceptionRecovery_StringImpl(new Lazy(fobj, LazyThreadSafetyMode.ExecutionAndPublication), ref counter, null); + Value_ExceptionRecovery_StringImpl(new Lazy(fobj, LazyThreadSafetyMode.None), ref counter, null); + Value_ExceptionRecovery_StringImpl(new Lazy(fobj, LazyThreadSafetyMode.PublicationOnly), ref counter, 5.ToString()); + } + + class MyException + : Exception + { + public int Value { get; } + + public MyException(int value) + { + Value = value; + } + } + + public class ExceptionInCtor + { + public ExceptionInCtor() : this(99) { } + + public ExceptionInCtor(int value) + { + throw new MyException(value); + } + } + + public static IEnumerable Value_Func_Exception_MemberData() + { + yield return new object[] { new Lazy(() => { throw new MyException(99); }) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, true) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, false) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, LazyThreadSafetyMode.ExecutionAndPublication) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, LazyThreadSafetyMode.None) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, LazyThreadSafetyMode.PublicationOnly) }; + } + + [Theory] + [MemberData(nameof(Value_Func_Exception_MemberData))] + public static void Value_Func_Exception(Lazy lazy) + { + Assert.Throws(() => lazy.Value); + } + + public static IEnumerable Value_FuncCtor_Exception_MemberData() + { + yield return new object[] { new Lazy(() => new ExceptionInCtor(99)) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), true) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), false) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), LazyThreadSafetyMode.ExecutionAndPublication) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), LazyThreadSafetyMode.None) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), LazyThreadSafetyMode.PublicationOnly) }; + } + + [Theory] + [MemberData(nameof(Value_FuncCtor_Exception_MemberData))] + public static void Value_FuncCtor_Exception(Lazy lazy) + { + Assert.Throws(() => lazy.Value); + } + + public static IEnumerable Value_TargetInvocationException_MemberData() + { + yield return new object[] { new Lazy() }; + yield return new object[] { new Lazy(true) }; + yield return new object[] { new Lazy(false) }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.ExecutionAndPublication) }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.None) }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.PublicationOnly) }; + } - int x = 0; - Lazy lazy = null; - lazy = new Lazy(() => x++ < 5 ? lazy.Value : "Test", true); + [Theory] + [MemberData(nameof(Value_TargetInvocationException_MemberData))] + public static void Value_TargetInvocationException(Lazy lazy) + { + Assert.Throws(() => lazy.Value); + } + + public static IEnumerable Exceptions_Func_Idempotent_MemberData() + { + yield return new object[] { new Lazy(() => { throw new MyException(99); }) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, true) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, false) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, LazyThreadSafetyMode.ExecutionAndPublication) }; + yield return new object[] { new Lazy(() => { throw new MyException(99); }, LazyThreadSafetyMode.None) }; + } - Assert.Throws(() => lazilyAllocatedValue = lazy.Value); - Assert.Equal("abc", lazilyAllocatedValue); + [Theory] + [MemberData(nameof(Exceptions_Func_Idempotent_MemberData))] + public static void Exceptions_Func_Idempotent(Lazy x) + { + var e = Assert.ThrowsAny(() => x.Value); + Assert.Same(e, Assert.ThrowsAny(() => x.Value)); + } + + public static IEnumerable Exceptions_Ctor_Idempotent_MemberData() + { + yield return new object[] { new Lazy(() => new ExceptionInCtor(99)) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), true) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), false) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), LazyThreadSafetyMode.ExecutionAndPublication) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), LazyThreadSafetyMode.None) }; + } + + [Theory] + [MemberData(nameof(Exceptions_Ctor_Idempotent_MemberData))] + public static void Exceptions_Ctor_Idempotent(Lazy x) + { + var e = Assert.ThrowsAny(() => x.Value); + Assert.Same(e, Assert.ThrowsAny(() => x.Value)); + } + + public static IEnumerable Exceptions_Func_NotIdempotent_MemberData() + { + yield return new object[] { new Lazy(() => { throw new MyException(99); }, LazyThreadSafetyMode.PublicationOnly) }; + } + + public static IEnumerable Exceptions_Ctor_NotIdempotent_MemberData() + { + yield return new object[] { new Lazy() }; + yield return new object[] { new Lazy(true) }; + yield return new object[] { new Lazy(false) }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.ExecutionAndPublication) }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.None) }; + yield return new object[] { new Lazy(LazyThreadSafetyMode.PublicationOnly) }; + yield return new object[] { new Lazy(() => new ExceptionInCtor(99), LazyThreadSafetyMode.PublicationOnly) }; + } + + [Theory] + [MemberData(nameof(Exceptions_Func_NotIdempotent_MemberData))] + public static void Exceptions_Func_NotIdempotent(Lazy x) + { + var e = Assert.ThrowsAny(() => x.Value); + Assert.NotSame(e, Assert.ThrowsAny(() => x.Value)); + } + + [Theory] + [MemberData(nameof(Exceptions_Ctor_NotIdempotent_MemberData))] + public static void Exceptions_Ctor_NotIdempotent(Lazy x) + { + var e = Assert.ThrowsAny(() => x.Value); + Assert.NotSame(e, Assert.ThrowsAny(() => x.Value)); + } + + [Fact] + public static void Serialization_ValueType() + { + var stream = new MemoryStream(); + var formatter = new BinaryFormatter(); + formatter.Serialize(stream, new Lazy(() => 42)); + stream.Seek(0, SeekOrigin.Begin); + + var fortytwo = (Lazy)formatter.Deserialize(stream); + Assert.True(fortytwo.IsValueCreated); + Assert.Equal(fortytwo.Value, 42); + } + + [Fact] + public static void Serialization_RefType() + { + var stream = new MemoryStream(); + var formatter = new BinaryFormatter(); + formatter.Serialize(stream, new Lazy(() => "42")); + stream.Seek(0, SeekOrigin.Begin); + + var x = BinaryFormatterHelpers.Clone(new object()); + var fortytwo = (Lazy)formatter.Deserialize(stream); + Assert.True(fortytwo.IsValueCreated); + Assert.Equal(fortytwo.Value, "42"); } [Theory]