Skip to content

Commit 293d547

Browse files
Unit tests for EntrypointInvoker
1 parent a0cc4c3 commit 293d547

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Reflection;
7+
using System.Runtime.Loader;
8+
using System.Threading.Tasks;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Xunit;
12+
13+
namespace Microsoft.AspNetCore.Blazor.Hosting
14+
{
15+
public class EntrypointInvokerTest
16+
{
17+
[Theory]
18+
[InlineData(false, false)]
19+
[InlineData(false, true)]
20+
[InlineData(true, false)]
21+
[InlineData(true, true)]
22+
public void InvokesEntrypoint_Sync_Success(bool hasReturnValue, bool hasParams)
23+
{
24+
// Arrange
25+
var returnType = hasReturnValue ? "int" : "void";
26+
var paramsDecl = hasParams ? "string[] args" : string.Empty;
27+
var returnStatement = hasReturnValue ? "return 123;" : "return;";
28+
var assembly = CompileToAssembly(@"
29+
static " + returnType + @" Main(" + paramsDecl + @")
30+
{
31+
DidMainExecute = true;
32+
" + returnStatement + @"
33+
}", out var didMainExecute);
34+
35+
// Act
36+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
37+
38+
// Assert
39+
Assert.True(didMainExecute());
40+
}
41+
42+
[Theory]
43+
[InlineData(false, false)]
44+
[InlineData(false, true)]
45+
[InlineData(true, false)]
46+
[InlineData(true, true)]
47+
public void InvokesEntrypoint_Async_Success(bool hasReturnValue, bool hasParams)
48+
{
49+
// Arrange
50+
var returnTypeGenericParam = hasReturnValue ? "<int>" : string.Empty;
51+
var paramsDecl = hasParams ? "string[] args" : string.Empty;
52+
var returnStatement = hasReturnValue ? "return 123;" : "return;";
53+
var assembly = CompileToAssembly(@"
54+
public static TaskCompletionSource<object> ContinueTcs { get; } = new TaskCompletionSource<object>();
55+
56+
static async Task" + returnTypeGenericParam + @" Main(" + paramsDecl + @")
57+
{
58+
await ContinueTcs.Task;
59+
DidMainExecute = true;
60+
" + returnStatement + @"
61+
}", out var didMainExecute);
62+
63+
// Act/Assert 1: Waits for task
64+
// The fact that we're not blocking here proves that we're not executing the
65+
// metadata-declared entrypoint, as that would block
66+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
67+
Assert.False(didMainExecute());
68+
69+
// Act/Assert 2: Continues
70+
var tcs = (TaskCompletionSource<object>)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null);
71+
tcs.SetResult(null);
72+
Assert.True(didMainExecute());
73+
}
74+
75+
[Fact]
76+
public void InvokesEntrypoint_Sync_Exception()
77+
{
78+
// Arrange
79+
var assembly = CompileToAssembly(@"
80+
public static void Main()
81+
{
82+
DidMainExecute = true;
83+
throw new InvalidTimeZoneException(""Test message"");
84+
}", out var didMainExecute);
85+
86+
// Act/Assert
87+
// The fact that this doesn't throw shows that EntrypointInvoker is doing something
88+
// to handle the exception. We can't assert about what it does here, because that
89+
// would involve capturing console output, which isn't safe in unit tests. Instead
90+
// we'll check this in E2E tests.
91+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
92+
Assert.True(didMainExecute());
93+
}
94+
95+
[Fact]
96+
public void InvokesEntrypoint_Async_Exception()
97+
{
98+
// Arrange
99+
var assembly = CompileToAssembly(@"
100+
public static TaskCompletionSource<object> ContinueTcs { get; } = new TaskCompletionSource<object>();
101+
102+
public static async Task Main()
103+
{
104+
await ContinueTcs.Task;
105+
DidMainExecute = true;
106+
throw new InvalidTimeZoneException(""Test message"");
107+
}", out var didMainExecute);
108+
109+
// Act/Assert 1: Waits for task
110+
EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
111+
Assert.False(didMainExecute());
112+
113+
// Act/Assert 2: Continues
114+
// As above, we can't directly observe the exception handling behavior here,
115+
// so this is covered in E2E tests instead.
116+
var tcs = (TaskCompletionSource<object>)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null);
117+
tcs.SetResult(null);
118+
Assert.True(didMainExecute());
119+
}
120+
121+
private static Assembly CompileToAssembly(string mainMethod, out Func<bool> didMainExecute)
122+
{
123+
var syntaxTree = CSharpSyntaxTree.ParseText(@"
124+
using System;
125+
using System.Threading.Tasks;
126+
127+
namespace SomeApp
128+
{
129+
public static class Program
130+
{
131+
public static bool DidMainExecute { get; private set; }
132+
133+
" + mainMethod + @"
134+
}
135+
}");
136+
137+
var compilation = CSharpCompilation.Create(
138+
$"TestAssembly-{Guid.NewGuid().ToString("D")}",
139+
new[] { syntaxTree },
140+
new[] { MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location) },
141+
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
142+
using var ms = new MemoryStream();
143+
var compilationResult = compilation.Emit(ms);
144+
ms.Seek(0, SeekOrigin.Begin);
145+
var assembly = AssemblyLoadContext.Default.LoadFromStream(ms);
146+
147+
var didMainExecuteProp = assembly.GetType("SomeApp.Program").GetProperty("DidMainExecute");
148+
didMainExecute = () => (bool)didMainExecuteProp.GetValue(null);
149+
150+
return assembly;
151+
}
152+
}
153+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
55
</PropertyGroup>
66

77
<ItemGroup>
88
<Reference Include="Microsoft.AspNetCore.Blazor" />
9+
<Reference Include="Microsoft.CodeAnalysis.CSharp" />
910
</ItemGroup>
1011

1112
</Project>

0 commit comments

Comments
 (0)