<commit_before>// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyModel;

namespace Microsoft.AspNetCore.Mvc.Testing
{
    /// <summary>
    /// Factory for bootstrapping an application in memory for functional end to end tests.
    /// </summary>
    /// <typeparam name="TEntryPoint">A type in the entry point assembly of the application.
    /// Typically the Startup or Program classes can be used.</typeparam>
    public class WebApplicationFactory<TEntryPoint> : IDisposable where TEntryPoint : class
    {
        private bool _disposed;
        private TestServer _server;
        private Action<IWebHostBuilder> _configuration;
        private IList<HttpClient> _clients = new List<HttpClient>();
        private List<WebApplicationFactory<TEntryPoint>> _derivedFactories =
            new List<WebApplicationFactory<TEntryPoint>>();

        /// <summary>
        /// <para>
        /// Creates an instance of <see cref="WebApplicationFactory{TEntryPoint}"/>. This factory can be used to
        /// create a <see cref="TestServer"/> instance using the MVC application defined by <typeparamref name="TEntryPoint"/>
        /// and one or more <see cref="HttpClient"/> instances used to send <see cref="HttpRequestMessage"/> to the <see cref="TestServer"/>.
        /// The <see cref="WebApplicationFactory{TEntryPoint}"/> will find the entry point class of <typeparamref name="TEntryPoint"/>
        /// assembly and initialize the application by calling <c>IWebHostBuilder CreateWebHostBuilder(string [] args)</c>
        /// on <typeparamref name="TEntryPoint"/>.
        /// </para>
        /// <para>
        /// This constructor will infer the application content root path by searching for a
        /// <see cref="WebApplicationFactoryContentRootAttribute"/> on the assembly containing the functional tests with
        /// a key equal to the <typeparamref name="TEntryPoint"/> assembly <see cref="Assembly.FullName"/>.
        /// In case an attribute with the right key can't be found, <see cref="WebApplicationFactory{TEntryPoint}"/>
        /// will fall back to searching for a solution file (*.sln) and then appending <typeparamref name="TEntryPoint"/> asembly name
        /// to the solution directory. The application root directory will be used to discover views and content files.
        /// </para>
        /// <para>
        /// The application assemblies will be loaded from the dependency context of the assembly containing
        /// <typeparamref name="TEntryPoint" />. This means that project dependencies of the assembly containing
        /// <typeparamref name="TEntryPoint" /> will be loaded as application assemblies.
        /// </para>
        /// </summary>
        public WebApplicationFactory()
        {
            _configuration = ConfigureWebHost;
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="WebApplicationFactory{TEntryPoint}"/> class.
        /// </summary>
        ~WebApplicationFactory()
        {
            Dispose(false);
        }

        /// <summary>
        /// Gets the <see cref="TestServer"/> created by this <see cref="WebApplicationFactory{TEntryPoint}"/>.
        /// </summary>
        public TestServer Server => _server;

        /// <summary>
        /// Gets the <see cref="IReadOnlyList{WebApplicationFactory}"/> of factories created from this factory
        /// by further customizing the <see cref="IWebHostBuilder"/> when calling 
        /// <see cref="WebApplicationFactory{TEntryPoint}.WithWebHostBuilder(Action{IWebHostBuilder})"/>.
        /// </summary>
        public IReadOnlyList<WebApplicationFactory<TEntryPoint>> Factories => _derivedFactories.AsReadOnly();

        /// <summary>
        /// Gets the <see cref="WebApplicationFactoryClientOptions"/> used by <see cref="CreateClient()"/>.
        /// </summary>
        public WebApplicationFactoryClientOptions ClientOptions { get; private set; } = new WebApplicationFactoryClientOptions();

        /// <summary>
        /// Creates a new <see cref="WebApplicationFactory{TEntryPoint}"/> with a <see cref="IWebHostBuilder"/>
        /// that is further customized by <paramref name="configuration"/>.
        /// </summary>
        /// <param name="configuration">
        /// An <see cref="Action{IWebHostBuilder}"/> to configure the <see cref="IWebHostBuilder"/>.
        /// </param>
        /// <returns>A new <see cref="WebApplicationFactory{TEntryPoint}"/>.</returns>
        public WebApplicationFactory<TEntryPoint> WithWebHostBuilder(Action<IWebHostBuilder> configuration) =>
            WithWebHostBuilderCore(configuration);

        internal virtual WebApplicationFactory<TEntryPoint> WithWebHostBuilderCore(Action<IWebHostBuilder> configuration)
        {
            var factory = new DelegatedWebApplicationFactory(
                ClientOptions,
                CreateServer,
                CreateWebHostBuilder,
                GetTestAssemblies,
                ConfigureClient,
                builder =>
                {
                    _configuration(builder);
                    configuration(builder);
                });

            _derivedFactories.Add(factory);

            return factory;
        }

        private void EnsureServer()
        {
            if (_server != null)
            {
                return;
            }

            EnsureDepsFile();


            var builder = CreateWebHostBuilder();
            SetContentRoot(builder);
            _configuration(builder);
            _server = CreateServer(builder);
        }

        private void SetContentRoot(IWebHostBuilder builder)
        {
            var metadataAttributes = GetContentRootMetadataAttributes(
                typeof(TEntryPoint).Assembly.FullName,
                typeof(TEntryPoint).Assembly.GetName().Name);

            string contentRoot = null;
            for (var i = 0; i < metadataAttributes.Length; i++)
            {
                var contentRootAttribute = metadataAttributes[i];
                var contentRootCandidate = Path.Combine(
                    AppContext.BaseDirectory,
                    contentRootAttribute.ContentRootPath);

                var contentRootMarker = Path.Combine(
                    contentRootCandidate,
                    Path.GetFileName(contentRootAttribute.ContentRootTest));

                if (File.Exists(contentRootMarker))
                {
                    contentRoot = contentRootCandidate;
                    break;
                }
            }

            if (contentRoot != null)
            {
                builder.UseContentRoot(contentRoot);
            }
            else
            {
                builder.UseSolutionRelativeContentRoot(typeof(TEntryPoint).Assembly.GetName().Name);
            }
        }

        private WebApplicationFactoryContentRootAttribute[] GetContentRootMetadataAttributes(
            string tEntryPointAssemblyFullName,
            string tEntryPointAssemblyName)
        {
            var testAssembly = GetTestAssemblies();<commit_msg>Add ability to override the testing web content root using environment variables<commit_after>// 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.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyModel;

namespace Microsoft.AspNetCore.Mvc.Testing
{
    /// <summary>
    /// Factory for bootstrapping an application in memory for functional end to end tests.
    /// </summary>
    /// <typeparam name="TEntryPoint">A type in the entry point assembly of the application.
    /// Typically the Startup or Program classes can be used.</typeparam>
    public class WebApplicationFactory<TEntryPoint> : IDisposable where TEntryPoint : class
    {
        private bool _disposed;
        private TestServer _server;
        private Action<IWebHostBuilder> _configuration;
        private IList<HttpClient> _clients = new List<HttpClient>();
        private List<WebApplicationFactory<TEntryPoint>> _derivedFactories =
            new List<WebApplicationFactory<TEntryPoint>>();

        /// <summary>
        /// <para>
        /// Creates an instance of <see cref="WebApplicationFactory{TEntryPoint}"/>. This factory can be used to
        /// create a <see cref="TestServer"/> instance using the MVC application defined by <typeparamref name="TEntryPoint"/>
        /// and one or more <see cref="HttpClient"/> instances used to send <see cref="HttpRequestMessage"/> to the <see cref="TestServer"/>.
        /// The <see cref="WebApplicationFactory{TEntryPoint}"/> will find the entry point class of <typeparamref name="TEntryPoint"/>
        /// assembly and initialize the application by calling <c>IWebHostBuilder CreateWebHostBuilder(string [] args)</c>
        /// on <typeparamref name="TEntryPoint"/>.
        /// </para>
        /// <para>
        /// This constructor will infer the application content root path by searching for a
        /// <see cref="WebApplicationFactoryContentRootAttribute"/> on the assembly containing the functional tests with
        /// a key equal to the <typeparamref name="TEntryPoint"/> assembly <see cref="Assembly.FullName"/>.
        /// In case an attribute with the right key can't be found, <see cref="WebApplicationFactory{TEntryPoint}"/>
        /// will fall back to searching for a solution file (*.sln) and then appending <typeparamref name="TEntryPoint"/> asembly name
        /// to the solution directory. The application root directory will be used to discover views and content files.
        /// </para>
        /// <para>
        /// The application assemblies will be loaded from the dependency context of the assembly containing
        /// <typeparamref name="TEntryPoint" />. This means that project dependencies of the assembly containing
        /// <typeparamref name="TEntryPoint" /> will be loaded as application assemblies.
        /// </para>
        /// </summary>
        public WebApplicationFactory()
        {
            _configuration = ConfigureWebHost;
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="WebApplicationFactory{TEntryPoint}"/> class.
        /// </summary>
        ~WebApplicationFactory()
        {
            Dispose(false);
        }

        /// <summary>
        /// Gets the <see cref="TestServer"/> created by this <see cref="WebApplicationFactory{TEntryPoint}"/>.
        /// </summary>
        public TestServer Server => _server;

        /// <summary>
        /// Gets the <see cref="IReadOnlyList{WebApplicationFactory}"/> of factories created from this factory
        /// by further customizing the <see cref="IWebHostBuilder"/> when calling 
        /// <see cref="WebApplicationFactory{TEntryPoint}.WithWebHostBuilder(Action{IWebHostBuilder})"/>.
        /// </summary>
        public IReadOnlyList<WebApplicationFactory<TEntryPoint>> Factories => _derivedFactories.AsReadOnly();

        /// <summary>
        /// Gets the <see cref="WebApplicationFactoryClientOptions"/> used by <see cref="CreateClient()"/>.
        /// </summary>
        public WebApplicationFactoryClientOptions ClientOptions { get; private set; } = new WebApplicationFactoryClientOptions();

        /// <summary>
        /// Creates a new <see cref="WebApplicationFactory{TEntryPoint}"/> with a <see cref="IWebHostBuilder"/>
        /// that is further customized by <paramref name="configuration"/>.
        /// </summary>
        /// <param name="configuration">
        /// An <see cref="Action{IWebHostBuilder}"/> to configure the <see cref="IWebHostBuilder"/>.
        /// </param>
        /// <returns>A new <see cref="WebApplicationFactory{TEntryPoint}"/>.</returns>
        public WebApplicationFactory<TEntryPoint> WithWebHostBuilder(Action<IWebHostBuilder> configuration) =>
            WithWebHostBuilderCore(configuration);

        internal virtual WebApplicationFactory<TEntryPoint> WithWebHostBuilderCore(Action<IWebHostBuilder> configuration)
        {
            var factory = new DelegatedWebApplicationFactory(
                ClientOptions,
                CreateServer,
                CreateWebHostBuilder,
                GetTestAssemblies,
                ConfigureClient,
                builder =>
                {
                    _configuration(builder);
                    configuration(builder);
                });

            _derivedFactories.Add(factory);

            return factory;
        }

        private void EnsureServer()
        {
            if (_server != null)
            {
                return;
            }

            EnsureDepsFile();


            var builder = CreateWebHostBuilder();
            SetContentRoot(builder);
            _configuration(builder);
            _server = CreateServer(builder);
        }

        private void SetContentRoot(IWebHostBuilder builder)
        {
            if (SetContentRootFromSetting(builder))
            {
                return;
            }

            var metadataAttributes = GetContentRootMetadataAttributes(
                typeof(TEntryPoint).Assembly.FullName,
                typeof(TEntryPoint).Assembly.GetName().Name);

            string contentRoot = null;
            for (var i = 0; i < metadataAttributes.Length; i++)
            {
                var contentRootAttribute = metadataAttributes[i];
                var contentRootCandidate = Path.Combine(
                    AppContext.BaseDirectory,
                    contentRootAttribute.ContentRootPath);

                var contentRootMarker = Path.Combine(
                    contentRootCandidate,
                    Path.GetFileName(contentRootAttribute.ContentRootTest));

                if (File.Exists(contentRootMarker))
                {
                    contentRoot = contentRootCandidate;
                    break;
                }
            }

            if (contentRoot != null)
            {
                builder.UseContentRoot(contentRoot);
            }
            else
            {
                builder.UseSolutionRelativeContentRoot(typeof(TEntryPoint).Assembly.GetName().Name);
            }
        }

        private static bool SetContentRootFromSetting(IWebHostBuilder builder)
        {
            // Attempt to look for TEST_CONTENTROOT_APPNAME in settings. This should result in looking for
            // ASPNETCORE_TEST_CONTENTROOT_APPNAME environment variable.
            var assemblyName = typeof(TEntryPoint).Assembly.GetName().Name;
            var settingSuffix = assemblyName.ToUpperInvariant().Replace(".", "_");
            var settingName = $"TEST_CONTENTROOT_{settingSuffix}";

            var settingValue = builder.GetSetting(settingName);
            if (settingValue == null)
            {
                return false;
            }

            builder.UseContentRoot(settingValue);
            return true;
        }

        private WebApplicationFactoryContentRootAttribute[] GetContentRootMetadataAttributes(
            string tEntryPointAssemblyFullName,
            string tEntryPointAssemblyName)
        {
            var testAssembly = GetTestAssemblies();
{'fraction_non_alphanumeric': 0.09778679687995434, 'fraction_numerical': 0.0003804933730737523, 'mean_word_length': 2.223630417007359, 'pattern_counts': {'":': 0, '<': 149, '<?xml version=': 0, '>': 155, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1790705', 'n_tokens_mistral': 3733, 'n_tokens_neox': 3681, 'n_words': 1040}