public void InvokesEntrypoint_Async_Exception()
        {
            // Arrange
            var assembly = CompileToAssembly(@"
public static TaskCompletionSource<object> ContinueTcs { get; } = new TaskCompletionSource<object>();

public static async Task Main()
{
    await ContinueTcs.Task;
    DidMainExecute = true;
    throw new InvalidTimeZoneException(""Test message"");
}", out var didMainExecute);

            // Act/Assert 1: Waits for task
            EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
            Assert.False(didMainExecute());

            // Act/Assert 2: Continues
            // As above, we can't directly observe the exception handling behavior here,
            // so this is covered in E2E tests instead.
            var tcs = (TaskCompletionSource <object>)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null);

            tcs.SetResult(null);
            Assert.True(didMainExecute());
        }
        public void InvokesEntrypoint_Async_Success(bool hasReturnValue, bool hasParams)
        {
            // Arrange
            var returnTypeGenericParam = hasReturnValue ? "<int>" : string.Empty;
            var paramsDecl             = hasParams ? "string[] args" : string.Empty;
            var returnStatement        = hasReturnValue ? "return 123;" : "return;";
            var assembly = CompileToAssembly(@"
public static TaskCompletionSource<object> ContinueTcs { get; } = new TaskCompletionSource<object>();

static async Task" + returnTypeGenericParam + @" Main(" + paramsDecl + @")
{
    await ContinueTcs.Task;
    DidMainExecute = true;
    " + returnStatement + @"
}", out var didMainExecute);

            // Act/Assert 1: Waits for task
            // The fact that we're not blocking here proves that we're not executing the
            // metadata-declared entrypoint, as that would block
            EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
            Assert.False(didMainExecute());

            // Act/Assert 2: Continues
            var tcs = (TaskCompletionSource <object>)assembly.GetType("SomeApp.Program").GetProperty("ContinueTcs").GetValue(null);

            tcs.SetResult(null);
            Assert.True(didMainExecute());
        }
        public void InvokesEntrypoint_Sync_Exception()
        {
            // Arrange
            var assembly = CompileToAssembly(@"
public static void Main()
{
    DidMainExecute = true;
    throw new InvalidTimeZoneException(""Test message"");
}", out var didMainExecute);

            // Act/Assert
            // The fact that this doesn't throw shows that EntrypointInvoker is doing something
            // to handle the exception. We can't assert about what it does here, because that
            // would involve capturing console output, which isn't safe in unit tests. Instead
            // we'll check this in E2E tests.
            EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });
            Assert.True(didMainExecute());
        }
        public void InvokesEntrypoint_Sync_Success(bool hasReturnValue, bool hasParams)
        {
            // Arrange
            var returnType      = hasReturnValue ? "int" : "void";
            var paramsDecl      = hasParams ? "string[] args" : string.Empty;
            var returnStatement = hasReturnValue ? "return 123;" : "return;";
            var assembly        = CompileToAssembly(@"
static " + returnType + @" Main(" + paramsDecl + @")
{
    DidMainExecute = true;
    " + returnStatement + @"
}", out var didMainExecute);

            // Act
            EntrypointInvoker.InvokeEntrypoint(assembly.FullName, new string[] { });

            // Assert
            Assert.True(didMainExecute());
        }