// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Fakes;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Tests.Fakes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing;
using Xunit;

[assembly: HostingStartup(typeof(WebHostBuilderTests.TestHostingStartup))]

namespace Microsoft.AspNetCore.Hosting
{
    public class WebHostBuilderTests
    {
        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_honors_UseStartup_with_string(IWebHostBuilder builder)
        {
            builder = builder.UseServer(new TestServer());

            using (var host = builder.UseStartup("MyStartupAssembly").Build())
            {
                var options = CreateWebHostOptions(host.Services.GetRequiredService<IConfiguration>());
                Assert.Equal("MyStartupAssembly", options.ApplicationName);
                Assert.Equal("MyStartupAssembly", options.StartupAssembly);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task StartupMissing_Fallback(IWebHostBuilder builder)
        {
            var server = new TestServer();
            using (var host = builder.UseServer(server).UseStartup("MissingStartupAssembly").Build())
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "MissingStartupAssembly");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task StartupStaticCtorThrows_Fallback(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var host = builder.UseServer(server).UseStartup<StartupStaticCtorThrows>().Build();
            using (host)
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "Exception from static constructor");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void UseStartupThrowsWhenFactoryIsNull(IWebHostBuilder builder)
        {
            var server = new TestServer();
            Assert.Throws<ArgumentNullException>(() => builder.UseServer(server).UseStartup((Func<WebHostBuilderContext, object>)null));
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void UseStartupThrowsWhenFactoryReturnsNull(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var ex = Assert.Throws<InvalidOperationException>(() => builder.UseServer(server).UseStartup<object>(context => null).Build());
            Assert.Equal("The specified factory returned null startup instance.", ex.Message);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task MultipleUseStartupCallsLastWins(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var host = builder.UseServer(server)
                              .UseStartup<StartupCtorThrows>()
                              .UseStartup<object>(context => throw new InvalidOperationException("This doesn't run"))
                              .Configure(app =>
                              {
                                  throw new InvalidOperationException("This doesn't run");
                              })
                              .Configure(app =>
                              {
                                  app.Run(context =>
                                  {
                                      return context.Response.WriteAsync("This wins");
                                  });
                              })
                              .Build();
            using (host)
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "This wins");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task UseStartupFactoryWorks(IWebHostBuilder builder)
        {
            void ConfigureServices(IServiceCollection services) { }
            void Configure(IApplicationBuilder app)
            {
                app.Run(context => context.Response.WriteAsync("UseStartupFactoryWorks"));
            }

            var server = new TestServer();
            var host = builder.UseServer(server)
                              .UseStartup(context => new DelegatingStartup(ConfigureServices, Configure))
                              .Build();
            using (host)
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "UseStartupFactoryWorks");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task StartupCtorThrows_Fallback(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var host = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
            using (host)
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "Exception from constructor");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task StartupCtorThrows_TypeLoadException(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var host = builder.UseServer(server).UseStartup<StartupThrowTypeLoadException>().Build();
            using (host)
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "Message from the LoaderException</div>");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task IHostApplicationLifetimeRegisteredEvenWhenStartupCtorThrows_Fallback(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var host = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
            using (host)
            {
                await host.StartAsync();
                var services = host.Services.GetServices<IHostApplicationLifetime>();
                Assert.NotNull(services);
                Assert.NotEmpty(services);

                await AssertResponseContains(server.RequestDelegate, "Exception from constructor");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task StartupConfigureServicesThrows_Fallback(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var host = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
            using (host)
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "Exception from ConfigureServices");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task StartupConfigureThrows_Fallback(IWebHostBuilder builder)
        {
            var server = new TestServer();
            var host = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
            using (host)
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "Exception from Configure");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void DefaultCreatesLoggerFactory(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (var host = hostBuilder.Build())
            {
                Assert.NotNull(host.Services.GetService<ILoggerFactory>());
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void ConfigureDefaultServiceProvider(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .UseServer(new TestServer())
                .ConfigureServices(s =>
                {
                    s.AddTransient<ServiceD>();
                    s.AddScoped<ServiceC>();
                })
                .Configure(app =>
                {
                    app.ApplicationServices.GetRequiredService<ServiceC>();
                })
                .UseDefaultServiceProvider(options =>
                {
                    options.ValidateScopes = true;
                });

            using var host = hostBuilder.Build();
            Assert.Throws<InvalidOperationException>(() => host.Start());
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void ConfigureDefaultServiceProviderWithContext(IWebHostBuilder builder)
        {
            var configurationCallbackCalled = false;
            var hostBuilder = builder
                .UseServer(new TestServer())
                .ConfigureServices(s =>
                {
                    s.AddTransient<ServiceD>();
                    s.AddScoped<ServiceC>();
                })
                .Configure(app =>
                {
                    app.ApplicationServices.GetRequiredService<ServiceC>();
                })
                .UseDefaultServiceProvider((context, options) =>
                {
                    Assert.NotNull(context.HostingEnvironment);
                    Assert.NotNull(context.Configuration);
                    configurationCallbackCalled = true;
                    options.ValidateScopes = true;
                });

            using var host = hostBuilder.Build();
            Assert.Throws<InvalidOperationException>(() => host.Start());
            Assert.True(configurationCallbackCalled);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void MultipleConfigureLoggingInvokedInOrder(IWebHostBuilder builder)
        {
            var callCount = 0; //Verify ordering
            var hostBuilder = builder
                .ConfigureLogging(loggerFactory =>
                {
                    Assert.Equal(0, callCount++);
                })
                .ConfigureLogging(loggerFactory =>
                {
                    Assert.Equal(1, callCount++);
                })
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (hostBuilder.Build())
            {
                Assert.Equal(2, callCount);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task MultipleStartupAssembliesSpecifiedOnlyAddAssemblyOnce(IWebHostBuilder builder)
        {
            var provider = new TestLoggerProvider();
            var assemblyName = "RandomName";
            var data = new Dictionary<string, string>
            {
                { WebHostDefaults.ApplicationKey,  assemblyName },
                { WebHostDefaults.HostingStartupAssembliesKey, assemblyName }
            };
            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();

            builder = builder
                 .UseConfiguration(config)
                 .ConfigureLogging((_, factory) =>
                 {
                     factory.AddProvider(provider);
                 })
                .UseServer(new TestServer());

            // Verify that there was only one exception throw rather than two.
            using (var host = builder.Build())
            {
                await host.StartAsync();
                var context = provider.Sink.Writes.Where(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException);
                Assert.NotNull(context);
                Assert.Single(context);
            }
        }

        [Fact]
        public void HostingContextContainsAppConfigurationDuringConfigureLogging()
        {
            var hostBuilder = CreateWebHostBuilder()
                 .ConfigureAppConfiguration((context, configBuilder) =>
                    configBuilder.AddInMemoryCollection(
                        new KeyValuePair<string, string>[]
                        {
                            new KeyValuePair<string, string>("key1", "value1")
                        }))
                 .ConfigureLogging((context, factory) =>
                 {
                     Assert.Equal("value1", context.Configuration["key1"]);
                 })
                 .UseServer(new TestServer())
                 .UseStartup<StartupNoServices>();

            using (hostBuilder.Build()) { }
        }

        [Fact]
        public void HostingContextContainsAppConfigurationDuringConfigureServices()
        {
            var hostBuilder = CreateWebHostBuilder()
                 .ConfigureAppConfiguration((context, configBuilder) =>
                    configBuilder.AddInMemoryCollection(
                        new KeyValuePair<string, string>[]
                        {
                            new KeyValuePair<string, string>("key1", "value1")
                        }))
                 .ConfigureServices((context, factory) =>
                 {
                     Assert.Equal("value1", context.Configuration["key1"]);
                 })
                 .UseServer(new TestServer())
                 .UseStartup<StartupNoServices>();

            using (hostBuilder.Build()) { }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void ThereIsAlwaysConfiguration(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (var host = hostBuilder.Build())
            {
                Assert.NotNull(host.Services.GetService<IConfiguration>());
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void ConfigureConfigurationSettingsPropagated(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .UseSetting("key1", "value1")
                .ConfigureAppConfiguration((context, configBuilder) =>
                {
                    var config = configBuilder.Build();
                    Assert.Equal("value1", config["key1"]);
                })
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (hostBuilder.Build()) { }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void CanConfigureConfigurationAndRetrieveFromDI(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .ConfigureAppConfiguration((_, configBuilder) =>
                {
                    configBuilder
                        .AddInMemoryCollection(
                            new KeyValuePair<string, string>[]
                            {
                                new KeyValuePair<string, string>("key1", "value1")
                            })
                        .AddEnvironmentVariables();
                })
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (var host = hostBuilder.Build())
            {
                var config = host.Services.GetService<IConfiguration>();
                Assert.NotNull(config);
                Assert.Equal("value1", config["key1"]);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DoNotCaptureStartupErrorsByDefault(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .UseServer(new TestServer())
                .UseStartup<StartupBoom>();

            var exception = Assert.Throws<InvalidOperationException>(() => hostBuilder.Build());
            Assert.Equal("A public method named 'ConfigureProduction' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupBoom' type.", exception.Message);
        }

        [Fact]
        public void ServiceProviderDisposedOnBuildException()
        {
            var service = new DisposableService();
            var hostBuilder = new WebHostBuilder()
                .UseServer(new TestServer())
                .ConfigureServices(services =>
                {
                    // Added as a factory since instances are never disposed by the container
                    services.AddSingleton(sp => service);
                })
                .UseStartup<StartupWithResolvedDisposableThatThrows>();

            Assert.Throws<InvalidOperationException>(() => hostBuilder.Build());
            Assert.True(service.Disposed);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void CaptureStartupErrorsHonored(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .CaptureStartupErrors(false)
                .UseServer(new TestServer())
                .UseStartup<StartupBoom>();

            var exception = Assert.Throws<InvalidOperationException>(() => hostBuilder.Build());
            Assert.Equal("A public method named 'ConfigureProduction' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupBoom' type.", exception.Message);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void ConfigureServices_CanBeCalledMultipleTimes(IWebHostBuilder builder)
        {
            var callCount = 0; // Verify ordering
            var hostBuilder = builder
                .UseServer(new TestServer())
                .ConfigureServices(services =>
                {
                    Assert.Equal(0, callCount++);
                    services.AddTransient<ServiceA>();
                })
                .ConfigureServices(services =>
                {
                    Assert.Equal(1, callCount++);
                    services.AddTransient<ServiceB>();
                })
                .Configure(app => { });

            using (var host = hostBuilder.Build())
            {
                Assert.Equal(2, callCount);

                Assert.NotNull(host.Services.GetRequiredService<ServiceA>());
                Assert.NotNull(host.Services.GetRequiredService<ServiceB>());
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void CodeBasedSettingsCodeBasedOverride(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvA")
                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvB")
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (var host = hostBuilder.Build())
            {
                var options = CreateWebHostOptions(host.Services.GetRequiredService<IConfiguration>());
                Assert.Equal("EnvB", options.Environment);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void CodeBasedSettingsConfigBasedOverride(IWebHostBuilder builder)
        {
            var settings = new Dictionary<string, string>
            {
                { WebHostDefaults.EnvironmentKey, "EnvB" }
            };

            var config = new ConfigurationBuilder()
                .AddInMemoryCollection(settings)
                .Build();

            var hostBuilder = builder
                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvA")
                .UseConfiguration(config)
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (var host = hostBuilder.Build())
            {
                var options = CreateWebHostOptions(host.Services.GetRequiredService<IConfiguration>());
                Assert.Equal("EnvB", options.Environment);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void ConfigBasedSettingsCodeBasedOverride(IWebHostBuilder builder)
        {
            var settings = new Dictionary<string, string>
            {
                { WebHostDefaults.EnvironmentKey, "EnvA" }
            };

            var config = new ConfigurationBuilder()
                .AddInMemoryCollection(settings)
                .Build();

            var hostBuilder = builder
                .UseConfiguration(config)
                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvB")
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (var host = hostBuilder.Build())
            {
                var options = CreateWebHostOptions(host.Services.GetRequiredService<IConfiguration>());
                Assert.Equal("EnvB", options.Environment);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void ConfigBasedSettingsConfigBasedOverride(IWebHostBuilder builder)
        {
            var settings = new Dictionary<string, string>
            {
                { WebHostDefaults.EnvironmentKey, "EnvA" }
            };

            var config = new ConfigurationBuilder()
                .AddInMemoryCollection(settings)
                .Build();

            var overrideSettings = new Dictionary<string, string>
            {
                { WebHostDefaults.EnvironmentKey, "EnvB" }
            };

            var overrideConfig = new ConfigurationBuilder()
                .AddInMemoryCollection(overrideSettings)
                .Build();

            var hostBuilder = builder
                .UseConfiguration(config)
                .UseConfiguration(overrideConfig)
                .UseServer(new TestServer())
                .UseStartup<StartupNoServices>();

            using (var host = hostBuilder.Build())
            {
                var options = CreateWebHostOptions(host.Services.GetRequiredService<IConfiguration>());
                Assert.Equal("EnvB", options.Environment);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void UseEnvironmentIsNotOverriden(IWebHostBuilder builder)
        {
            var vals = new Dictionary<string, string>
            {
                { "ENV", "Dev" },
            };
            var configBuilder = new ConfigurationBuilder()
                .AddInMemoryCollection(vals);
            var config = configBuilder.Build();

            var expected = "MY_TEST_ENVIRONMENT";


            using (var host = builder
                .UseConfiguration(config)
                .UseEnvironment(expected)
                .UseServer(new TestServer())
                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
                .Build())
            {
                Assert.Equal(expected, host.Services.GetService<IHostEnvironment>().EnvironmentName);
                Assert.Equal(expected, host.Services.GetService<IWebHostEnvironment>().EnvironmentName);
#pragma warning disable CS0618 // Type or member is obsolete
                Assert.Equal(expected, host.Services.GetService<AspNetCore.Hosting.IHostingEnvironment>().EnvironmentName);
                Assert.Equal(expected, host.Services.GetService<Extensions.Hosting.IHostingEnvironment>().EnvironmentName);
#pragma warning restore CS0618 // Type or member is obsolete
            }
        }
        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void BuildAndDispose(IWebHostBuilder builder)
        {
            var vals = new Dictionary<string, string>
            {
                { "ENV", "Dev" },
            };
            var configBuilder = new ConfigurationBuilder()
                .AddInMemoryCollection(vals);
            var config = configBuilder.Build();

            var expected = "MY_TEST_ENVIRONMENT";
            using (var host = builder
                .UseConfiguration(config)
                .UseEnvironment(expected)
                .UseServer(new TestServer())
                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
                .Build()) { }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void UseBasePathConfiguresBasePath(IWebHostBuilder builder)
        {
            var vals = new Dictionary<string, string>
            {
                { "ENV", "Dev" },
            };
            var configBuilder = new ConfigurationBuilder()
                .AddInMemoryCollection(vals);
            var config = configBuilder.Build();

            using (var host = builder
                .UseConfiguration(config)
                .UseContentRoot("/")
                .UseServer(new TestServer())
                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
                .Build())
            {
                Assert.Equal("/", host.Services.GetService<IHostEnvironment>().ContentRootPath);
                Assert.Equal("/", host.Services.GetService<IWebHostEnvironment>().ContentRootPath);
#pragma warning disable CS0618 // Type or member is obsolete
                Assert.Equal("/", host.Services.GetService<AspNetCore.Hosting.IHostingEnvironment>().ContentRootPath);
                Assert.Equal("/", host.Services.GetService<Extensions.Hosting.IHostingEnvironment>().ContentRootPath);
#pragma warning restore CS0618 // Type or member is obsolete
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void RelativeContentRootIsResolved(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseContentRoot("testroot")
                .UseServer(new TestServer())
                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
                .Build())
            {
                var basePath = host.Services.GetRequiredService<IHostEnvironment>().ContentRootPath;
#pragma warning disable CS0618 // Type or member is obsolete
                var basePath2 = host.Services.GetService<AspNetCore.Hosting.IHostingEnvironment>().ContentRootPath;
#pragma warning restore CS0618 // Type or member is obsolete

                Assert.True(Path.IsPathRooted(basePath));
                Assert.EndsWith(Path.DirectorySeparatorChar + "testroot", basePath);

                Assert.True(Path.IsPathRooted(basePath2));
                Assert.EndsWith(Path.DirectorySeparatorChar + "testroot", basePath2);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DefaultContentRootIsApplicationBasePath(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseServer(new TestServer())
                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
                .Build())
            {
                var appBase = AppContext.BaseDirectory;
                Assert.Equal(appBase, host.Services.GetService<IHostEnvironment>().ContentRootPath);
#pragma warning disable CS0618 // Type or member is obsolete
                Assert.Equal(appBase, host.Services.GetService<AspNetCore.Hosting.IHostingEnvironment>().ContentRootPath);
#pragma warning restore CS0618 // Type or member is obsolete
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DefaultWebHostBuilderWithNoStartupThrows(IWebHostBuilder builder)
        {
            builder.UseServer(new TestServer());

            var ex = Assert.Throws<InvalidOperationException>(() =>
            {
                using var host = builder.Build();
                host.Start();
            });

            Assert.Contains("No application configured.", ex.Message);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DefaultApplicationNameWithUseStartupOfString(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseServer(new TestServer())
                .UseStartup(typeof(Startup).Assembly.GetName().Name)
                .Build())
            {
                var hostingEnv = host.Services.GetService<IHostEnvironment>();
#pragma warning disable CS0618 // Type or member is obsolete
                var hostingEnv2 = host.Services.GetService<AspNetCore.Hosting.IHostingEnvironment>();
#pragma warning restore CS0618 // Type or member is obsolete
                Assert.Equal(typeof(Startup).Assembly.GetName().Name, hostingEnv.ApplicationName);
                Assert.Equal(typeof(Startup).Assembly.GetName().Name, hostingEnv2.ApplicationName);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DefaultApplicationNameWithUseStartupOfT(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseServer(new TestServer())
                .UseStartup<StartupNoServicesNoInterface>()
                .Build())
            {
                var hostingEnv = host.Services.GetService<IHostEnvironment>();
#pragma warning disable CS0618 // Type or member is obsolete
                var hostingEnv2 = host.Services.GetService<AspNetCore.Hosting.IHostingEnvironment>();
#pragma warning restore CS0618 // Type or member is obsolete
                Assert.Equal(typeof(StartupNoServicesNoInterface).Assembly.GetName().Name, hostingEnv.ApplicationName);
                Assert.Equal(typeof(StartupNoServicesNoInterface).Assembly.GetName().Name, hostingEnv2.ApplicationName);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DefaultApplicationNameWithUseStartupOfType(IWebHostBuilder builder)
        {
            using var host = builder
                .UseServer(new TestServer())
                .UseStartup(typeof(StartupNoServicesNoInterface))
                .Build();

            var hostingEnv = host.Services.GetService<IHostEnvironment>();
            Assert.Equal(typeof(StartupNoServicesNoInterface).Assembly.GetName().Name, hostingEnv.ApplicationName);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DefaultApplicationNameWithConfigure(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseServer(new TestServer())
                .Configure(app => { })
                .Build())
            {
                var hostingEnv = host.Services.GetService<IHostEnvironment>();

                // Should be the assembly containing this test, because that's where the delegate comes from
                Assert.Equal(typeof(WebHostBuilderTests).Assembly.GetName().Name, hostingEnv.ApplicationName);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void DefaultApplicationNameWithUseStartupFactory(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseServer(new TestServer())
                .UseStartup(context => new DelegatingStartup(s => { }, app => { }))
                .Build())
            {
                var hostingEnv = host.Services.GetService<IHostEnvironment>();

                // Should be the assembly containing this test, because that's where the delegate comes from
                Assert.Equal(typeof(WebHostBuilderTests).Assembly.GetName().Name, hostingEnv.ApplicationName);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void Configure_SupportsNonStaticMethodDelegate(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseServer(new TestServer())
                .Configure(app => { })
                .Build())
            {
                var hostingEnv = host.Services.GetService<IHostEnvironment>();
                Assert.Equal("Microsoft.AspNetCore.Hosting.Tests", hostingEnv.ApplicationName);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public void Configure_SupportsStaticMethodDelegate(IWebHostBuilder builder)
        {
            using (var host = builder
                .UseServer(new TestServer())
                .Configure(StaticConfigureMethod)
                .Build())
            {
                var hostingEnv = host.Services.GetService<IHostEnvironment>();
                Assert.Equal("Microsoft.AspNetCore.Hosting.Tests", hostingEnv.ApplicationName);
            }
        }

        [Fact]
        public void Build_DoesNotAllowBuildingMuiltipleTimes()
        {
            var builder = CreateWebHostBuilder();
            var server = new TestServer();
            using (builder.UseServer(server)
                .UseStartup<StartupNoServices>()
                .Build())
            {
                var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
                Assert.Equal("WebHostBuilder allows creation only of a single instance of WebHost", ex.Message);
            }
        }

        [Fact]
        public async Task UseStartupImplementingIStartupWorks()
        {
            void Configure(IApplicationBuilder app)
            {
                app.Run(context => context.Response.WriteAsync("Configure"));
            }

            IServiceProvider ConfigureServices(IServiceCollection services) => services.BuildServiceProvider();

            var builder = CreateWebHostBuilder();
            var server = new TestServer();
            using (var host = builder.UseServer(server)
                .UseStartup(context => new DelegatingStartupWithIStartup(ConfigureServices, Configure))
                .Build())
            {
                await host.StartAsync();
                await AssertResponseContains(server.RequestDelegate, "Configure");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_DoesNotOverrideILoggerFactorySetByConfigureServices(IWebHostBuilder builder)
        {
            var factory = new DisposableLoggerFactory();
            var server = new TestServer();

            using (var host = builder.UseServer(server)
                .ConfigureServices(collection => collection.AddSingleton<ILoggerFactory>(factory))
                .UseStartup<StartupWithILoggerFactory>()
                .Build())
            {
                var factoryFromHost = host.Services.GetService<ILoggerFactory>();
                Assert.Equal(factory, factoryFromHost);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_RunsHostingStartupAssembliesIfSpecified(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
                .Configure(app => { })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                Assert.Equal("1", builder.GetSetting("testhostingstartup1"));
                Assert.Equal("1", builder.GetSetting("testhostingstartup1_calls"));
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_RunsDeduplicatedHostingStartupAssembliesIfSpecified(IWebHostBuilder builder)
        {
            var fullName = typeof(TestStartupAssembly1.TestHostingStartup1).Assembly.FullName;
            var name = typeof(TestStartupAssembly1.TestHostingStartup1).Assembly.GetName().Name;

            builder = builder
                .CaptureStartupErrors(false)
                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, fullName + ";" + name)
                .Configure(app => { })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                Assert.Equal("1", builder.GetSetting("testhostingstartup1"));
                Assert.Equal("1", builder.GetSetting("testhostingstartup1_calls"));
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_RunsHostingStartupRunsPrimaryAssemblyFirst(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
                .Configure(app => { })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                Assert.Equal("0", builder.GetSetting("testhostingstartup"));
                Assert.Equal("1", builder.GetSetting("testhostingstartup1"));
                Assert.Equal("01", builder.GetSetting("testhostingstartup_chain"));
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_RunsHostingStartupAssembliesBeforeApplication(IWebHostBuilder builder)
        {
            var startupAssemblyName = typeof(WebHostBuilderTests).GetTypeInfo().Assembly.GetName().Name;

            builder = builder
                .CaptureStartupErrors(false)
                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName)
                .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
                .UseStartup<StartupVerifyServiceA>()
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                host.Start();
                var startup = host.Services.GetRequiredService<StartupVerifyServiceA>();
                Assert.NotNull(startup.ServiceADescriptor);
                Assert.NotNull(startup.ServiceA);
            }
        }

        [Fact]
        public async Task ExternalContainerInstanceCanBeUsedForEverything()
        {
            var disposables = new List<DisposableService>();

            var containerFactory = new ExternalContainerFactory(services =>
            {
                services.AddSingleton(sp =>
                {
                    var service = new DisposableService();
                    disposables.Add(service);
                    return service;
                });
            });

            var host = CreateWebHostBuilder()
                .UseStartup<StartupWithExternalServices>()
                .UseServer(new TestServer())
                .ConfigureServices(services =>
                {
                    services.AddSingleton<IServiceProviderFactory<IServiceCollection>>(containerFactory);
                })
                .Build();

            using (host)
            {
                await host.StartAsync();
            }

            // We should create the hosting service provider and the application service provider
            Assert.Equal(2, containerFactory.ServiceProviders.Count);
            Assert.Equal(2, disposables.Count);

            Assert.NotEqual(disposables[0], disposables[1]);
            Assert.True(disposables[0].Disposed);
            Assert.True(disposables[1].Disposed);
        }

        [Fact]
        public void GenericWebHostThrowsWithIStartup()
        {
            var builder = new GenericWebHostBuilderWrapper(new HostBuilder())
                .UseStartup<StartupNoServices>();

            var exception = Assert.Throws<NotSupportedException>(() => builder.Build());
            Assert.Equal("Microsoft.AspNetCore.Hosting.IStartup isn't supported", exception.Message);
        }

        [Fact]
        public void GenericWebHostThrowsOnBuild()
        {
            var exception = Assert.Throws<NotSupportedException>(() =>
            {
                var hostBuilder = new HostBuilder()
                       .ConfigureWebHost(builder =>
                       {
                           builder.UseStartup<StartupNoServices>();
                           builder.Build();
                       });
            });

            Assert.Equal("Building this implementation of IWebHostBuilder is not supported.", exception.Message);
        }

        [Fact]
        public void GenericWebHostDoesNotSupportBuildingInConfigureServices()
        {
            var hostBuilder = new HostBuilder()
                   .ConfigureWebHost(builder =>
                   {
                       builder.UseStartup<StartupWithBuiltConfigureServices>();
                   });
            var exception = Assert.Throws<NotSupportedException>(() =>
            {
                hostBuilder.Build();
            });

            Assert.Equal($"ConfigureServices returning an {typeof(IServiceProvider)} isn't supported.", exception.Message);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_HostingStartupAssemblyCanBeExcluded(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
                .UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
                .Configure(app => { })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                Assert.Null(builder.GetSetting("testhostingstartup1"));
                Assert.Equal("0", builder.GetSetting("testhostingstartup_chain"));
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_ConfigureLoggingInHostingStartupWorks(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .Configure(app =>
                {
                    var loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
                    var logger = loggerFactory.CreateLogger(nameof(WebHostBuilderTests));
                    logger.LogInformation("From startup");
                })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                host.Start();
                var sink = host.Services.GetRequiredService<ITestSink>();
                Assert.Contains(sink.Writes, w => w.State.ToString() == "From startup");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_ConfigureAppConfigurationInHostingStartupWorks(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .Configure(app => { })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                var configuration = host.Services.GetRequiredService<IConfiguration>();
                Assert.Equal("value", configuration["testhostingstartup:config"]);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_AppConfigAvailableEverywhere(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .ConfigureAppConfiguration((context, configurationBuilder) =>
                {
                    configurationBuilder.AddInMemoryCollection(
                        new[]
                        {
                            new KeyValuePair<string,string>("appconfig", "appvalue")
                        });
                })
                .ConfigureLogging((context, logging) =>
                {
                    Assert.Equal("appvalue", context.Configuration["appconfig"]);
                })
                .ConfigureServices((context, services) =>
                {
                    Assert.Equal("appvalue", context.Configuration["appconfig"]);
                })
                .UseDefaultServiceProvider((context, services) =>
                {
                    Assert.Equal("appvalue", context.Configuration["appconfig"]);
                })
                .UseStartup<StartupCheckConfig>()
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                var configuration = host.Services.GetRequiredService<IConfiguration>();
                Assert.Equal("appvalue", configuration["appconfig"]);
            }
        }

        public class StartupCheckConfig
        {
            public StartupCheckConfig(IConfiguration config)
            {
                Assert.Equal("value", config["testhostingstartup:config"]);
            }

            public void Configure(IApplicationBuilder app)
            {

            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_DoesRunHostingStartupFromPrimaryAssemblyEvenIfNotSpecified(IWebHostBuilder builder)
        {
            builder = builder
                .Configure(app => { })
                .UseServer(new TestServer());

            using (builder.Build())
            {
                Assert.Equal("0", builder.GetSetting("testhostingstartup"));
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_HostingStartupFromPrimaryAssemblyCanBeDisabled(IWebHostBuilder builder)
        {
            builder = builder
                .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")
                .Configure(app => { })
                .UseServer(new TestServer());

            using (builder.Build())
            {
                Assert.Null(builder.GetSetting("testhostingstartup"));
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void Build_DoesntThrowIfUnloadableAssemblyNameInHostingStartupAssemblies(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName")
                .Configure(app => { })
                .UseServer(new TestServer());

            using (builder.Build())
            {
                Assert.Equal("0", builder.GetSetting("testhostingstartup"));
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue(IWebHostBuilder builder)
        {
            var provider = new TestLoggerProvider();
            builder = builder
                .ConfigureLogging((_, factory) =>
                {
                    factory.AddProvider(provider);
                })
                .CaptureStartupErrors(true)
                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName")
                .Configure(app => { })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                await host.StartAsync();
                var context = provider.Sink.Writes.FirstOrDefault(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException);
                Assert.NotNull(context);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsTrue(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(true)
                .Configure(app =>
                {
                    throw new InvalidOperationException("Startup exception");
                })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                host.Start();
                var sink = host.Services.GetRequiredService<ITestSink>();
                Assert.Contains(sink.Writes, w => w.Exception?.Message == "Startup exception");
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsFalse(IWebHostBuilder builder)
        {
            ITestSink testSink = null;

            builder = builder
                .CaptureStartupErrors(false)
                .Configure(app =>
                {
                    testSink = app.ApplicationServices.GetRequiredService<ITestSink>();

                    throw new InvalidOperationException("Startup exception");
                })
                .UseServer(new TestServer());

            using var host = builder.Build();
            Assert.Throws<InvalidOperationException>(() => host.Start());

            Assert.NotNull(testSink);
            Assert.Contains(testSink.Writes, w => w.Exception?.Message == "Startup exception");
        }

        [Fact]
        public void HostingStartupTypeCtorThrowsIfNull()
        {
            Assert.Throws<ArgumentNullException>(() => new HostingStartupAttribute(null));
        }

        [Fact]
        public void HostingStartupTypeCtorThrowsIfNotIHosting()
        {
            Assert.Throws<ArgumentException>(() => new HostingStartupAttribute(typeof(WebHostTests)));
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void UseShutdownTimeoutConfiguresShutdownTimeout(IWebHostBuilder builder)
        {
            builder = builder
                .CaptureStartupErrors(false)
                .UseShutdownTimeout(TimeSpan.FromSeconds(102))
                .Configure(app => { })
                .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                var options = CreateWebHostOptions(host.Services.GetRequiredService<IConfiguration>());
                Assert.Equal(TimeSpan.FromSeconds(102), options.ShutdownTimeout);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public async Task StartupFiltersDoNotRunIfNotApplicationConfigured(IWebHostBuilder builder)
        {
            var hostBuilder = builder
                .ConfigureServices(services =>
                {
                    services.AddSingleton<IStartupFilter, MyStartupFilter>();
                })
                .UseServer(new TestServer());

            var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
            {
                using var host = hostBuilder.Build();

                var filter = (MyStartupFilter)host.Services.GetServices<IStartupFilter>().FirstOrDefault(s => s is MyStartupFilter);
                Assert.NotNull(filter);
                try
                {
                    await host.StartAsync();
                }
                finally
                {
                    Assert.False(filter.Executed);
                }
            });

            Assert.Contains("No application configured.", exception.Message);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuildersWithConfig))]
        public void UseConfigurationWithSectionAddsSubKeys(IWebHostBuilder builder)
        {
            var config = new ConfigurationBuilder()
                .AddInMemoryCollection(new[]
                {
                    new KeyValuePair<string, string>("key", "value"),
                    new KeyValuePair<string, string>("nested:key", "nestedvalue"),
                }).Build();
            var section = config.GetSection("nested");

            builder = builder
                .CaptureStartupErrors(false)
                .Configure(app => { })
                .UseConfiguration(section)
                .UseServer(new TestServer());

            Assert.Equal("nestedvalue", builder.GetSetting("key"));

            using var host = builder.Build();
            var appConfig = host.Services.GetRequiredService<IConfiguration>();
            Assert.Equal("nestedvalue", appConfig["key"]);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public async Task ThrowingFromHostedServiceFailsStartAsync(IWebHostBuilder builder)
        {
            builder.Configure(app => { })
                   .ConfigureServices(services =>
                   {
                       services.AddHostedService<ThrowingHostedService>();
                   })
                   .UseServer(new TestServer());

            using (var host = builder.Build())
            {
                var startEx = await Assert.ThrowsAsync<InvalidOperationException>(() => host.StartAsync());
                Assert.Equal("Hosted Service throws in StartAsync", startEx.Message);
                var stopEx = await Assert.ThrowsAsync<AggregateException>(() => host.StopAsync());
                Assert.Single(stopEx.InnerExceptions);
                Assert.Equal("Hosted Service throws in StopAsync", stopEx.InnerExceptions[0].Message);
            }
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public async Task ThrowingFromHostedServiceStopsOtherHostedServicesFromRunningStartAsync(IWebHostBuilder builder)
        {
            builder.Configure(app => { })
                   .ConfigureServices(services =>
                   {
                       services.AddHostedService<ThrowingHostedService>();
                       services.AddHostedService<NonThrowingHostedService>();
                   })
                   .UseServer(new TestServer());

            using var host = builder.Build();
            var service = host.Services.GetServices<IHostedService>().OfType<NonThrowingHostedService>().First();
            var startEx = await Assert.ThrowsAsync<InvalidOperationException>(() => host.StartAsync());
            Assert.Equal("Hosted Service throws in StartAsync", startEx.Message);

            var stopEx = await Assert.ThrowsAsync<AggregateException>(() => host.StopAsync());
            Assert.Single(stopEx.InnerExceptions);
            Assert.Equal("Hosted Service throws in StopAsync", stopEx.InnerExceptions[0].Message);

            // This service is never constructed
            Assert.False(service.StartCalled);
            Assert.True(service.StopCalled);
        }

        [Theory]
        [MemberData(nameof(DefaultWebHostBuilders))]
        public async Task HostedServicesStartedBeforeServer(IWebHostBuilder builder)
        {
            builder.Configure(app => { })
                .ConfigureServices(services =>
                {
                    services.AddSingleton<StartOrder>();
                    services.AddHostedService<MustBeStartedFirst>();
                    services.AddSingleton<IServer, ServerMustBeStartedSecond>();
                });

            using var host = builder.Build();
            await host.StartAsync();
            var ordering = host.Services.GetRequiredService<StartOrder>();
            Assert.Equal(2, ordering.Order);
            await host.StopAsync();
        }

        private WebHostOptions CreateWebHostOptions(IConfiguration configuration, string applicationNameFallback = null)
        {
            return new WebHostOptions(
                configuration,
                applicationNameFallback: applicationNameFallback);
        }

        private class StartOrder
        {
            public int Order { get; set; }
        }

        private class MustBeStartedFirst : IHostedService
        {
            public MustBeStartedFirst(StartOrder ordering)
            {
                Ordering = ordering;
            }

            public StartOrder Ordering { get; }

            public Task StartAsync(CancellationToken cancellationToken)
            {
                Assert.Equal(0, Ordering.Order);
                Ordering.Order++;
                return Task.CompletedTask;
            }

            public Task StopAsync(CancellationToken cancellationToken)
            {
                return Task.CompletedTask;
            }
        }

        private class ServerMustBeStartedSecond : IServer
        {
            public ServerMustBeStartedSecond(StartOrder ordering)
            {
                Ordering = ordering;
            }

            public StartOrder Ordering { get; }

            public IFeatureCollection Features { get; } = new FeatureCollection();

            public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
            {
                Assert.Equal(1, Ordering.Order);
                Ordering.Order++;
                return Task.CompletedTask;
            }

            public Task StopAsync(CancellationToken cancellationToken)
            {
                return Task.CompletedTask;
            }

            public void Dispose()
            {
            }
        }

        private static void StaticConfigureMethod(IApplicationBuilder app) { }

        private IWebHostBuilder CreateWebHostBuilder()
        {
            var vals = new Dictionary<string, string>
            {
                { "DetailedErrors", "true" },
                { "captureStartupErrors", "true" }
            };
            var builder = new ConfigurationBuilder()
                .AddInMemoryCollection(vals);
            var config = builder.Build();

            return new WebHostBuilder().UseConfiguration(config);
        }

        public static TheoryData<IWebHostBuilder> DefaultWebHostBuilders => new TheoryData<IWebHostBuilder>
        {
            new WebHostBuilder(),
            new GenericWebHostBuilderWrapper(new HostBuilder())
        };

        public static TheoryData<IWebHostBuilder> DefaultWebHostBuildersWithConfig
        {
            get
            {
                var vals = new Dictionary<string, string>
                {
                    { "DetailedErrors", "true" },
                    { "captureStartupErrors", "true" }
                };

                var builder = new ConfigurationBuilder()
                    .AddInMemoryCollection(vals);
                var config = builder.Build();

                return new TheoryData<IWebHostBuilder> {
                    new WebHostBuilder().UseConfiguration(config),
                    new GenericWebHostBuilderWrapper(new HostBuilder()).UseConfiguration(config)
                };
            }
        }

        private async Task AssertResponseContains(RequestDelegate app, string expectedText)
        {
            var httpContext = new DefaultHttpContext();
            httpContext.Response.Body = new MemoryStream();
            await app(httpContext);
            httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
            var bodyText = new StreamReader(httpContext.Response.Body).ReadToEnd();
            Assert.Contains(expectedText, bodyText);
        }

        private class ThrowingHostedService : IHostedService
        {
            public Task StartAsync(CancellationToken cancellationToken)
            {
                throw new InvalidOperationException("Hosted Service throws in StartAsync");
            }

            public Task StopAsync(CancellationToken cancellationToken)
            {
                throw new NotImplementedException("Hosted Service throws in StopAsync");
            }
        }

        private class NonThrowingHostedService : IHostedService
        {
            public bool StartCalled { get; set; }
            public bool StopCalled { get; set; }

            public Task StartAsync(CancellationToken cancellationToken)
            {
                StartCalled = true;
                return Task.CompletedTask;
            }

            public Task StopAsync(CancellationToken cancellationToken)
            {
                StopCalled = true;
                return Task.CompletedTask;
            }
        }

        private class MyStartupFilter : IStartupFilter
        {
            public bool Executed { get; set; }

            public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
            {
                Executed = true;
                return next;
            }
        }

        private class TestServer : IServer
        {
            IFeatureCollection IServer.Features { get; } = new FeatureCollection();
            public RequestDelegate RequestDelegate { get; private set; }

            public void Dispose() { }

            public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
            {
                RequestDelegate = async ctx =>
                {
                    var httpContext = application.CreateContext(ctx.Features);
                    try
                    {
                        await application.ProcessRequestAsync(httpContext);
                    }
                    catch (Exception ex)
                    {
                        application.DisposeContext(httpContext, ex);
                        throw;
                    }
                    application.DisposeContext(httpContext, null);
                };

                return Task.CompletedTask;
            }

            public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
        }

        internal class ExternalContainerFactory : IServiceProviderFactory<IServiceCollection>
        {
            private readonly Action<IServiceCollection> _configureServices;
            private readonly List<IServiceProvider> _serviceProviders = new List<IServiceProvider>();

            public List<IServiceProvider> ServiceProviders => _serviceProviders;

            public ExternalContainerFactory(Action<IServiceCollection> configureServices)
            {
                _configureServices = configureServices;
            }

            public IServiceCollection CreateBuilder(IServiceCollection services)
            {
                _configureServices(services);
                return services;
            }

            public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
            {
                var provider = containerBuilder.BuildServiceProvider();
                _serviceProviders.Add(provider);
                return provider;
            }
        }

        internal class StartupWithExternalServices
        {
            public DisposableService DisposableServiceCtor { get; set; }

            public DisposableService DisposableServiceApp { get; set; }

            public StartupWithExternalServices(DisposableService disposable)
            {
                DisposableServiceCtor = disposable;
            }

            public void ConfigureServices(IServiceCollection services) { }

            public void Configure(IApplicationBuilder app, DisposableService disposable)
            {
                DisposableServiceApp = disposable;
            }
        }

        internal class StartupVerifyServiceA
        {
            internal ServiceA ServiceA { get; set; }

            internal ServiceDescriptor ServiceADescriptor { get; set; }

            public void ConfigureServices(IServiceCollection services)
            {
                services.AddSingleton(this);

                ServiceADescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(ServiceA));
            }

            public void Configure(IApplicationBuilder app)
            {
                ServiceA = app.ApplicationServices.GetService<ServiceA>();
            }
        }

        public class DisposableService : IDisposable
        {
            public bool Disposed { get; private set; }

            public void Dispose()
            {
                Disposed = true;
            }
        }

        public class TestHostingStartup : IHostingStartup
        {
            public void Configure(IWebHostBuilder builder)
            {
                var loggerProvider = new TestLoggerProvider();
                builder.UseSetting("testhostingstartup", "0")
                       .UseSetting("testhostingstartup_chain", builder.GetSetting("testhostingstartup_chain") + "0")
                       .ConfigureServices(services =>
                       {
                           // This check is required because MVC still uses the
                           // IWebHostEnvironment instance before the container is baked
#pragma warning disable CS0618 // Type or member is obsolete
                           var heDescriptor = services.SingleOrDefault(s => s.ServiceType == typeof(IHostingEnvironment));
                           Assert.NotNull(heDescriptor);
                           Assert.NotNull(heDescriptor.ImplementationInstance);
#pragma warning restore CS0618 // Type or member is obsolete
                           var wheDescriptor = services.SingleOrDefault(s => s.ServiceType == typeof(IWebHostEnvironment));
                           Assert.NotNull(wheDescriptor);
                           Assert.NotNull(wheDescriptor.ImplementationInstance);
                       })
                       .ConfigureServices(services => services.AddSingleton<ServiceA>())
                       .ConfigureServices(services => services.AddSingleton<ITestSink>(loggerProvider.Sink))
                       .ConfigureLogging((_, lf) => lf.AddProvider(loggerProvider))
                       .ConfigureAppConfiguration((context, configurationBuilder) => configurationBuilder.AddInMemoryCollection(
                           new[]
                           {
                               new KeyValuePair<string,string>("testhostingstartup:config", "value")
                           }));
            }
        }

        private class DelegatingStartupWithIStartup : IStartup
        {
            private readonly Func<IServiceCollection, IServiceProvider> _configureServices;
            private readonly Action<IApplicationBuilder> _configure;

            public DelegatingStartupWithIStartup(Func<IServiceCollection, IServiceProvider> configureServices, Action<IApplicationBuilder> configure)
            {
                _configureServices = configureServices;
                _configure = configure;
            }

            // These are explicitly implemented to verify they don't get called via reflection
            IServiceProvider IStartup.ConfigureServices(IServiceCollection services) => _configureServices(services);
            void IStartup.Configure(IApplicationBuilder app) => _configure(app);
        }

        public class DelegatingStartup
        {
            private readonly Action<IServiceCollection> _configureServices;
            private readonly Action<IApplicationBuilder> _configure;

            public DelegatingStartup(Action<IServiceCollection> configureServices, Action<IApplicationBuilder> configure)
            {
                _configureServices = configureServices;
                _configure = configure;
            }

            public void ConfigureServices(IServiceCollection services) => _configureServices(services);
            public void Configure(IApplicationBuilder app) => _configure(app);
        }

        public class StartupWithResolvedDisposableThatThrows
        {
            public StartupWithResolvedDisposableThatThrows(DisposableService service)
            {

            }

            public void ConfigureServices(IServiceCollection services)
            {
                throw new InvalidOperationException();
            }

            public void Configure(IApplicationBuilder app)
            {

            }
        }

        public class TestLoggerProvider : ILoggerProvider
        {
            public TestSink Sink { get; set; } = new TestSink();

            public ILogger CreateLogger(string categoryName) => new TestLogger(categoryName, Sink, enabled: true);

            public void Dispose() { }
        }

        private class ServiceC
        {
            public ServiceC(ServiceD serviceD) { }
        }

        internal class ServiceD { }

        internal class ServiceA { }

        internal class ServiceB { }

        private class DisposableLoggerFactory : ILoggerFactory
        {
            public void Dispose()
            {
                Disposed = true;
            }

            public bool Disposed { get; set; }

            public ILogger CreateLogger(string categoryName) => NullLogger.Instance;

            public void AddProvider(ILoggerProvider provider) { }
        }
    }
}
