using Lucene.Net.Analysis;
using Lucene.Net.Codecs;
using Lucene.Net.Diagnostics;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Index.Extensions;
using Lucene.Net.Randomized;
using Lucene.Net.Randomized.Generators;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Support;
using Lucene.Net.Support.IO;
using Lucene.Net.Support.Threading;
using Lucene.Net.Util.Automaton;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using JCG = J2N.Collections.Generic;
using Console = Lucene.Net.Util.SystemConsole;
using Assert = Lucene.Net.TestFramework.Assert;
using Directory = Lucene.Net.Store.Directory;
using FieldInfo = Lucene.Net.Index.FieldInfo;
using static Lucene.Net.Search.FieldCache;
using static Lucene.Net.Util.FieldCacheSanityChecker;
using J2N.Collections.Generic.Extensions;

#if TESTFRAMEWORK_MSTEST
using Before = Microsoft.VisualStudio.TestTools.UnitTesting.TestInitializeAttribute;
using After = Microsoft.VisualStudio.TestTools.UnitTesting.TestCleanupAttribute;
using OneTimeSetUp = Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitializeAttribute;
using OneTimeTearDown = Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanupAttribute;
using Test = Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute;
using TestFixture = Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute;
#elif TESTFRAMEWORK_NUNIT
using Before = NUnit.Framework.SetUpAttribute;
using After = NUnit.Framework.TearDownAttribute;
using OneTimeSetUp = NUnit.Framework.OneTimeSetUpAttribute;
using OneTimeTearDown = NUnit.Framework.OneTimeTearDownAttribute;
using Test = NUnit.Framework.TestAttribute;
using TestFixture = NUnit.Framework.TestFixtureAttribute;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using NUnit.Framework.Internal.Commands;
#elif TESTFRAMEWORK_XUNIT
using Before = Lucene.Net.Attributes.NoOpAttribute;
using After = Lucene.Net.Attributes.NoOpAttribute;
using OneTimeSetUp = Lucene.Net.Attributes.NoOpAttribute;
using OneTimeTearDown = Lucene.Net.Attributes.NoOpAttribute;
using Test = Lucene.Net.TestFramework.SkippableFactAttribute;
using TestFixture = Lucene.Net.Attributes.NoOpAttribute;
#endif

namespace Lucene.Net.Util
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /// <summary>
    /// Base class for all Lucene.Net unit tests.
    ///
    /// <h3>Class and instance setup.</h3>
    ///
    /// <para>
    /// The preferred way to specify class (suite-level) setup/cleanup is to use
    /// static methods annotated with <see cref="OneTimeSetUp"/> and <see cref="OneTimeTearDown"/>. Any
    /// code in these methods is executed within the test framework's control and
    /// ensure proper setup has been made. <b>Try not to use static initializers
    /// (including complex final field initializers).</b> Static initializers are
    /// executed before any setup rules are fired and may cause you (or somebody
    /// else) headaches.
    /// </para>
    ///
    /// <para>
    /// For instance-level setup, use <see cref="Before"/> and <see cref="After"/> annotated
    /// methods. If you override either <see cref="SetUp()"/> or <see cref="TearDown()"/> in
    /// your subclass, make sure you call <c>base.SetUp()</c> and
    /// <c>base.TearDown()</c>. This is detected and enforced.
    /// </para>
    ///
    /// <h3>Specifying test cases</h3>
    ///
    /// <para>
    /// Any test method annotated with <see cref="Test"/> is considered a test case.
    /// </para>
    /// </summary>

    // LUCENENET TODO: Randomized seed
    ///// <h3>Randomized execution and test facilities</h3>
    /////
    ///// <para>
    ///// <see cref="LuceneTestCase"/> uses <see cref="RandomizedRunner"/> to execute test cases.
    ///// <see cref="RandomizedRunner"/> has built-in support for tests randomization
    ///// including access to a repeatable <see cref="Random"/> instance. See
    ///// <see cref="Random"/> property. Any test using <see cref="Random"/> acquired from
    ///// <see cref="Random"/> should be fully reproducible (assuming no race conditions
    ///// between threads etc.). The initial seed for a test case is reported in many
    ///// ways:
    ///// <list type="bullet">
    /////     <item>
    /////         <description>
    /////             as part of any exception thrown from its body (inserted as a dummy stack
    /////             trace entry),
    /////         </description>
    /////     </item>
    /////     <item>
    /////         <description>
    /////             as part of the main thread executing the test case (if your test hangs,
    /////             just dump the stack trace of all threads and you'll see the seed),
    /////         </description>
    /////     </item>
    /////     <item>
    /////         <description>
    /////             the master seed can also be accessed manually by getting the current
    /////             context (<see cref="RandomizedContext.Current"/>) and then calling
    /////             <see cref="RandomizedContext.RunnerSeed"/>.
    /////         </description>
    /////     </item>
    ///// </list>
    ///// </para>
    ///// </summary>
    [TestFixture]
#if TESTFRAMEWORK_XUNIT
    [Xunit.Collection("NonParallel")]
#endif
    public abstract partial class LuceneTestCase //: Assert // Wait long for leaked threads to complete before failure. zk needs this. -  See LUCENE-3995 for rationale.
#if TESTFRAMEWORK_XUNIT
        : IDisposable, Xunit.IClassFixture<BeforeAfterClass>
    {
        private int isDisposed = 0;

        public LuceneTestCase(BeforeAfterClass beforeAfter)
        {
#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
            ClassEnvRule = new TestRuleSetupAndRestoreClassEnv();
#endif
            beforeAfter.SetBeforeAfterClassActions(BeforeClass, AfterClass);
            this.SetUp();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && Interlocked.CompareExchange(ref isDisposed, 1, 0) == 0)
                this.TearDown();
        }
#elif TESTFRAMEWORK_MSTEST
    {
        //private static LuceneTestCase _currentTestInstance;
        //private static int isInitialized = 0;


        public LuceneTestCase()
        {
            lock (initalizationLock)
            {
                var thisType = this.GetType();
                if (!initalizationLock.Contains(thisType.FullName))
                    initalizationLock.Add(thisType.FullName);
                else
                    return; // Only allow this class to initialize once

                //if (_currentTestInstance != null)
                //    _currentTestInstance.AfterClass();
                //_currentTestInstance = this;

                _testClassName = thisType.FullName;
                _testName = string.Empty;
                _testClassType = thisType;

                BeforeClass();
            }
        }
#else
    {
#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        public LuceneTestCase()
        {
            ClassEnvRule = new TestRuleSetupAndRestoreClassEnv();
        }
#endif
#endif

        // --------------------------------------------------------------------
        // Test groups, system properties and other annotations modifying tests
        // --------------------------------------------------------------------

        internal const string SYSPROP_NIGHTLY = "tests:nightly";
        internal const string SYSPROP_WEEKLY = "tests:weekly";
        internal const string SYSPROP_AWAITSFIX = "tests:awaitsfix";
        internal const string SYSPROP_SLOW = "tests:slow";
        internal const string SYSPROP_BADAPPLES = "tests:badapples"; // LUCENENET specific - made internal, because not fully implemented

        ///// <seealso cref="IgnoreAfterMaxFailures"/>
        internal const string SYSPROP_MAXFAILURES = "tests:maxfailures"; // LUCENENET specific - made internal, because not fully implemented

        ///// <seealso cref="IgnoreAfterMaxFailures"/>
        internal const string SYSPROP_FAILFAST = "tests:failfast"; // LUCENENET specific - made internal, because not fully implemented

#if TESTFRAMEWORK_NUNIT
        private class LuceneDelegatingTestCommand : DelegatingTestCommand
        {
            private readonly Func<bool> shouldSkip;
            public LuceneDelegatingTestCommand(TestCommand command, Func<bool> shouldSkip, string skipReason) : base(command)
            {
                this.shouldSkip = shouldSkip ?? throw new ArgumentNullException(nameof(shouldSkip));
                SkipReason = skipReason ?? throw new ArgumentNullException(nameof(skipReason));
            }

            public RunState RunState { get; set; } = RunState.Skipped;
            public string SkipReason { get; }

            public override TestResult Execute(TestExecutionContext context)
            {
                var test = context.CurrentTest;
                // The test framework setting overrides the NUnit category setting, if false
                bool skip = shouldSkip();
                if (skip)
                {
                    test.RunState = RunState;
                    test.Properties.Set(PropertyNames.SkipReason, SkipReason);
                }

                context.CurrentResult = test.MakeTestResult();

                if (!skip)
                {
                    try
                    {
                        context.CurrentResult = innerCommand.Execute(context);
                    }
                    catch (Exception ex)
                    {
                        context.CurrentResult.RecordException(ex);
                    }
                }

                return context.CurrentResult;
            }
        }
#endif


        /// <summary>
        /// Attribute for tests that should only be run during nightly builds.
        /// </summary>
        [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
        [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "API looks better with this nested.")]
        public sealed class NightlyAttribute : System.Attribute
#if TESTFRAMEWORK_NUNIT
            , IApplyToTest, IApplyToContext, IWrapTestMethod
        {
            private const string SKIP_REASON = "This is a nightly test.";

            void IApplyToTest.ApplyToTest(NUnit.Framework.Internal.Test test)
            {
                // This method is called before initialization. The only thing
                // we can do here is set the category.
                test.Properties.Add(PropertyNames.Category, "Nightly");
            }

            void IApplyToContext.ApplyToContext(TestExecutionContext context)
            {
                // Cover the case where this attribute is applied to the whole test fixture
                var currentTest = context.CurrentTest;
                if (!TestNightly && currentTest is NUnit.Framework.Internal.TestFixture fixture)
                {
                    foreach (var testInterface in fixture.Tests)
                    {
                        var test = (NUnit.Framework.Internal.Test)testInterface;
                        test.RunState = RunState.Skipped;
                        test.Properties.Set(PropertyNames.SkipReason, SKIP_REASON);
                    }
                }
            }

            TestCommand ICommandWrapper.Wrap(TestCommand command)
            {
                // This is to cover the case where the test is decorated with the attribute
                // directly.
                return new LuceneDelegatingTestCommand(command, () => !TestNightly, SKIP_REASON);
            }
        }
#else
        { }
#endif

        /// <summary>
        /// Attribute for tests that should only be run during weekly builds.
        /// </summary>
        [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
        [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "API looks better with this nested.")]
        public sealed class WeeklyAttribute : System.Attribute
#if TESTFRAMEWORK_NUNIT
            , IApplyToTest, IApplyToContext, IWrapTestMethod
        {
            private const string SKIP_REASON = "This is a weekly test.";

            void IApplyToTest.ApplyToTest(NUnit.Framework.Internal.Test test)
            {
                // This method is called before initialization. The only thing
                // we can do here is set the category.
                test.Properties.Add(PropertyNames.Category, "Weekly");
            }

            void IApplyToContext.ApplyToContext(TestExecutionContext context)
            {
                // Cover the case where this attribute is applied to the whole test fixture
                var currentTest = context.CurrentTest;
                if (!TestWeekly && currentTest is NUnit.Framework.Internal.TestFixture fixture)
                {
                    foreach (var testInterface in fixture.Tests)
                    {
                        var test = (NUnit.Framework.Internal.Test)testInterface;
                        test.RunState = RunState.Skipped;
                        test.Properties.Set(PropertyNames.SkipReason, SKIP_REASON);
                    }
                }
            }

            TestCommand ICommandWrapper.Wrap(TestCommand command)
            {
                // This is to cover the case where the test is decorated with the attribute
                // directly.
                return new LuceneDelegatingTestCommand(command, () => !TestWeekly, SKIP_REASON);
            }
        }
#else
        { }
#endif

        /// <summary>
        /// Attribute for tests which exhibit a known issue and are temporarily disabled.
        /// </summary>
        [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
        [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "API looks better with this nested.")]
        public sealed class AwaitsFixAttribute : System.Attribute
#if TESTFRAMEWORK_NUNIT
            , IApplyToTest, IApplyToContext, IWrapTestMethod
        {
            void IApplyToTest.ApplyToTest(NUnit.Framework.Internal.Test test)
            {
                // This method is called before initialization. The only thing
                // we can do here is set the category.
                test.Properties.Add(PropertyNames.Category, "AwaitsFix");
            }

            void IApplyToContext.ApplyToContext(TestExecutionContext context)
            {
                // Cover the case where this attribute is applied to the whole test fixture
                var currentTest = context.CurrentTest;
                if (!TestAwaitsFix && currentTest is NUnit.Framework.Internal.TestFixture fixture)
                {
                    foreach (var testInterface in fixture.Tests)
                    {
                        var test = (NUnit.Framework.Internal.Test)testInterface;
                        test.RunState = RunState.Skipped;
                        test.Properties.Set(PropertyNames.SkipReason, BugUrl);
                    }
                }
            }

            TestCommand ICommandWrapper.Wrap(TestCommand command)
            {
                // This is to cover the case where the test is decorated with the attribute
                // directly.
                return new LuceneDelegatingTestCommand(command, () => !TestAwaitsFix, BugUrl) { RunState = RunState.Ignored };
            }
#else
        {
#endif
            /// <summary>
            /// Point to issue tracker entry. </summary>
            public string BugUrl { get; set; } = "A known bug is being investigated regarding this issue.";
        }

        /// <summary>
        /// Attribute for tests that are slow. Slow tests do run by default but can be
        /// disabled if a quick run is needed.
        /// </summary>
        [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
        [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "API looks better with this nested.")]
        public sealed class SlowAttribute : System.Attribute
#if TESTFRAMEWORK_NUNIT
            , IApplyToTest, IApplyToContext, IWrapTestMethod
        {
            void IApplyToTest.ApplyToTest(NUnit.Framework.Internal.Test test)
            {
                // This method is called before initialization. The only thing
                // we can do here is set the category.
                test.Properties.Add(PropertyNames.Category, "Slow");
            }

            void IApplyToContext.ApplyToContext(TestExecutionContext context)
            {
                // Cover the case where this attribute is applied to the whole test fixture
                var currentTest = context.CurrentTest;
                if (!TestSlow && currentTest is NUnit.Framework.Internal.TestFixture fixture)
                {
                    foreach (var testInterface in fixture.Tests)
                    {
                        var test = (NUnit.Framework.Internal.Test)testInterface;
                        test.RunState = RunState.Skipped;
                        test.Properties.Set(PropertyNames.SkipReason, Message);
                    }
                }
            }

            TestCommand ICommandWrapper.Wrap(TestCommand command)
            {
                // This is to cover the case where the test is decorated with the attribute
                // directly.
                return new LuceneDelegatingTestCommand(command, () => !TestSlow, Message);
            }
#else
        {
#endif
            public string Message { get; set; } = "This test is slow.";
        }

        /////// <summary>
        /////// Attribute for tests that fail frequently and should
        /////// be moved to a <a href="https://builds.apache.org/job/Lucene-BadApples-trunk-java7/">"vault" plan in Jenkins</a>.
        ///////
        /////// Tests annotated with this will be turned off by default. If you want to enable
        /////// them, set:
        /////// <pre>
        /////// -Dtests.badapples=true
        /////// </pre>
        /////// </summary>
        ////[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
        ////[SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "API looks better with this nested.")]
        ////public class BadAppleAttribute : System.Attribute
        ////{
        ////    /// <summary>
        ////    /// Point to issue tracker entry. </summary>
        ////    public virtual string BugUrl { get; set; }
        ////}

        /// <summary>
        /// Attribute for test classes that should avoid certain codec types
        /// (because they are expensive, for example).
        /// </summary>
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
        [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "API looks better with this nested.")]
        public sealed class SuppressCodecsAttribute : System.Attribute
        {
            private static readonly Regex WHITESPACE_REMOVAL = new Regex(@"\s*,\s*", RegexOptions.Compiled | RegexOptions.CultureInvariant);

            /// <summary>
            /// Constructor for CLS compliance.
            /// </summary>
            /// <param name="codecs">A comma-deliminated set of codec names.</param>
            public SuppressCodecsAttribute(string codecs)
            {
                this.Value = WHITESPACE_REMOVAL.Split(codecs);
            }

            [CLSCompliant(false)]
            public SuppressCodecsAttribute(params string[] value)
            {
                this.Value = value;
            }

            [WritableArray]
            [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
            public string[] Value { get; private set; }
        }

        // LUCENENET TODO: Finish implementation
        /// <summary>
        /// Marks any suites which are known not to close all the temporary
        /// files. This may prevent temp files and folders from being cleaned
        /// up after the suite is completed.
        /// </summary>
        /// <seealso cref="LuceneTestCase.CreateTempDir()"/>
        /// <seealso cref="LuceneTestCase.CreateTempFile(string, string)"/>
        [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
        [SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "API looks better with this nested.")]
        public sealed class SuppressTempFileChecksAttribute : System.Attribute
        {
            /// <summary>
            /// Point to issue tracker entry. </summary>
            public string BugUrl { get; set; } = "None";
        }

        // -----------------------------------------------------------------
        // Truly immutable fields and constants, initialized once and valid
        // for all suites ever since.
        // -----------------------------------------------------------------

        // :Post-Release-Update-Version.LUCENE_XY:
        /// <summary>
        /// Use this constant when creating Analyzers and any other version-dependent stuff.
        /// <para/><b>NOTE:</b> Change this when development starts for new Lucene version:
        /// </summary>
        public const LuceneVersion TEST_VERSION_CURRENT = LuceneVersion.LUCENE_48;

        // LUCENENET specific - need to jump through a few hoops in order to
        // ensure the test configuration data is loaded before anything utilizes
        // it. We simply isolate this all in a SystemPropertyData class that is
        // lazily loaded the first time the SystemPropertyHolder property is called.
        private static SystemPropertyData testProperties;
        private static SystemPropertyData TestProperties
            => LazyInitializer.EnsureInitialized(ref testProperties, () => new SystemPropertyData());

        /// <summary>
        /// Used to defer static initialization of system properties
        /// until they are called explicitly.
        /// </summary>
        private class SystemPropertyData // LUCENENET specific
        {
            public SystemPropertyData()
            {
                // LUCENENET: Always ensure the system properties are initailized before any attempt to read them.
                LuceneTestFrameworkInitializer.EnsureInitialized();
                Verbose = SystemProperties.GetPropertyAsBoolean("tests:verbose", // LUCENENET specific - reformatted with :
#if DEBUG
                    true
#else
                    false
#endif
                );
                UseInfoStream = SystemProperties.GetPropertyAsBoolean("tests:infostream", Verbose); // LUCENENET specific - reformatted with :
                RandomMultiplier = SystemProperties.GetPropertyAsInt32("tests:multiplier", 1); // LUCENENET specific - reformatted with :
                TestCodec = SystemProperties.GetProperty("tests:codec", "random");// LUCENENET specific - reformatted with :
                TestPostingsFormat = SystemProperties.GetProperty("tests:postingsformat", "random"); // LUCENENET specific - reformatted with :
                TestDocValuesFormat = SystemProperties.GetProperty("tests:docvaluesformat", "random"); // LUCENENET specific - reformatted with :
                TestDirectory = SystemProperties.GetProperty("tests:directory", "random"); // LUCENENET specific - reformatted with :
                TestLineDocsFile = SystemProperties.GetProperty("tests:linedocsfile", DEFAULT_LINE_DOCS_FILE); // LUCENENET specific - reformatted with :
                TestNightly = SystemProperties.GetPropertyAsBoolean(SYSPROP_NIGHTLY, false);
                TestWeekly = SystemProperties.GetPropertyAsBoolean(SYSPROP_WEEKLY, false);
                TestAwaitsFix = SystemProperties.GetPropertyAsBoolean(SYSPROP_AWAITSFIX, false);
                TestSlow = SystemProperties.GetPropertyAsBoolean(SYSPROP_SLOW, true); // LUCENENET specific - made default true, as per the docs
                TestThrottling = TestNightly ? Throttling.SOMETIMES : Throttling.NEVER;
                LeaveTemporary = LoadLeaveTemorary();
            }


            /// <summary>
            /// True if and only if tests are run in verbose mode. If this flag is false
            /// tests are not expected to print any messages.
            /// </summary>
            public bool Verbose { get; }

            /// <summary>
            /// TODO: javadoc? </summary>
            public bool UseInfoStream { get; } 

            /// <summary>
            /// A random multiplier which you should use when writing random tests:
            /// multiply it by the number of iterations to scale your tests (for nightly builds).
            /// </summary>
            public int RandomMultiplier { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Gets the codec to run tests with. </summary>
            public string TestCodec { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Gets the postingsFormat to run tests with. </summary>
            public string TestPostingsFormat { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Gets the docValuesFormat to run tests with </summary>
            public string TestDocValuesFormat { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Gets the directory to run tests with </summary>
            public string TestDirectory { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// The line file used by LineFileDocs </summary>
            public string TestLineDocsFile { get; set; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Whether or not <see cref="NightlyAttribute"/> tests should run. </summary>
            public bool TestNightly { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Whether or not <see cref="WeeklyAttribute"/> tests should run. </summary>
            public bool TestWeekly { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Whether or not <see cref="AwaitsFixAttribute"/> tests should run. </summary>
            public bool TestAwaitsFix { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Whether or not <see cref="SlowAttribute"/> tests should run. </summary>
            public bool TestSlow { get; } // LUCENENET specific - changed from field to property, and renamed

            /// <summary>
            /// Throttling, see <see cref="MockDirectoryWrapper.Throttling"/>. </summary>
            public Throttling TestThrottling { get; }

            /// <summary>
            /// Leave temporary files on disk, even on successful runs. </summary>
            public bool LeaveTemporary { get; }

            private static bool LoadLeaveTemorary()
            {
                bool defaultValue = false;
                // LUCENENET specific - reformatted with :
                foreach (string property in new string[] {
                    "tests:leaveTemporary" /* ANT tasks's (junit4) flag. */,
                    "tests:leavetemporary" /* lowercase */,
                    "tests:leavetmpdir" /* default */,
                    "solr:test:leavetmpdir" /* Solr's legacy */
                })
                {
                    defaultValue |= SystemProperties.GetPropertyAsBoolean(property, false);
                }
                return defaultValue;
            }
        }


        /// <summary>
        /// True if and only if tests are run in verbose mode. If this flag is false
        /// tests are not expected to print any messages.
        /// </summary>
        public static bool Verbose => TestProperties.Verbose;

        /// <summary>
        /// TODO: javadoc? </summary>
        public static bool UseInfoStream => TestProperties.UseInfoStream;

        /// <summary>
        /// A random multiplier which you should use when writing random tests:
        /// multiply it by the number of iterations to scale your tests (for nightly builds).
        /// </summary>
        public static int RandomMultiplier => TestProperties.RandomMultiplier; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// TODO: javadoc? </summary>
        public const string DEFAULT_LINE_DOCS_FILE = "europarl.lines.txt.gz";

        /// <summary>
        /// TODO: javadoc? </summary>
        public const string JENKINS_LARGE_LINE_DOCS_FILE = "enwiki.random.lines.txt";

        /// <summary>
        /// Gets the codec to run tests with. </summary>
        public static string TestCodec => TestProperties.TestCodec; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// Gets the postingsFormat to run tests with. </summary>
        public static string TestPostingsFormat => TestProperties.TestPostingsFormat; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// Gets the docValuesFormat to run tests with </summary>
        public static string TestDocValuesFormat => TestProperties.TestDocValuesFormat; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// Gets the directory to run tests with </summary>
        public static string TestDirectory => TestProperties.TestDirectory; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// The line file used by <see cref="LineFileDocs"/> </summary>
        public static string TestLineDocsFile // LUCENENET specific - changed from field to property, and renamed
        {
            get => TestProperties.TestLineDocsFile;
            set => TestProperties.TestLineDocsFile = value;
        }

        /// <summary>
        /// The line file used by <see cref="LineFileDocs"/> to override <see cref="TestLineDocsFile"/> if it exists (points to an unzipped file).</summary>
        // LUCENENET specific - used to unzip the LineDocsFile at the class level so we don't have to do it in every test.
        internal static string TempLineDocsFile { get; set; }

        /// <summary>
        /// Whether to use the temporary line docs file. This should be set to <c>true</c> when a subclass requires <see cref="LineFileDocs"/>.</summary>
        // LUCENENET specific - used to unzip the LineDocsFile at the class level so we don't have to do it in every test.
        internal bool UseTempLineDocsFile { get; set; }

        /// <summary>
        /// Whether or not <see cref="NightlyAttribute"/> tests should run. </summary>
        public static bool TestNightly => TestProperties.TestNightly; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// Whether or not <see cref="WeeklyAttribute"/> tests should run. </summary>
        public static bool TestWeekly => TestProperties.TestWeekly; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// Whether or not <see cref="AwaitsFixAttribute"/> tests should run. </summary>
        public static bool TestAwaitsFix => TestProperties.TestAwaitsFix; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// Whether or not <see cref="SlowAttribute"/> tests should run. </summary>
        public static bool TestSlow => TestProperties.TestSlow; // LUCENENET specific - changed from field to property, and renamed

        /// <summary>
        /// Throttling, see <see cref="MockDirectoryWrapper.Throttling"/>. </summary>
        internal static Throttling TestThrottling => TestProperties.TestThrottling; // LUCENENET specific - made internal, because not fully implemented

        /// <summary>
        /// Leave temporary files on disk, even on successful runs. </summary>
        public static bool LeaveTemporary => TestProperties.LeaveTemporary;


        // LUCENENET: Not Implemented
        /////// <summary>
        /////// These property keys will be ignored in verification of altered properties. </summary>
        /////// <seealso> cref= SystemPropertiesInvariantRule </seealso>
        /////// <seealso> cref= #ruleChain </seealso>
        /////// <seealso> cref= #classRules </seealso>
        ////private static readonly string[] IGNORED_INVARIANT_PROPERTIES = { "user.timezone", "java.rmi.server.randomIDs" };

        /// <summary>
        /// Filesystem-based <see cref="Directory"/> implementations. </summary>
        private static readonly IList<string> FS_DIRECTORIES = new string[] {
            "SimpleFSDirectory",
            "NIOFSDirectory",
            "MMapDirectory"
        };

        /// <summary>
        /// All <see cref="Directory"/> implementations. </summary>
        private static readonly IList<string> CORE_DIRECTORIES = LoadCoreDirectories();

        private static IList<string> LoadCoreDirectories()
        {
            return new List<string>(FS_DIRECTORIES)
            {
                "RAMDirectory"
            };
        }

        protected static ICollection<string> DoesntSupportOffsets { get; } = new JCG.HashSet<string>
        {
            "Lucene3x",
            "MockFixedIntBlock",
            "MockVariableIntBlock",
            "MockSep",
            "MockRandom"
        };

        // -----------------------------------------------------------------
        // Fields initialized in class or instance rules.
        // -----------------------------------------------------------------

        /// <summary>
        /// When <c>true</c>, Codecs for old Lucene version will support writing
        /// indexes in that format. Defaults to <c>false</c>, can be disabled by
        /// specific tests on demand.
        /// <para/>
        /// @lucene.internal
        /// </summary>
        public static bool OldFormatImpersonationIsActive { get; set; } = false; // LUCENENET specific - made into a property, since this is intended for end users to set

        // -----------------------------------------------------------------
        // Class level (suite) rules.
        // -----------------------------------------------------------------

        ///// <summary>
        ///// Stores the currently class under test.
        ///// </summary>
        //private static TestRuleStoreClassName ClassNameRule;

        /// <summary>
        /// Class environment setup rule.
        /// </summary>
#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        internal static TestRuleSetupAndRestoreClassEnv ClassEnvRule { get; } = new TestRuleSetupAndRestoreClassEnv();
#else
        // LUCENENET specific
        // Is non-static to remove inter-class dependencies on this variable
        internal TestRuleSetupAndRestoreClassEnv ClassEnvRule { get; private set; }

        /// <summary>
        /// Gets the Similarity from the Class Environment setup rule
        /// 
        /// LUCENENET specific
        /// Exposed because <see cref="TestRuleSetupAndRestoreClassEnv"/> is
        /// internal and this field is needed by other classes.
        /// </summary>
        public Similarity Similarity { get { return ClassEnvRule.similarity; } }

        /// <summary>
        /// Gets the Timezone from the Class Environment setup rule
        /// 
        /// LUCENENET specific
        /// Exposed because <see cref="TestRuleSetupAndRestoreClassEnv"/> is
        /// internal and this field is needed by other classes.
        /// </summary>
        public TimeZoneInfo TimeZone { get { return ClassEnvRule.timeZone; } }
#endif

        // LUCENENET TODO
        /// <summary>
        /// Suite failure marker (any error in the test or suite scope).
        /// </summary>
        public static readonly /*TestRuleMarkFailure*/ bool SuiteFailureMarker = true; // Means: was successful

        ///// <summary>
        ///// Ignore tests after hitting a designated number of initial failures. This
        ///// is truly a "static" global singleton since it needs to span the lifetime of all
        ///// test classes running inside this AppDomain (it cannot be part of a class rule).
        /////
        ///// <para/>This poses some problems for the test framework's tests because these sometimes
        ///// trigger intentional failures which add up to the global count. This field contains
        ///// a (possibly) changing reference to <see cref="TestRuleIgnoreAfterMaxFailures"/> and we
        ///// dispatch to its current value from the <see cref="#classRules"/> chain using <see cref="TestRuleDelegate"/>.
        ///// </summary>
        ////private static AtomicReference<TestRuleIgnoreAfterMaxFailures> IgnoreAfterMaxFailuresDelegate = LoadMaxFailuresDelegate();

        //////private static TestRule IgnoreAfterMaxFailures = LoadIgnoreAfterMaxFailures();

        ////private static AtomicReference<TestRuleIgnoreAfterMaxFailures> LoadMaxFailuresDelegate()
        ////{
        ////    int maxFailures = SystemProperties.GetPropertyAsInt32(SYSPROP_MAXFAILURES, int.MaxValue);
        ////    bool failFast = SystemProperties.GetPropertyAsBoolean(SYSPROP_FAILFAST, false);

        ////    if (failFast)
        ////    {
        ////        if (maxFailures == int.MaxValue)
        ////        {
        ////            maxFailures = 1;
        ////        }
        ////        else
        ////        {
        ////            Console.Out.Write(typeof(LuceneTestCase).Name + " WARNING: Property '" + SYSPROP_MAXFAILURES + "'=" + maxFailures + ", 'failfast' is" + " ignored.");
        ////        }
        ////    }

        ////    return new AtomicReference<TestRuleIgnoreAfterMaxFailures>(new TestRuleIgnoreAfterMaxFailures(maxFailures));
        ////}

        ////private static TestRule LoadIgnoreAfterMaxFailures()
        ////{
        ////    return TestRuleDelegate.Of(IgnoreAfterMaxFailuresDelegate);
        ////}

        /////// <summary>
        /////// Temporarily substitute the global <seealso cref="TestRuleIgnoreAfterMaxFailures"/>. See
        /////// <seealso cref="#ignoreAfterMaxFailuresDelegate"/> for some explanation why this method
        /////// is needed.
        /////// </summary>
        /////*public static TestRuleIgnoreAfterMaxFailures ReplaceMaxFailureRule(TestRuleIgnoreAfterMaxFailures newValue)
        ////{
        ////  return IgnoreAfterMaxFailuresDelegate.GetAndSet(newValue);
        ////}*/

        /////// <summary>
        /////// Max 10mb of static data stored in a test suite class after the suite is complete.
        /////// Prevents static data structures leaking and causing OOMs in subsequent tests.
        /////// </summary>
        //////private static readonly long STATIC_LEAK_THRESHOLD = 10 * 1024 * 1024;

        /////// <summary>
        /////// By-name list of ignored types like loggers etc. </summary>
        //////private static ISet<string> STATIC_LEAK_IGNORED_TYPES = new JCG.HashSet<string>(new string[] { "org.slf4j.Logger", "org.apache.solr.SolrLogFormatter", typeof(EnumSet).Name });

        /////// <summary>
        /////// this controls how suite-level rules are nested. It is important that _all_ rules declared
        /////// in <seealso cref="LuceneTestCase"/> are executed in proper order if they depend on each
        /////// other.
        /////// </summary>

        ////public static TestRule classRules = RuleChain
        ////  .outerRule(new TestRuleIgnoreTestSuites())
        ////  .around(IgnoreAfterMaxFailures)
        ////  .around(SuiteFailureMarker = new TestRuleMarkFailure())
        ////  .around(new TestRuleAssertionsRequired())
        ////  .around(new TemporaryFilesCleanupRule())
        ////  .around(new StaticFieldsInvariantRule(STATIC_LEAK_THRESHOLD, true)
        ////  {
        ////      @Override
        ////      protected bool accept(System.Reflection.FieldInfo field)
        ////      {
        ////          if (STATIC_LEAK_IGNORED_TYPES.contains(field.Type.Name))
        ////          {
        ////              return false;
        ////          }
        ////          if (field.DeclaringClass == typeof(LuceneTestCase))
        ////          {
        ////              return false;
        ////          }
        ////          return base.accept(field);
        ////      }})
        ////      .around(new NoClassHooksShadowingRule())
        ////      .around(new NoInstanceHooksOverridesRule()
        ////      {
        ////      @Override
        ////      protected bool verify(Method key)
        ////      {
        ////          string name = key.Name;
        ////          return !(name.Equals("SetUp", StringComparison.Ordinal) || name.Equals("TearDown", StringComparison.Ordinal));
        ////      }})
        ////      .around(new SystemPropertiesInvariantRule(IGNORED_INVARIANT_PROPERTIES))
        ////      .around(ClassNameRule = new TestRuleStoreClassName())
        ////      .around(ClassEnvRule = new TestRuleSetupAndRestoreClassEnv());
        ////*/

        // Don't count known classes that consume memory once.
        // Don't count references from ourselves, we're top-level.

        // -----------------------------------------------------------------
        // Test level rules.
        // -----------------------------------------------------------------
        /////// <summary>
        /////// Enforces <seealso cref="#setUp()"/> and <seealso cref="#tearDown()"/> calls are chained. </summary>
        /////*private TestRuleSetupTeardownChained parentChainCallRule = new TestRuleSetupTeardownChained();

        /////// <summary>
        /////// Save test thread and name. </summary>
        ////private TestRuleThreadAndTestName threadAndTestNameRule = new TestRuleThreadAndTestName();

        /////// <summary>
        /////// Taint suite result with individual test failures. </summary>
        ////private TestRuleMarkFailure testFailureMarker = new TestRuleMarkFailure(SuiteFailureMarker);*/

        /////// <summary>
        /////// this controls how individual test rules are nested. It is important that
        /////// _all_ rules declared in <seealso cref="LuceneTestCase"/> are executed in proper order
        /////// if they depend on each other.
        /////// </summary>
        ////public TestRule ruleChain = RuleChain
        ////    .outerRule(TestFailureMarker)
        ////    .around(IgnoreAfterMaxFailures)
        ////    .around(ThreadAndTestNameRule)
        ////    .around(new SystemPropertiesInvariantRule(IGNORED_INVARIANT_PROPERTIES))
        ////    .around(new TestRuleSetupAndRestoreInstanceEnv()).
        ////    around(new TestRuleFieldCacheSanity()).
        ////    around(ParentChainCallRule);
        ////*/

        // -----------------------------------------------------------------
        // Suite and test case setup/ cleanup.
        // -----------------------------------------------------------------

        // LUCENENET specific: Temporary storage for random selections so they
        // can be set once per OneTimeSetUp and reused multiple times in SetUp
        // where they are written to the output.
        private string codecType;
        private string similarityName;

        /// <summary>
        /// For subclasses to override. Overrides must call <c>base.SetUp()</c>.
        /// </summary>
        [Before]
#pragma warning disable xUnit1013
        public virtual void SetUp()
#pragma warning restore xUnit1013
        {
            // LUCENENET TODO: Not sure how to convert these
            //ParentChainCallRule.SetupCalled = true;

            // LUCENENET: Printing out randomized context regardless
            // of whether verbose is enabled (since we need it for debugging,
            // but the verbose output can crash tests).
            Console.Write("Culture: ");
            Console.WriteLine(ClassEnvRule.locale.Name);

            Console.Write("Time Zone: ");
            Console.WriteLine(ClassEnvRule.timeZone.DisplayName);

            Console.Write("Default Codec: ");
            Console.Write(ClassEnvRule.codec.Name);
            Console.Write(" (");
            Console.Write(codecType);
            Console.WriteLine(")");

            Console.Write("Default Similarity: ");
            Console.WriteLine(similarityName);

            Console.Write("Nightly: ");
            Console.WriteLine(TestNightly);

            Console.Write("Weekly: ");
            Console.WriteLine(TestWeekly);

            Console.Write("Slow: ");
            Console.WriteLine(TestSlow);

            Console.Write("Awaits Fix: ");
            Console.WriteLine(TestAwaitsFix);

            Console.Write("Directory: ");
            Console.WriteLine(TestDirectory);

            Console.Write("Verbose: ");
            Console.WriteLine(Verbose);

            Console.Write("Random Multiplier: ");
            Console.WriteLine(RandomMultiplier);
        }

        /// <summary>
        /// For subclasses to override. Overrides must call <c>base.TearDown()</c>.
        /// </summary>
        [After]
#pragma warning disable xUnit1013
        public virtual void TearDown()
#pragma warning restore xUnit1013
        {
            /* LUCENENET TODO: Not sure how to convert these
                ParentChainCallRule.TeardownCalled = true;
                */
        }

#if TESTFRAMEWORK_MSTEST
        private static readonly IList<string> initalizationLock = new List<string>();
        private static string _testClassName = string.Empty;
        private static string _testName = string.Empty;
        private static Type _testClassType;
#endif


#if TESTFRAMEWORK_MSTEST
        /// <summary>
        /// Sets up dependency injection of codec factories for running the test class,
        /// and also picks random defaults for culture, time zone, similarity, and default codec.
        /// </summary>
        // LUCENENET TODO: Add support for attribute inheritance when it is released (2.0.0)
        //[Microsoft.VisualStudio.TestTools.UnitTesting.ClassInitialize(Microsoft.VisualStudio.TestTools.UnitTesting.InheritanceBehavior.BeforeEachDerivedClass)]
        public static void BeforeClass(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext context)
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));

            lock (initalizationLock)
            {
                if (!initalizationLock.Contains(context.FullyQualifiedTestClassName))
                    initalizationLock.Add(context.FullyQualifiedTestClassName);
                else
                    return; // Only allow this class to initialize once (MSTest bug)

                _testClassName = context.FullyQualifiedTestClassName;
                _testName = context.TestName;

#if FEATURE_ASSEMBLY_GETCALLINGASSEMBLY
                var callingAssembly = Assembly.GetCallingAssembly();
                _testClassType = callingAssembly.GetType(_testClassName);
#else
                _testClassType = Type.GetType(_testClassName);
#endif
            }

            try
            {
                // Setup the factories
                LuceneTestFrameworkInitializer.EnsureInitialized();

                ClassEnvRule.Before(null);

                // LUCENENET: Generate the info once so it can be printed out for each test
                codecType = ClassEnvRule.codec.GetType().Name;
                similarityName = ClassEnvRule.similarity.ToString();

                // LUCENENET TODO: Scan for a custom attribute and setup ordering to
                // initialize data from this class to the top class

                if (UseTempLineDocsFile)
                {
                    TempLineDocsFile = LineFileDocs.MaybeCreateTempFile();
                }
            }
            catch (Exception ex)
            {
                // Write the stack trace so we have something to go on if an error occurs here.
                throw new Exception($"An exception occurred during BeforeClass:\n{ex.ToString()}", ex);
            }
        }
#endif

        /// <summary>
        /// Sets up dependency injection of codec factories for running the test class,
        /// and also picks random defaults for culture, time zone, similarity, and default codec.
        /// <para/>
        /// If you override this method, be sure to call <c>base.BeforeClass()</c> BEFORE setting
        /// up your test fixture.
        /// </summary>
        // LUCENENET specific method for setting up dependency injection of test classes.
        [OneTimeSetUp]
#pragma warning disable xUnit1013
        public virtual void BeforeClass()
#pragma warning restore xUnit1013
        {
            try
            {
                // Setup the factories
                LuceneTestFrameworkInitializer.EnsureInitialized();

                ClassEnvRule.Before(this);

                // LUCENENET: Generate the info once so it can be printed out for each test
                codecType = ClassEnvRule.codec.GetType().Name;
                similarityName = ClassEnvRule.similarity.ToString();

                if (UseTempLineDocsFile)
                {
                    TempLineDocsFile = LineFileDocs.MaybeCreateTempFile();
                }
            }
            catch (Exception ex)
            {
                // Write the stack trace so we have something to go on if an error occurs here.
                throw new Exception($"An exception occurred during BeforeClass:\n{ex}", ex);
            }
        }

#if TESTFRAMEWORK_MSTEST
        /// <summary>
        /// Tears down random defaults and cleans up temporary files.
        /// </summary>
        // LUCENENET TODO: Add support for attribute inheritance when it is released (2.0.0)
        //[Microsoft.VisualStudio.TestTools.UnitTesting.ClassCleanup(Microsoft.VisualStudio.TestTools.UnitTesting.InheritanceBehavior.BeforeEachDerivedClass)]
        public static void ClassCleanup()
        {
            try
            {
                ClassEnvRule.After(null);
                CleanupTemporaryFiles();
                if (UseTempLineDocsFile)
                {
                    TempLineDocsFile = null;
                }
            }
            catch (Exception ex)
            {
                // Write the stack trace so we have something to go on if an error occurs here.
                throw new Exception($"An exception occurred during AfterClass:\n{ex.ToString()}", ex);
            }
        }
#endif

        /// <summary>
        /// Tears down random defaults and cleans up temporary files.
        /// <para/>
        /// If you override this method, be sure to call <c>base.AfterClass()</c> AFTER
        /// tearing down your test fixture.
        /// </summary>
        // LUCENENET specific method for setting up dependency injection of test classes.
        [OneTimeTearDown]
#pragma warning disable xUnit1013
        public virtual void AfterClass()
#pragma warning restore xUnit1013
        {
            try
            {
                ClassEnvRule.After(this);
                CleanupTemporaryFiles();
                if (UseTempLineDocsFile)
                {
                    TempLineDocsFile = null;
                }
            }
            catch (Exception ex)
            {
                // Write the stack trace so we have something to go on if an error occurs here.
                throw new Exception($"An exception occurred during AfterClass:\n{ex}", ex);
            }
        }

        // -----------------------------------------------------------------
        // Test facilities and facades for subclasses.
        // -----------------------------------------------------------------

        /// <summary>
        /// Access to the current <see cref="System.Random"/> instance. It is safe to use
        /// this method from multiple threads, etc., but it should be called while within a runner's
        /// scope (so no static initializers). The returned <see cref="System.Random"/> instance will be
        /// <b>different</b> when this method is called inside a <see cref="BeforeClass()"/> hook (static
        /// suite scope) and within <see cref="Before"/>/ <see cref="After"/> hooks or test methods.
        ///
        /// <para/>The returned instance must not be shared with other threads or cross a single scope's
        /// boundary. For example, a <see cref="System.Random"/> acquired within a test method shouldn't be reused
        /// for another test case.
        ///
        /// <para/>There is an overhead connected with getting the <see cref="System.Random"/> for a particular context
        /// and thread. It is better to cache the <see cref="System.Random"/> locally if tight loops with multiple
        /// invocations are present or create a derivative local <see cref="System.Random"/> for millions of calls
        /// like this:
        /// <code>
        /// Random random = new Random(Random.Next());
        /// // tight loop with many invocations.
        /// </code>
        /// </summary>
        [SuppressMessage("Style", "IDE0025:Use expression body for properties", Justification = "Multiple lines")]
        public static Random Random
        {
            get
            {
#if TESTFRAMEWORK_NUNIT
                return NUnit.Framework.TestContext.CurrentContext.Random;
#else
                return _random ?? (_random = new Random(/* LUCENENET TODO seed */));
                //return RandomizedContext.Current.Random;
#endif
            }
        }

#if !TESTFRAMEWORK_NUNIT
        [ThreadStatic]
        private static Random _random;
#endif

        /////// <summary>
        /////// Registers a <see cref="IDisposable"/> resource that should be closed after the test
        /////// completes.
        /////// </summary>
        /////// <returns> <c>resource</c> (for call chaining). </returns>
        /////*public static T CloseAfterTest<T>(T resource)
        ////{
        ////    return RandomizedContext.Current.CloseAtEnd(resource, LifecycleScope.TEST);
        ////}*/

        /////// <summary>
        /////// Registers a <see cref="IDisposable"/> resource that should be closed after the suite
        /////// completes.
        /////// </summary>
        /////// <returns> <c>resource</c> (for call chaining). </returns>
        /////*public static T CloseAfterSuite<T>(T resource)
        ////{
        ////    return RandomizedContext.Current.CloseAtEnd(resource, LifecycleScope.SUITE);
        ////}*/

        /// <summary>
        /// Return the current type being tested.
        /// </summary>
        public static Type GetTestClass()
        {
#if !TESTFRAMEWORK_XUNIT
#if TESTFRAMEWORK_NUNIT || !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
#if TESTFRAMEWORK_NUNIT
            string testClassName = NUnit.Framework.TestContext.CurrentContext.Test.ClassName;
#else
            if (_testClassType != null)
                return _testClassType;
            string testClassName = _testClassName;
#endif

            // 1st attempt - try resolving the type name directly
            Type testClass = Type.GetType(testClassName);
            if (testClass != null)
                return testClass;

            // 2nd attempt - try scanning the referenced assemblies to see if we can find the class by fullname
            testClass = AssemblyUtils.GetReferencedAssemblies().SelectMany(a => a.GetTypes().Where(t => t.FullName == testClassName)).FirstOrDefault();
            if (testClass != null)
                return testClass;
#endif
#endif
            // Default in case none of the above attempts worked.
            return typeof(LuceneTestCase);
        }



        /// <summary>
        /// Return the name of the currently executing test case.
        /// </summary>
        [SuppressMessage("Style", "IDE0025:Use expression body for properties", Justification = "Multiple lines")]
        public virtual string TestName
        {
            get
            {
#if TESTFRAMEWORK_NUNIT
                return NUnit.Framework.TestContext.CurrentContext.Test.MethodName;
#elif TESTFRAMEWORK_MSTEST
                return _testName;
#else
                //return ThreadAndTestNameRule.TestMethodName;
                return this.GetType().Name; // LUCENENET TODO: return the current test method name if the test framework supports such a thing.
#endif
            }
        }

        /// <summary>
        /// Some tests expect the directory to contain a single segment, and want to
        /// do tests on that segment's reader. This is an utility method to help them.
        /// </summary>
        public static SegmentReader GetOnlySegmentReader(DirectoryReader reader)
        {
            IList<AtomicReaderContext> subReaders = reader.Leaves;
            if (subReaders.Count != 1)
            {
                throw new ArgumentException(reader + " has " + subReaders.Count + " segments instead of exactly one");
            }
            AtomicReader r = (AtomicReader)subReaders[0].Reader;
            Assert.IsTrue(r is SegmentReader);
            return (SegmentReader)r;
        }

        /// <summary>
        /// Returns true if and only if the calling thread is the primary thread
        /// executing the test case.
        /// <para/>
        /// LUCENENET: Not Implemented - always returns true
        /// </summary>
        /*Assert.IsNotNull(ThreadAndTestNameRule.TestCaseThread, "Test case thread not set?");
                return Thread.CurrentThread == ThreadAndTestNameRule.TestCaseThread;*/
        internal static bool IsTestThread => true; // LUCENENET specific - changed from public to internal since there is no way to support it

        /// <summary>
        /// Asserts that <see cref="FieldCacheSanityChecker"/> does not detect any
        /// problems with <see cref="FieldCache.DEFAULT"/>.
        /// <para>
        /// If any problems are found, they are logged to <see cref="Console.Error"/>
        /// (allong with the msg) when the Assertion is thrown.
        /// </para>
        /// <para>
        /// This method is called by <see cref="TearDown()"/> after every test method,
        /// however <see cref="IndexReader"/>s scoped inside test methods may be garbage
        /// collected prior to this method being called, causing errors to
        /// be overlooked. Tests are encouraged to keep their <see cref="IndexReader"/>s
        /// scoped at the class level, or to explicitly call this method
        /// directly in the same scope as the <see cref="IndexReader"/>.
        /// </para>
        /// </summary>
        /// <seealso cref="Lucene.Net.Util.FieldCacheSanityChecker"/>
        protected static void AssertSaneFieldCaches(string msg)
        {
            CacheEntry[] entries = FieldCache.DEFAULT.GetCacheEntries();
            Insanity[] insanity = null;
            try
            {
                try
                {
                    insanity = FieldCacheSanityChecker.CheckSanity(entries);
                }
                catch (Exception /*e*/)
                {
                    DumpArray(msg + ": FieldCache", entries, Console.Error);
                    throw;  // LUCENENET: CA2200: Rethrow to preserve stack details (https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2200-rethrow-to-preserve-stack-details)
                }

                Assert.AreEqual(0, insanity.Length, msg + ": Insane FieldCache usage(s) found");
                insanity = null;
            }
            finally
            {
                // report this in the event of any exception/failure
                // if no failure, then insanity will be null anyway
                if (null != insanity)
                {
                    DumpArray(msg + ": Insane FieldCache usage(s)", insanity, Console.Error);
                }
            }
        }

        /// <summary>
        /// Returns a number of at least <c>i</c>
        /// <para/>
        /// The actual number returned will be influenced by whether <see cref="TestNightly"/>
        /// is active and <see cref="RandomMultiplier"/>, but also with some random fudge.
        /// </summary>
        public static int AtLeast(Random random, int i)
        {
            //int min = (TestNightly ? 2 * i : i) * RandomMultiplier;
            // LUCENENET specific - reduced nightly factor to lower the
            // total test time in Nightly builds to get under the 1 hour time limit of Azure DevOps
            int min = (TestNightly ? (int)(1.5 * i) : i) * RandomMultiplier;
            int max = min + (min / 2);
            return TestUtil.NextInt32(random, min, max);
        }

        public static int AtLeast(int i)
        {
            return AtLeast(Random, i);
        }

        /// <summary>
        /// Returns true if something should happen rarely,
        /// <para/>
        /// The actual number returned will be influenced by whether <see cref="TestNightly"/>
        /// is active and <see cref="RandomMultiplier"/>.
        /// </summary>
        public static bool Rarely(Random random)
        {
            //int p = TestNightly ? 10 : 1;
            // LUCENENET specific - reduced nightly instance by 1/2 to lower the
            // total test time in Nightly builds to get under the 1 hour time limit of Azure DevOps
            int p = TestNightly ? 5 : 1;
            p += (int)(p * Math.Log(RandomMultiplier));
            int min = 100 - Math.Min(p, 50); // never more than 50
            return random.Next(100) >= min;
        }

        public static bool Rarely()
        {
            return Rarely(Random);
        }

        public static bool Usually(Random random)
        {
            return !Rarely(random);
        }

        public static bool Usually()
        {
            return Usually(Random);
        }

        public static void AssumeTrue(string msg, bool condition)
        {
            RandomizedTest.AssumeTrue(msg, condition);
        }

        public static void AssumeFalse(string msg, bool condition)
        {
            RandomizedTest.AssumeFalse(msg, condition);
        }

        public static void AssumeNoException(string msg, Exception e)
        {
            RandomizedTest.AssumeNoException(msg, e);
        }

        /// <summary>
        /// Return <paramref name="args"/> as a <see cref="ISet{Object}"/> instance. The order of elements is not
        /// preserved in enumerators.
        /// </summary>
        public static ISet<object> AsSet(params object[] args)
        {
            return new JCG.HashSet<object>(args);
        }

        /// <summary>
        /// Convenience method for logging an enumerator.
        /// </summary>
        /// <param name="label">  String logged before/after the items in the enumerator. </param>
        /// <param name="iter">   Each element is ToString()ed and logged on it's own line. If iter is null this is logged differnetly then an empty enumerator. </param>
        /// <param name="stream"> Stream to log messages to. </param>
        public static void DumpEnumerator(string label, IEnumerator iter, TextWriter stream) // LUCENENET specifc - renamed from DumpIterator
        {
            stream.WriteLine("*** BEGIN " + label + " ***");
            if (null == iter)
            {
                stream.WriteLine(" ... NULL ...");
            }
            else
            {
                while (iter.MoveNext())
                {
                    stream.WriteLine(Collections.ToString(iter.Current));
                }
            }
            stream.WriteLine("*** END " + label + " ***");
        }

        /// <summary>
        /// Convenience method for logging an array.  Wraps the array in an enumerator and delegates to <see cref="DumpEnumerator(string, IEnumerator, TextWriter)"/>
        /// </summary>
        /// <seealso cref="DumpEnumerator(string, IEnumerator, TextWriter)"/>
        public static void DumpArray(string label, object[] objs, TextWriter stream)
        {
            IEnumerator iter = (null == objs) ? (IEnumerator)null : objs.GetEnumerator();
            DumpEnumerator(label, iter, stream);
        }

        /// <summary>
        /// Create a new <see cref="IndexWriterConfig"/> with random defaults.
        /// </summary>
#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        // LUCENENET specific
        // Non-static so that we do not depend on any hidden static dependencies
        public IndexWriterConfig NewIndexWriterConfig(LuceneVersion v, Analyzer a)
#else
        public static IndexWriterConfig NewIndexWriterConfig(LuceneVersion v, Analyzer a)
#endif
        {
            return NewIndexWriterConfig(Random, v, a);
        }

#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        /// <summary>
        /// Create a new <see cref="IndexWriterConfig"/> with random defaults.
        /// </summary>
        // LUCENENET specific
        // Pass LuceneTestCase so we can access instance properties similarity and timeZone
        public static IndexWriterConfig NewIndexWriterConfig(LuceneTestCase luceneTestCase, LuceneVersion v, Analyzer a)
        {
            return NewIndexWriterConfig(luceneTestCase, Random, v, a);
        }

        /// <summary>
        /// Create a new <see cref="IndexWriterConfig"/> with random defaults using the specified <paramref name="random"/>.
        /// </summary>
        // LUCENENET specific - non-static overload so we don't have to explicitly
        // pass LuceneTestCase
        public IndexWriterConfig NewIndexWriterConfig(Random random, LuceneVersion v, Analyzer a)
        {
            return NewIndexWriterConfig(this, random, v, a);

        }
#endif

#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        /// <summary>
        /// Create a new <see cref="IndexWriterConfig"/> with random defaults using the specified <paramref name="random"/>.
        /// </summary>
        /// <param name="random">A random instance (usually <see cref="LuceneTestCase.Random"/>).</param>
        /// <param name="v"></param>
        /// <param name="a"></param>
        public static IndexWriterConfig NewIndexWriterConfig(Random random, LuceneVersion v, Analyzer a)
#else
        /// <summary>
        /// Create a new <see cref="IndexWriterConfig"/> with random defaults using the specified <paramref name="random"/>.
        /// </summary>
        /// <param name="luceneTestCase">The current test instance.</param>
        /// <param name="random">A random instance (usually <see cref="LuceneTestCase.Random"/>).</param>
        /// <param name="v"></param>
        /// <param name="a"></param>
        // LUCENENET specific
        // This is the only static ctor for IndexWriterConfig because it removes the dependency
        // on ClassEnvRule by using parameters Similarity and TimeZone.
        public static IndexWriterConfig NewIndexWriterConfig(LuceneTestCase luceneTestCase, Random random, LuceneVersion v, Analyzer a)
#endif
        {
            IndexWriterConfig c = new IndexWriterConfig(v, a);
#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
            c.SetSimilarity(ClassEnvRule.similarity);
#else
            c.SetSimilarity(luceneTestCase.ClassEnvRule.similarity);
#endif
            if (Verbose)
            {
                // Even though TestRuleSetupAndRestoreClassEnv calls
                // InfoStream.setDefault, we do it again here so that
                // the PrintStreamInfoStream.messageID increments so
                // that when there are separate instances of
                // IndexWriter created we see "IW 0", "IW 1", "IW 2",
                // ... instead of just always "IW 0":
                c.SetInfoStream(new TestRuleSetupAndRestoreClassEnv.ThreadNameFixingPrintStreamInfoStream(Console.Out));
            }

            if (random.NextBoolean())
            {
                c.SetMergeScheduler(new SerialMergeScheduler());
            }
            else if (Rarely(random))
            {
                int maxThreadCount = TestUtil.NextInt32(Random, 1, 4);
                int maxMergeCount = TestUtil.NextInt32(Random, maxThreadCount, maxThreadCount + 4);
                IConcurrentMergeScheduler mergeScheduler;

#if !FEATURE_CONCURRENTMERGESCHEDULER
                mergeScheduler = new TaskMergeScheduler();
#else
                //if (Rarely(random))
                //{
                //    mergeScheduler = new TaskMergeScheduler();
                //}
                //else
                {
                    mergeScheduler = new ConcurrentMergeScheduler();
                }
#endif
                mergeScheduler.SetMaxMergesAndThreads(maxMergeCount, maxThreadCount);
                c.SetMergeScheduler(mergeScheduler);
            }
            if (random.NextBoolean())
            {
                if (Rarely(random))
                {
                    // crazy value
                    c.SetMaxBufferedDocs(TestUtil.NextInt32(random, 2, 15));
                }
                else
                {
                    // reasonable value
                    c.SetMaxBufferedDocs(TestUtil.NextInt32(random, 16, 1000));
                }
            }
            if (random.NextBoolean())
            {
                if (Rarely(random))
                {
                    // crazy value
                    c.SetTermIndexInterval(random.NextBoolean() ? TestUtil.NextInt32(random, 1, 31) : TestUtil.NextInt32(random, 129, 1000));
                }
                else
                {
                    // reasonable value
                    c.SetTermIndexInterval(TestUtil.NextInt32(random, 32, 128));
                }
            }
            if (random.NextBoolean())
            {
                int maxNumThreadStates = Rarely(random) ? TestUtil.NextInt32(random, 5, 20) : TestUtil.NextInt32(random, 1, 4); // reasonable value -  crazy value

                // LUCENENET specific - Removed RandomDocumentsWriterPerThreadPool, as was done in Lucene 4.8.1 (see #208)
                c.SetMaxThreadStates(maxNumThreadStates);
            }
#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
            c.SetMergePolicy(NewMergePolicy(random));
#else
            c.SetMergePolicy(NewMergePolicy(random, luceneTestCase.ClassEnvRule.timeZone));
#endif
            if (Rarely(random))
            {
                c.SetMergedSegmentWarmer(new SimpleMergedSegmentWarmer(c.InfoStream));
            }
            c.SetUseCompoundFile(random.NextBoolean());
            c.SetReaderPooling(random.NextBoolean());
            c.SetReaderTermsIndexDivisor(TestUtil.NextInt32(random, 1, 4));
            c.SetCheckIntegrityAtMerge(random.NextBoolean());
            return c;
        }

#if FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        // LUCENENET specific - non-static overload so we don't have to explicitly
        // pass LuceneTestCase
        public MergePolicy NewMergePolicy(Random r)
        {
            return NewMergePolicy(this, r);
        }

        // LUCENENET specific - non-static overload so we don't have to explicitly
        // pass LuceneTestCase
        public MergePolicy NewMergePolicy()
        {
            return NewMergePolicy(this);
        }
#endif


#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        public static MergePolicy NewMergePolicy(Random r)
#else
        // LUCENENET specific
        // LuceneTestCase added to remove dependency on the then-static <see cref="ClassEnvRule"/>
        public static MergePolicy NewMergePolicy(LuceneTestCase luceneTestCase, Random r)
#endif
        {
            if (Rarely(r))
            {
                return new MockRandomMergePolicy(r);
            }
            else if (r.NextBoolean())
            {
                return NewTieredMergePolicy(r);
            }
            else if (r.Next(5) == 0)
            {
#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
                return NewAlcoholicMergePolicy(r, ClassEnvRule.timeZone);
#else
                return NewAlcoholicMergePolicy(r, luceneTestCase.ClassEnvRule.timeZone);
#endif
            }
            return NewLogMergePolicy(r);
        }

#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        public static MergePolicy NewMergePolicy()
        {
            return NewMergePolicy(Random);
        }
#else
        /// <param name="luceneTestCase">The current test instance.</param>
        // LUCENENET specific
        // Timezone added to remove dependency on the then-static <see cref="ClassEnvRule"/>
        //
        public static MergePolicy NewMergePolicy(LuceneTestCase luceneTestCase)
        {
            return NewMergePolicy(luceneTestCase, Random);
        }
#endif

        public static LogMergePolicy NewLogMergePolicy()
        {
            return NewLogMergePolicy(Random);
        }

        public static TieredMergePolicy NewTieredMergePolicy()
        {
            return NewTieredMergePolicy(Random);
        }


#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        public static AlcoholicMergePolicy NewAlcoholicMergePolicy()
        {
            return NewAlcoholicMergePolicy(Random, ClassEnvRule.timeZone);
#else
        public static AlcoholicMergePolicy NewAlcoholicMergePolicy(TimeZoneInfo timeZone)
        {
            return NewAlcoholicMergePolicy(Random, timeZone);
#endif
        }

        public static AlcoholicMergePolicy NewAlcoholicMergePolicy(Random random, TimeZoneInfo timeZone)
        {
            return new AlcoholicMergePolicy(timeZone, new Random(random.Next()));
        }

        public static LogMergePolicy NewLogMergePolicy(Random random)
        {
            LogMergePolicy logmp = random.NextBoolean() ? (LogMergePolicy)new LogDocMergePolicy() : new LogByteSizeMergePolicy();

            logmp.CalibrateSizeByDeletes = random.NextBoolean();
            if (Rarely(random))
            {
                logmp.MergeFactor = TestUtil.NextInt32(random, 2, 9);
            }
            else
            {
                logmp.MergeFactor = TestUtil.NextInt32(random, 10, 50);
            }
            ConfigureRandom(random, logmp);
            return logmp;
        }

        private static void ConfigureRandom(Random random, MergePolicy mergePolicy)
        {
            if (random.NextBoolean())
            {
                mergePolicy.NoCFSRatio = 0.1 + random.NextDouble() * 0.8;
            }
            else
            {
                mergePolicy.NoCFSRatio = random.NextBoolean() ? 1.0 : 0.0;
            }

            if (Rarely())
            {
                mergePolicy.MaxCFSSegmentSizeMB = 0.2 + random.NextDouble() * 2.0;
            }
            else
            {
                mergePolicy.MaxCFSSegmentSizeMB = double.PositiveInfinity;
            }
        }

        public static TieredMergePolicy NewTieredMergePolicy(Random random)
        {
            TieredMergePolicy tmp = new TieredMergePolicy();
            if (Rarely(random))
            {
                tmp.MaxMergeAtOnce = TestUtil.NextInt32(random, 2, 9);
                tmp.MaxMergeAtOnceExplicit = TestUtil.NextInt32(random, 2, 9);
            }
            else
            {
                tmp.MaxMergeAtOnce = TestUtil.NextInt32(random, 10, 50);
                tmp.MaxMergeAtOnceExplicit = TestUtil.NextInt32(random, 10, 50);
            }
            if (Rarely(random))
            {
                tmp.MaxMergedSegmentMB = 0.2 + random.NextDouble() * 2.0;
            }
            else
            {
                tmp.MaxMergedSegmentMB = random.NextDouble() * 100;
            }
            tmp.FloorSegmentMB = 0.2 + random.NextDouble() * 2.0;
            tmp.ForceMergeDeletesPctAllowed = 0.0 + random.NextDouble() * 30.0;
            if (Rarely(random))
            {
                tmp.SegmentsPerTier = TestUtil.NextInt32(random, 2, 20);
            }
            else
            {
                tmp.SegmentsPerTier = TestUtil.NextInt32(random, 10, 50);
            }
            ConfigureRandom(random, tmp);
            tmp.ReclaimDeletesWeight = random.NextDouble() * 4;
            return tmp;
        }

        public static MergePolicy NewLogMergePolicy(bool useCFS)
        {
            MergePolicy logmp = NewLogMergePolicy();
            logmp.NoCFSRatio = useCFS ? 1.0 : 0.0;
            return logmp;
        }

        public static MergePolicy NewLogMergePolicy(bool useCFS, int mergeFactor)
        {
            LogMergePolicy logmp = NewLogMergePolicy();
            logmp.NoCFSRatio = useCFS ? 1.0 : 0.0;
            logmp.MergeFactor = mergeFactor;
            return logmp;
        }

        public static MergePolicy NewLogMergePolicy(int mergeFactor)
        {
            LogMergePolicy logmp = NewLogMergePolicy();
            logmp.MergeFactor = mergeFactor;
            return logmp;
        }

        /// <summary>
        /// Returns a new <see cref="Directory"/> instance. Use this when the test does not
        /// care about the specific <see cref="Directory"/> implementation (most tests).
        /// <para/>
        /// The <see cref="Directory"/> is wrapped with <see cref="BaseDirectoryWrapper"/>.
        /// This means usually it will be picky, such as ensuring that you
        /// properly dispose it and close all open files in your test. It will emulate
        /// some features of Windows, such as not allowing open files to be
        /// overwritten.
        /// </summary>
        public static BaseDirectoryWrapper NewDirectory()
        {
            return NewDirectory(Random);
        }

        /// <summary>
        /// Returns a new <see cref="Directory"/> instance, using the specified <paramref name="random"/>.
        /// See <see cref="NewDirectory()"/> for more information.
        /// </summary>
        public static BaseDirectoryWrapper NewDirectory(Random random)
        {
            var newDir = NewDirectoryImpl(random, TestDirectory);

            return WrapDirectory(random, newDir, Rarely(random));
        }

        public static MockDirectoryWrapper NewMockDirectory()
        {
            return NewMockDirectory(Random);
        }

        public static MockDirectoryWrapper NewMockDirectory(Random random)
        {
            return (MockDirectoryWrapper)WrapDirectory(random, NewDirectoryImpl(random, TestDirectory), false);
        }

        public static MockDirectoryWrapper NewMockFSDirectory(DirectoryInfo d)
        {
            return (MockDirectoryWrapper)NewFSDirectory(d, null, false);
        }

        /// <summary>
        /// Returns a new <see cref="Directory"/> instance, with contents copied from the
        /// provided directory. See <see cref="NewDirectory()"/> for more
        /// information.
        /// </summary>
        public static BaseDirectoryWrapper NewDirectory(Directory d)
        {
            return NewDirectory(Random, d);
        }

        /// <summary>
        /// Returns a new <see cref="FSDirectory"/> instance over the given file, which must be a folder. </summary>
        public static BaseDirectoryWrapper NewFSDirectory(DirectoryInfo d)
        {
            return NewFSDirectory(d, null);
        }

        /// <summary>
        /// Returns a new <see cref="FSDirectory"/> instance over the given file, which must be a folder. </summary>
        public static BaseDirectoryWrapper NewFSDirectory(DirectoryInfo d, LockFactory lf)
        {
            return NewFSDirectory(d, lf, Rarely());
        }

        private static BaseDirectoryWrapper NewFSDirectory(DirectoryInfo d, LockFactory lf, bool bare)
        {
            string fsdirClass = TestDirectory;
            if (fsdirClass.Equals("random", StringComparison.Ordinal))
            {
                fsdirClass = RandomPicks.RandomFrom(Random, FS_DIRECTORIES);
            }

            // LUCENENET specific - .NET will not throw an exception if the
            // class does not inherit FSDirectory. We get a null if the name
            // cannot be resolved, not an exception.
            // We need to do an explicit check to determine if this type
            // is not a subclass of FSDirectory.
            Type clazz = CommandLineUtil.LoadFSDirectoryClass(fsdirClass);
            if (clazz == null || !(typeof(FSDirectory).IsAssignableFrom(clazz)))
            {
                // TEST_DIRECTORY is not a sub-class of FSDirectory, so draw one at random
                fsdirClass = RandomPicks.RandomFrom(Random, FS_DIRECTORIES);
                clazz = CommandLineUtil.LoadFSDirectoryClass(fsdirClass);
            }

            Directory fsdir = NewFSDirectoryImpl(clazz, d);
            BaseDirectoryWrapper wrapped = WrapDirectory(Random, fsdir, bare);
            if (lf != null)
            {
                wrapped.SetLockFactory(lf);
            }
            return wrapped;
        }

        /// <summary>
        /// Returns a new <see cref="Directory"/> instance, using the specified <paramref name="random"/>
        /// with contents copied from the provided <paramref name="directory"/>. See
        /// <see cref="NewDirectory()"/> for more information.
        /// </summary>
        public static BaseDirectoryWrapper NewDirectory(Random random, Directory directory)
        {
            Directory impl = NewDirectoryImpl(random, TestDirectory);
            foreach (string file in directory.ListAll())
            {
                directory.Copy(impl, file, file, NewIOContext(random));
            }
            return WrapDirectory(random, impl, Rarely(random));
        }

        private static BaseDirectoryWrapper WrapDirectory(Random random, Directory directory, bool bare)
        {
            if (Rarely(random))
            {
                directory = new NRTCachingDirectory(directory, random.NextDouble(), random.NextDouble());
            }

            if (Rarely(random))
            {
                double maxMBPerSec = 10 + 5 * (random.NextDouble() - 0.5);
                if (LuceneTestCase.Verbose)
                {
                    Console.WriteLine("LuceneTestCase: will rate limit output IndexOutput to " + maxMBPerSec + " MB/sec");
                }
                RateLimitedDirectoryWrapper rateLimitedDirectoryWrapper = new RateLimitedDirectoryWrapper(directory);
                switch (random.Next(10))
                {
                    case 3: // sometimes rate limit on flush
                        rateLimitedDirectoryWrapper.SetMaxWriteMBPerSec(maxMBPerSec, IOContext.UsageContext.FLUSH);
                        break;

                    case 2: // sometimes rate limit flush & merge
                        rateLimitedDirectoryWrapper.SetMaxWriteMBPerSec(maxMBPerSec, IOContext.UsageContext.FLUSH);
                        rateLimitedDirectoryWrapper.SetMaxWriteMBPerSec(maxMBPerSec, IOContext.UsageContext.MERGE);
                        break;

                    default:
                        rateLimitedDirectoryWrapper.SetMaxWriteMBPerSec(maxMBPerSec, IOContext.UsageContext.MERGE);
                        break;
                }
                directory = rateLimitedDirectoryWrapper;
            }

            if (bare)
            {
                BaseDirectoryWrapper @base = new BaseDirectoryWrapper(directory);
                // LUCENENET TODO: CloseAfterSuite(new DisposableDirectory(@base, SuiteFailureMarker));
                return @base;
            }
            else
            {
                MockDirectoryWrapper mock = new MockDirectoryWrapper(random, directory);

                mock.Throttling = TestThrottling;
                // LUCENENET TODO: CloseAfterSuite(new DisposableDirectory(mock, SuiteFailureMarker));
                return mock;
            }
        }


        public static Field NewStringField(string name, string value, Field.Store stored)
        {
            return NewField(Random, name, value, stored == Field.Store.YES ? StringField.TYPE_STORED : StringField.TYPE_NOT_STORED);
        }

        public static Field NewTextField(string name, string value, Field.Store stored)
        {
            return NewField(Random, name, value, stored == Field.Store.YES ? TextField.TYPE_STORED : TextField.TYPE_NOT_STORED);
        }

        public static Field NewStringField(Random random, string name, string value, Field.Store stored)
        {
            return NewField(random, name, value, stored == Field.Store.YES ? StringField.TYPE_STORED : StringField.TYPE_NOT_STORED);
        }

        public static Field NewTextField(Random random, string name, string value, Field.Store stored)
        {
            return NewField(random, name, value, stored == Field.Store.YES ? TextField.TYPE_STORED : TextField.TYPE_NOT_STORED);
        }

        public static Field NewField(string name, string value, FieldType type)
        {
            return NewField(Random, name, value, type);
        }

        public static Field NewField(Random random, string name, string value, FieldType type)
        {
            name = new string(name.ToCharArray());
            if (Usually(random) || !type.IsIndexed)
            {
                // most of the time, don't modify the params
                return new Field(name, value, type);
            }

            // TODO: once all core & test codecs can index
            // offsets, sometimes randomly turn on offsets if we are
            // already indexing positions...

            FieldType newType = new FieldType(type);
            if (!newType.IsStored && random.NextBoolean())
            {
                newType.IsStored = true; // randomly store it
            }

            if (!newType.StoreTermVectors && random.NextBoolean())
            {
                newType.StoreTermVectors = true;
                if (!newType.StoreTermVectorOffsets)
                {
                    newType.StoreTermVectorOffsets = random.NextBoolean();
                }
                if (!newType.StoreTermVectorPositions)
                {
                    newType.StoreTermVectorPositions = random.NextBoolean();

                    if (newType.StoreTermVectorPositions && !newType.StoreTermVectorPayloads && !OldFormatImpersonationIsActive)
                    {
                        newType.StoreTermVectorPayloads = random.NextBoolean();
                    }
                }
            }

            // TODO: we need to do this, but smarter, ie, most of
            // the time we set the same value for a given field but
            // sometimes (rarely) we change it up:
            /*
            if (newType.OmitNorms) {
                newType.setOmitNorms(random.NextBoolean());
            }
            */

            return new Field(name, value, newType);
        }

        /// <summary>
        /// Return a random <see cref="CultureInfo"/> from the available cultures on the system. 
        /// <para/>
        /// See <a href="https://issues.apache.org/jira/browse/LUCENE-4020">https://issues.apache.org/jira/browse/LUCENE-4020</a>.
        /// </summary>
        public static CultureInfo RandomCulture(Random random) // LUCENENET specific renamed from RandomLocale
        {
            ICollection<CultureInfo> systemCultures = CultureInfoSupport.GetNeutralAndSpecificCultures();
#if NETSTANDARD1_X
            // .NET Core 1.0 on macOS seems to be flakey here and not return results occasionally, so compensate by
            // returning CultureInfo.InvariantCulture when it happens.
            if (systemCultures.Count == 0)
                return CultureInfo.InvariantCulture;
#endif
            return RandomPicks.RandomFrom(random, systemCultures);
        }

        /// <summary>
        /// Return a random <see cref="TimeZoneInfo"/> from the available timezones on the system 
        /// <para/>
        /// See <a href="https://issues.apache.org/jira/browse/LUCENE-4020">https://issues.apache.org/jira/browse/LUCENE-4020</a>.
        /// </summary>
        public static TimeZoneInfo RandomTimeZone(Random random)
        {
            var systemTimeZones = TimeZoneInfo.GetSystemTimeZones();
#if NETSTANDARD1_X
            // .NET Core 1.0 on macOS seems to be flakey here and not return results occasionally, so compensate by
            // returning TimeZoneInfo.Local when it happens.
            if (systemTimeZones.Count == 0)
                return TimeZoneInfo.Local;
#endif
            return RandomPicks.RandomFrom(random, systemTimeZones);
        }

        /// <summary>
        /// return a <see cref="CultureInfo"/> object equivalent to its programmatic name. </summary>
        public static CultureInfo CultureForName(string localeName) // LUCENENET specific - renamed from LocaleForName
        {
            return new CultureInfo(localeName);
        }

        public static bool DefaultCodecSupportsDocValues => !Codec.Default.Name.Equals("Lucene3x", StringComparison.Ordinal);

        private static Directory NewFSDirectoryImpl(Type clazz, DirectoryInfo file)
        {
            return CommandLineUtil.NewFSDirectory(clazz, file);
        }

        private static Directory NewDirectoryImpl(Random random, string clazzName)
        {
            if (clazzName.Equals("random", StringComparison.Ordinal))
            {
                if (Rarely(random))
                {
                    clazzName = RandomPicks.RandomFrom(random, CORE_DIRECTORIES);
                }
                else
                {
                    clazzName = "RAMDirectory";
                }
            }

            if (Verbose)
            {
                Trace.TraceInformation("Type of Directory is : {0}", clazzName);
            }

            Type clazz = CommandLineUtil.LoadDirectoryClass(clazzName);
            if (clazz == null)
                throw new InvalidOperationException($"Type '{clazzName}' could not be instantiated.");
            // If it is a FSDirectory type, try its ctor(File)
            if (typeof(FSDirectory).IsAssignableFrom(clazz))
            {
                DirectoryInfo dir = CreateTempDir("index-" + clazzName);
                dir.Create(); // ensure it's created so we 'have' it.
                return NewFSDirectoryImpl(clazz, dir);
            }

            // try empty ctor
            return (Directory)Activator.CreateInstance(clazz);
        }

        /// <summary>
        /// Sometimes wrap the <see cref="IndexReader"/> as slow, parallel or filter reader (or
        /// combinations of that)
        /// </summary>
        public static IndexReader MaybeWrapReader(IndexReader r)
        {
            Random random = Random;
            if (Rarely())
            {
                // TODO: remove this, and fix those tests to wrap before putting slow around:
                bool wasOriginallyAtomic = r is AtomicReader;
                for (int i = 0, c = random.Next(6) + 1; i < c; i++)
                {
                    switch (random.Next(5))
                    {
                        case 0:
                            r = SlowCompositeReaderWrapper.Wrap(r);
                            break;

                        case 1:
                            // will create no FC insanity in atomic case, as ParallelAtomicReader has own cache key:
                            r = (r is AtomicReader atomicReader) ? (IndexReader)new ParallelAtomicReader(atomicReader) : new ParallelCompositeReader((CompositeReader)r);
                            break;

                        case 2:
                            // Häckidy-Hick-Hack: a standard MultiReader will cause FC insanity, so we use
                            // QueryUtils' reader with a fake cache key, so insanity checker cannot walk
                            // along our reader:
                            r = new FCInvisibleMultiReader(r);
                            break;

                        case 3:
                            AtomicReader ar = SlowCompositeReaderWrapper.Wrap(r);
                            IList<string> allFields = new List<string>();
                            foreach (FieldInfo fi in ar.FieldInfos)
                            {
                                allFields.Add(fi.Name);
                            }
                            allFields.Shuffle(Random);
                            int end = allFields.Count == 0 ? 0 : random.Next(allFields.Count);
                            ISet<string> fields = new JCG.HashSet<string>(allFields.SubList(0, end));
                            // will create no FC insanity as ParallelAtomicReader has own cache key:
                            r = new ParallelAtomicReader(new FieldFilterAtomicReader(ar, fields, false), new FieldFilterAtomicReader(ar, fields, true));
                            break;

                        case 4:
                            // Häckidy-Hick-Hack: a standard Reader will cause FC insanity, so we use
                            // QueryUtils' reader with a fake cache key, so insanity checker cannot walk
                            // along our reader:
                            if (r is AtomicReader atomicReader2)
                            {
                                r = new AssertingAtomicReader(atomicReader2);
                            }
                            else if (r is DirectoryReader directoryReader)
                            {
                                r = new AssertingDirectoryReader(directoryReader);
                            }
                            break;

                        default:
                            Assert.Fail("should not get here");
                            break;
                    }
                }
                if (wasOriginallyAtomic)
                {
                    r = SlowCompositeReaderWrapper.Wrap(r);
                }
                else if ((r is CompositeReader) && !(r is FCInvisibleMultiReader))
                {
                    // prevent cache insanity caused by e.g. ParallelCompositeReader, to fix we wrap one more time:
                    r = new FCInvisibleMultiReader(r);
                }
                if (Verbose)
                {
                    Console.WriteLine("maybeWrapReader wrapped: " + r);
                }
            }
            return r;
        }

        /// <summary>
        /// TODO: javadoc </summary>
        public static IOContext NewIOContext(Random random)
        {
            return NewIOContext(random, IOContext.DEFAULT);
        }

        /// <summary>
        /// TODO: javadoc </summary>
        public static IOContext NewIOContext(Random random, IOContext oldContext)
        {
            int randomNumDocs = random.Next(4192);
            int size = random.Next(512) * randomNumDocs;
            if (oldContext.FlushInfo != null)
            {
                // Always return at least the estimatedSegmentSize of
                // the incoming IOContext:
                return new IOContext(new FlushInfo(randomNumDocs, (long)Math.Max(oldContext.FlushInfo.EstimatedSegmentSize, size)));
            }
            else if (oldContext.MergeInfo != null)
            {
                // Always return at least the estimatedMergeBytes of
                // the incoming IOContext:
                return new IOContext(new MergeInfo(randomNumDocs, Math.Max(oldContext.MergeInfo.EstimatedMergeBytes, size), random.NextBoolean(), TestUtil.NextInt32(random, 1, 100)));
            }
            else
            {
                // Make a totally random IOContext:
                IOContext context;
                switch (random.Next(5))
                {
                    case 0:
                        context = IOContext.DEFAULT;
                        break;

                    case 1:
                        context = IOContext.READ;
                        break;

                    case 2:
                        context = IOContext.READ_ONCE;
                        break;

                    case 3:
                        context = new IOContext(new MergeInfo(randomNumDocs, size, true, -1));
                        break;

                    case 4:
                        context = new IOContext(new FlushInfo(randomNumDocs, size));
                        break;

                    default:
                        context = IOContext.DEFAULT;
                        break;
                }
                return context;
            }
        }

#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        /// <summary>
        /// Create a new searcher over the reader. this searcher might randomly use
        /// threads.
        /// </summary>
        public static IndexSearcher NewSearcher(IndexReader r)
        {
            return NewSearcher(r, true);
        }

        /// <summary>
        /// Create a new searcher over the reader. This searcher might randomly use
        /// threads.
        /// </summary>
        public static IndexSearcher NewSearcher(IndexReader r, bool maybeWrap)
        {
            return NewSearcher(r, maybeWrap, true);
        }
#else
        /// <summary>
        /// Create a new searcher over the reader. this searcher might randomly use
        /// threads.
        /// </summary>
        // LUCENENET specific
        // Is non-static because <see cref="ClassEnvRule"/> is now non-static.
        public IndexSearcher NewSearcher(IndexReader r)
        {
            return NewSearcher(this, r, true);
        }

        /// <summary>
        /// Create a new searcher over the reader. This searcher might randomly use
        /// threads.
        /// </summary>
        // LUCENENET specific
        // Removes dependency on <see cref="LuceneTestCase.ClassEnvRule"/>
        public static IndexSearcher NewSearcher(LuceneTestCase luceneTestCase, IndexReader r)
        {
            return NewSearcher(luceneTestCase, r, true);
        }


        /// <summary>
        /// Create a new searcher over the reader. this searcher might randomly use
        /// threads.
        /// </summary>
        /// <param name="luceneTestCase">The current test instance.</param>
        // LUCENENET specific
        // Removes dependency on <see cref="LuceneTestCase.ClassEnv.Similarity"/>

        public static IndexSearcher NewSearcher(LuceneTestCase luceneTestCase, IndexReader r, bool maybeWrap)
        {
            return NewSearcher(luceneTestCase, r, maybeWrap, true);
        }

        /// <summary>
        /// Create a new searcher over the reader. This searcher might randomly use
        /// threads.
        /// </summary>
        // LUCENENET specific
        // Is non-static because <see cref="ClassEnvRule"/> is now non-static.
        public IndexSearcher NewSearcher(IndexReader r, bool maybeWrap)
        {
            return NewSearcher(this, r, maybeWrap, true);
        }

        /// <summary>
        /// Create a new searcher over the reader. This searcher might randomly use
        /// threads. If <paramref name="maybeWrap"/> is true, this searcher might wrap the
        /// reader with one that returns null for <see cref="CompositeReader.GetSequentialSubReaders()"/>. If
        /// <paramref name="wrapWithAssertions"/> is true, this searcher might be an
        /// <see cref="AssertingIndexSearcher"/> instance.
        /// </summary>
        // LUCENENET specific
        // Is non-static because <see cref="ClassEnvRule"/> is now non-static.
        public IndexSearcher NewSearcher(IndexReader r, bool maybeWrap, bool wrapWithAssertions)
        {
            return NewSearcher(this, r, maybeWrap, wrapWithAssertions);
        }
#endif

#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
        /// <summary>
        /// Create a new searcher over the reader. This searcher might randomly use
        /// threads. If <paramref name="maybeWrap"/> is true, this searcher might wrap the
        /// reader with one that returns null for <see cref="CompositeReader.GetSequentialSubReaders()"/>. If
        /// <paramref name="wrapWithAssertions"/> is true, this searcher might be an
        /// <see cref="AssertingIndexSearcher"/> instance.
        /// </summary>
        public static IndexSearcher NewSearcher(IndexReader r, bool maybeWrap, bool wrapWithAssertions)
#else
        /// <summary>
        /// Create a new searcher over the reader. This searcher might randomly use
        /// threads. If <paramref name="maybeWrap"/> is true, this searcher might wrap the
        /// reader with one that returns null for <see cref="CompositeReader.GetSequentialSubReaders()"/>. If
        /// <paramref name="wrapWithAssertions"/> is true, this searcher might be an
        /// <see cref="AssertingIndexSearcher"/> instance.
        /// </summary>
        /// <param name="luceneTestCase">The current test instance.</param>
        // LUCENENET specific
        // Removes dependency on <see cref="LuceneTestCase.ClassEnv.Similarity"/>
        public static IndexSearcher NewSearcher(LuceneTestCase luceneTestCase, IndexReader r, bool maybeWrap, bool wrapWithAssertions)
#endif
        {
            Random random = Random;
            if (Usually())
            {
                if (maybeWrap)
                {
                    r = MaybeWrapReader(r);
                }
                // TODO: this whole check is a coverage hack, we should move it to tests for various filterreaders.
                // ultimately whatever you do will be checkIndex'd at the end anyway.
                if (random.Next(500) == 0 && r is AtomicReader)
                {
                    // TODO: not useful to check DirectoryReader (redundant with checkindex)
                    // but maybe sometimes run this on the other crazy readers maybeWrapReader creates?
                    TestUtil.CheckReader(r);
                }
                IndexSearcher ret;
                if (wrapWithAssertions)
                {
                    ret = random.NextBoolean() ? new AssertingIndexSearcher(random, r) : new AssertingIndexSearcher(random, r.Context);
                }
                else
                {
                    ret = random.NextBoolean() ? new IndexSearcher(r) : new IndexSearcher(r.Context);
                }
#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
                ret.Similarity = ClassEnvRule.similarity;
#else
                ret.Similarity = luceneTestCase?.ClassEnvRule.similarity; // LUCENENET special case: passing null allows us to skip the Similarity
#endif
                return ret;
            }
            else
            {
                int threads = 0;
                TaskScheduler ex;
                if (random.NextBoolean())
                {
                    ex = null;
                }
                else
                {
                    threads = TestUtil.NextInt32(random, 1, 8);
                    ex = new LimitedConcurrencyLevelTaskScheduler(threads);
                    //ex = new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<IThreadRunnable>(), new NamedThreadFactory("LuceneTestCase"));
                    // uncomment to intensify LUCENE-3840
                    // ex.prestartAllCoreThreads();
                }
                if (ex != null)
                {
                    if (Verbose)
                    {
                        Console.WriteLine("NOTE: newSearcher using ExecutorService with " + threads + " threads");
                    }
                    //r.AddReaderClosedListener(new ReaderClosedListenerAnonymousInnerClassHelper(ex)); // LUCENENET TODO: Implement event (see the commented ReaderClosedListenerAnonymousInnerClassHelper class near the bottom of this file)
                }
                IndexSearcher ret;
                if (wrapWithAssertions)
                {
                    ret = random.NextBoolean() ? new AssertingIndexSearcher(random, r, ex) : new AssertingIndexSearcher(random, r.Context, ex);
                }
                else
                {
                    ret = random.NextBoolean() ? new IndexSearcher(r, ex) : new IndexSearcher(r.Context, ex);
                }
#if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION
                ret.Similarity = ClassEnvRule.similarity;
#else
                ret.Similarity = luceneTestCase?.ClassEnvRule.similarity; // LUCENENET special case: passing null allows us to skip the Similarity
#endif
                return ret;
            }
        }

        /// <summary>
        /// Gets a resource from the classpath as <see cref="Stream"/>. This method should only
        /// be used, if a real file is needed. To get a stream, code should prefer
        /// <see cref="J2N.AssemblyExtensions.FindAndGetManifestResourceStream(Assembly, Type, string)"/> using 
        /// <c>this.GetType().Assembly</c> and <c>this.GetType()</c>.
        /// </summary>
        protected virtual Stream GetDataFile(string name)
        {
            try
            {
                return this.GetType().getResourceAsStream(name);
            }
            catch (Exception e)
            {
                throw new IOException("Cannot find resource: " + name, e); // LUCENENET specific - wrapped inner exception
            }
        }

        /// <summary>
        /// Returns true if the default codec supports single valued docvalues with missing values </summary>
        public static bool DefaultCodecSupportsMissingDocValues
        {
            get
            {
                string name = Codec.Default.Name;
                if (name.Equals("Lucene3x", StringComparison.Ordinal)
                    || name.Equals("Lucene40", StringComparison.Ordinal)
                    || name.Equals("Appending", StringComparison.Ordinal)
                    || name.Equals("Lucene41", StringComparison.Ordinal)
                    || name.Equals("Lucene42", StringComparison.Ordinal))
                {
                    return false;
                }
                return true;
            }
        }

        /// <summary>
        /// Returns true if the default codec supports SORTED_SET docvalues </summary>
        public static bool DefaultCodecSupportsSortedSet
        {
            get
            {
                if (!DefaultCodecSupportsDocValues)
                {
                    return false;
                }
                string name = Codec.Default.Name;
                if (name.Equals("Lucene40", StringComparison.Ordinal)
                    || name.Equals("Lucene41", StringComparison.Ordinal)
                    || name.Equals("Appending", StringComparison.Ordinal))
                {
                    return false;
                }
                return true;
            }
        }

        /// <summary>
        /// Returns true if the codec "supports" docsWithField
        /// (other codecs return MatchAllBits, because you couldnt write missing values before)
        /// </summary>
        public static bool DefaultCodecSupportsDocsWithField
        {
            get
            {
                if (!DefaultCodecSupportsDocValues)
                {
                    return false;
                }
                string name = Codec.Default.Name;
                if (name.Equals("Appending", StringComparison.Ordinal)
                    || name.Equals("Lucene40", StringComparison.Ordinal)
                    || name.Equals("Lucene41", StringComparison.Ordinal)
                    || name.Equals("Lucene42", StringComparison.Ordinal))
                {
                    return false;
                }
                return true;
            }
        }

        /// <summary>
        /// Returns true if the codec "supports" field updates. </summary>
        public static bool DefaultCodecSupportsFieldUpdates
        {
            get
            {
                string name = Codec.Default.Name;
                if (name.Equals("Lucene3x", StringComparison.Ordinal)
                    || name.Equals("Appending", StringComparison.Ordinal)
                    || name.Equals("Lucene40", StringComparison.Ordinal)
                    || name.Equals("Lucene41", StringComparison.Ordinal)
                    || name.Equals("Lucene42", StringComparison.Ordinal)
                    || name.Equals("Lucene45", StringComparison.Ordinal))
                {
                    return false;
                }
                return true;
            }
        }

        public virtual void AssertReaderEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            AssertReaderStatisticsEquals(info, leftReader, rightReader);
            AssertFieldsEquals(info, leftReader, MultiFields.GetFields(leftReader), MultiFields.GetFields(rightReader), true);
            AssertNormsEquals(info, leftReader, rightReader);
            AssertStoredFieldsEquals(info, leftReader, rightReader);
            AssertTermVectorsEquals(info, leftReader, rightReader);
            AssertDocValuesEquals(info, leftReader, rightReader);
            AssertDeletedDocsEquals(info, leftReader, rightReader);
            AssertFieldInfosEquals(info, leftReader, rightReader);
        }

        /// <summary>
        /// Checks that reader-level statistics are the same.
        /// </summary>
        public virtual void AssertReaderStatisticsEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            // Somewhat redundant: we never delete docs
            Assert.AreEqual(leftReader.MaxDoc, rightReader.MaxDoc, info);
            Assert.AreEqual(leftReader.NumDocs, rightReader.NumDocs, info);
            Assert.AreEqual(leftReader.NumDeletedDocs, rightReader.NumDeletedDocs, info);
            Assert.AreEqual(leftReader.HasDeletions, rightReader.HasDeletions, info);
        }

        /// <summary>
        /// Fields API equivalency.
        /// </summary>
        public virtual void AssertFieldsEquals(string info, IndexReader leftReader, Fields leftFields, Fields rightFields, bool deep)
        {
            // Fields could be null if there are no postings,
            // but then it must be null for both
            if (leftFields == null || rightFields == null)
            {
                Assert.IsNull(leftFields, info);
                Assert.IsNull(rightFields, info);
                return;
            }
            AssertFieldStatisticsEquals(info, leftFields, rightFields);

            using IEnumerator<string> leftEnum = leftFields.GetEnumerator();
            using IEnumerator<string> rightEnum = rightFields.GetEnumerator();
            while (leftEnum.MoveNext())
            {
                string field = leftEnum.Current;
                rightEnum.MoveNext();
                Assert.AreEqual(field, rightEnum.Current, info);
                AssertTermsEquals(info, leftReader, leftFields.GetTerms(field), rightFields.GetTerms(field), deep);
            }
            Assert.IsFalse(rightEnum.MoveNext());
        }

        /// <summary>
        /// Checks that top-level statistics on <see cref="Fields"/> are the same.
        /// </summary>
        public virtual void AssertFieldStatisticsEquals(string info, Fields leftFields, Fields rightFields)
        {
            if (leftFields.Count != -1 && rightFields.Count != -1)
            {
                Assert.AreEqual(leftFields.Count, rightFields.Count, info);
            }
        }

        /// <summary>
        /// <see cref="Terms"/> API equivalency.
        /// </summary>
        public virtual void AssertTermsEquals(string info, IndexReader leftReader, Terms leftTerms, Terms rightTerms, bool deep)
        {
            if (leftTerms == null || rightTerms == null)
            {
                Assert.IsNull(leftTerms, info);
                Assert.IsNull(rightTerms, info);
                return;
            }
            AssertTermsStatisticsEquals(info, leftTerms, rightTerms);
            Assert.AreEqual(leftTerms.HasOffsets, rightTerms.HasOffsets);
            Assert.AreEqual(leftTerms.HasPositions, rightTerms.HasPositions);
            Assert.AreEqual(leftTerms.HasPayloads, rightTerms.HasPayloads);

            TermsEnum leftTermsEnum = leftTerms.GetEnumerator();
            TermsEnum rightTermsEnum = rightTerms.GetEnumerator();
            AssertTermsEnumEquals(info, leftReader, leftTermsEnum, rightTermsEnum, true);

            AssertTermsSeekingEquals(info, leftTerms, rightTerms);

            if (deep)
            {
                int numIntersections = AtLeast(3);
                for (int i = 0; i < numIntersections; i++)
                {
                    string re = AutomatonTestUtil.RandomRegexp(Random);
                    CompiledAutomaton automaton = new CompiledAutomaton((new RegExp(re, RegExpSyntax.NONE)).ToAutomaton());
                    if (automaton.Type == CompiledAutomaton.AUTOMATON_TYPE.NORMAL)
                    {
                        // TODO: test start term too
                        TermsEnum leftIntersection = leftTerms.Intersect(automaton, null);
                        TermsEnum rightIntersection = rightTerms.Intersect(automaton, null);
                        AssertTermsEnumEquals(info, leftReader, leftIntersection, rightIntersection, Rarely());
                    }
                }
            }
        }

        /// <summary>
        /// Checks collection-level statistics on <see cref="Terms"/>.
        /// </summary>
        public virtual void AssertTermsStatisticsEquals(string info, Terms leftTerms, Terms rightTerms)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(leftTerms.Comparer == rightTerms.Comparer);
            if (leftTerms.DocCount != -1 && rightTerms.DocCount != -1)
            {
                Assert.AreEqual(leftTerms.DocCount, rightTerms.DocCount, info);
            }
            if (leftTerms.SumDocFreq != -1 && rightTerms.SumDocFreq != -1)
            {
                Assert.AreEqual(leftTerms.SumDocFreq, rightTerms.SumDocFreq, info);
            }
            if (leftTerms.SumTotalTermFreq != -1 && rightTerms.SumTotalTermFreq != -1)
            {
                Assert.AreEqual(leftTerms.SumTotalTermFreq, rightTerms.SumTotalTermFreq, info);
            }
            if (leftTerms.Count != -1 && rightTerms.Count != -1)
            {
                Assert.AreEqual(leftTerms.Count, rightTerms.Count, info);
            }
        }

        internal class RandomBits : IBits
        {
            private static FixedBitSet bits;

            internal RandomBits(int maxDoc, double pctLive, Random random)
            {
                bits = new FixedBitSet(maxDoc);
                for (int i = 0; i < maxDoc; i++)
                {
                    if (random.NextDouble() <= pctLive)
                    {
                        bits.Set(i);
                    }
                }
            }

            public bool Get(int index)
                => bits.Get(index);

            public int Length => bits.Length;
        }

        /// <summary>
        /// Checks the terms enum sequentially.
        /// If <paramref name="deep"/> is false, it does a 'shallow' test that doesnt go down to the docsenums.
        /// </summary>
        public virtual void AssertTermsEnumEquals(string info, IndexReader leftReader, TermsEnum leftTermsEnum, TermsEnum rightTermsEnum, bool deep)
        {
            BytesRef term;
            IBits randomBits = new RandomBits(leftReader.MaxDoc, Random.NextDouble(), Random);
            DocsAndPositionsEnum leftPositions = null;
            DocsAndPositionsEnum rightPositions = null;
            DocsEnum leftDocs = null;
            DocsEnum rightDocs = null;

            while (leftTermsEnum.MoveNext())
            {
                term = leftTermsEnum.Term;
                rightTermsEnum.MoveNext();
                Assert.AreEqual(term, rightTermsEnum.Term, info);
                AssertTermStatsEquals(info, leftTermsEnum, rightTermsEnum);
                if (deep)
                {
                    AssertDocsAndPositionsEnumEquals(info, leftPositions = leftTermsEnum.DocsAndPositions(null, leftPositions), rightPositions = rightTermsEnum.DocsAndPositions(null, rightPositions));
                    AssertDocsAndPositionsEnumEquals(info, leftPositions = leftTermsEnum.DocsAndPositions(randomBits, leftPositions), rightPositions = rightTermsEnum.DocsAndPositions(randomBits, rightPositions));

                    AssertPositionsSkippingEquals(info, leftReader, leftTermsEnum.DocFreq, leftPositions = leftTermsEnum.DocsAndPositions(null, leftPositions), rightPositions = rightTermsEnum.DocsAndPositions(null, rightPositions));
                    AssertPositionsSkippingEquals(info, leftReader, leftTermsEnum.DocFreq, leftPositions = leftTermsEnum.DocsAndPositions(randomBits, leftPositions), rightPositions = rightTermsEnum.DocsAndPositions(randomBits, rightPositions));

                    // with freqs:
                    AssertDocsEnumEquals(info, leftDocs = leftTermsEnum.Docs(null, leftDocs), rightDocs = rightTermsEnum.Docs(null, rightDocs), true);
                    AssertDocsEnumEquals(info, leftDocs = leftTermsEnum.Docs(randomBits, leftDocs), rightDocs = rightTermsEnum.Docs(randomBits, rightDocs), true);

                    // w/o freqs:
                    AssertDocsEnumEquals(info, leftDocs = leftTermsEnum.Docs(null, leftDocs, DocsFlags.NONE), rightDocs = rightTermsEnum.Docs(null, rightDocs, DocsFlags.NONE), false);
                    AssertDocsEnumEquals(info, leftDocs = leftTermsEnum.Docs(randomBits, leftDocs, DocsFlags.NONE), rightDocs = rightTermsEnum.Docs(randomBits, rightDocs, DocsFlags.NONE), false);

                    // with freqs:
                    AssertDocsSkippingEquals(info, leftReader, leftTermsEnum.DocFreq, leftDocs = leftTermsEnum.Docs(null, leftDocs), rightDocs = rightTermsEnum.Docs(null, rightDocs), true);
                    AssertDocsSkippingEquals(info, leftReader, leftTermsEnum.DocFreq, leftDocs = leftTermsEnum.Docs(randomBits, leftDocs), rightDocs = rightTermsEnum.Docs(randomBits, rightDocs), true);

                    // w/o freqs:
                    AssertDocsSkippingEquals(info, leftReader, leftTermsEnum.DocFreq, leftDocs = leftTermsEnum.Docs(null, leftDocs, DocsFlags.NONE), rightDocs = rightTermsEnum.Docs(null, rightDocs, DocsFlags.NONE), false);
                    AssertDocsSkippingEquals(info, leftReader, leftTermsEnum.DocFreq, leftDocs = leftTermsEnum.Docs(randomBits, leftDocs, DocsFlags.NONE), rightDocs = rightTermsEnum.Docs(randomBits, rightDocs, DocsFlags.NONE), false);
                }
            }
            Assert.IsFalse(rightTermsEnum.MoveNext(), info);
        }

        /// <summary>
        /// Checks docs + freqs + positions + payloads, sequentially.
        /// </summary>
        public virtual void AssertDocsAndPositionsEnumEquals(string info, DocsAndPositionsEnum leftDocs, DocsAndPositionsEnum rightDocs)
        {
            if (leftDocs == null || rightDocs == null)
            {
                Assert.IsNull(leftDocs);
                Assert.IsNull(rightDocs);
                return;
            }
            Assert.AreEqual(-1, leftDocs.DocID, info);
            Assert.AreEqual(-1, rightDocs.DocID, info);
            int docid;
            while ((docid = leftDocs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
            {
                Assert.AreEqual(docid, rightDocs.NextDoc(), info);
                int freq = leftDocs.Freq;
                Assert.AreEqual(freq, rightDocs.Freq, info);
                for (int i = 0; i < freq; i++)
                {
                    Assert.AreEqual(leftDocs.NextPosition(), rightDocs.NextPosition(), info);
                    Assert.AreEqual(leftDocs.GetPayload(), rightDocs.GetPayload(), info);
                    Assert.AreEqual(leftDocs.StartOffset, rightDocs.StartOffset, info);
                    Assert.AreEqual(leftDocs.EndOffset, rightDocs.EndOffset, info);
                }
            }
            Assert.AreEqual(DocIdSetIterator.NO_MORE_DOCS, rightDocs.NextDoc(), info);
        }

        /// <summary>
        /// Checks docs + freqs, sequentially.
        /// </summary>
        public virtual void AssertDocsEnumEquals(string info, DocsEnum leftDocs, DocsEnum rightDocs, bool hasFreqs)
        {
            if (leftDocs == null)
            {
                Assert.IsNull(rightDocs);
                return;
            }
            Assert.AreEqual(-1, leftDocs.DocID, info);
            Assert.AreEqual(-1, rightDocs.DocID, info);
            int docid;
            while ((docid = leftDocs.NextDoc()) != DocIdSetIterator.NO_MORE_DOCS)
            {
                Assert.AreEqual(docid, rightDocs.NextDoc(), info);
                if (hasFreqs)
                {
                    Assert.AreEqual(leftDocs.Freq, rightDocs.Freq, info);
                }
            }
            Assert.AreEqual(DocIdSetIterator.NO_MORE_DOCS, rightDocs.NextDoc(), info);
        }

        /// <summary>
        /// Checks advancing docs.
        /// </summary>
        public virtual void AssertDocsSkippingEquals(string info, IndexReader leftReader, int docFreq, DocsEnum leftDocs, DocsEnum rightDocs, bool hasFreqs)
        {
            if (leftDocs == null)
            {
                Assert.IsNull(rightDocs);
                return;
            }
            int docid = -1;
            int averageGap = leftReader.MaxDoc / (1 + docFreq);
            int skipInterval = 16;

            while (true)
            {
                if (Random.NextBoolean())
                {
                    // nextDoc()
                    docid = leftDocs.NextDoc();
                    Assert.AreEqual(docid, rightDocs.NextDoc(), info);
                }
                else
                {
                    // advance()
                    int skip = docid + (int)Math.Ceiling(Math.Abs(skipInterval + Random.NextDouble() * averageGap));
                    docid = leftDocs.Advance(skip);
                    Assert.AreEqual(docid, rightDocs.Advance(skip), info);
                }

                if (docid == DocIdSetIterator.NO_MORE_DOCS)
                {
                    return;
                }
                if (hasFreqs)
                {
                    Assert.AreEqual(leftDocs.Freq, rightDocs.Freq, info);
                }
            }
        }

        /// <summary>
        /// Checks advancing docs + positions.
        /// </summary>
        public virtual void AssertPositionsSkippingEquals(string info, IndexReader leftReader, int docFreq, DocsAndPositionsEnum leftDocs, DocsAndPositionsEnum rightDocs)
        {
            if (leftDocs == null || rightDocs == null)
            {
                Assert.IsNull(leftDocs);
                Assert.IsNull(rightDocs);
                return;
            }

            int docid = -1;
            int averageGap = leftReader.MaxDoc / (1 + docFreq);
            int skipInterval = 16;

            while (true)
            {
                if (Random.NextBoolean())
                {
                    // nextDoc()
                    docid = leftDocs.NextDoc();
                    Assert.AreEqual(docid, rightDocs.NextDoc(), info);
                }
                else
                {
                    // advance()
                    int skip = docid + (int)Math.Ceiling(Math.Abs(skipInterval + Random.NextDouble() * averageGap));
                    docid = leftDocs.Advance(skip);
                    Assert.AreEqual(docid, rightDocs.Advance(skip), info);
                }

                if (docid == DocIdSetIterator.NO_MORE_DOCS)
                {
                    return;
                }
                int freq = leftDocs.Freq;
                Assert.AreEqual(freq, rightDocs.Freq, info);
                for (int i = 0; i < freq; i++)
                {
                    Assert.AreEqual(leftDocs.NextPosition(), rightDocs.NextPosition(), info);
                    Assert.AreEqual(leftDocs.GetPayload(), rightDocs.GetPayload(), info);
                }
            }
        }

        private void AssertTermsSeekingEquals(string info, Terms leftTerms, Terms rightTerms)
        {
            TermsEnum leftEnum = null;
            TermsEnum rightEnum = null;

            // just an upper bound
            int numTests = AtLeast(20);
            Random random = Random;

            // collect this number of terms from the left side
            ISet<BytesRef> tests = new JCG.HashSet<BytesRef>();
            int numPasses = 0;
            while (numPasses < 10 && tests.Count < numTests)
            {
                leftEnum = leftTerms.GetEnumerator(leftEnum);
                BytesRef term;
                while (leftEnum.MoveNext())
                {
                    term = leftEnum.Term;
                    int code = random.Next(10);
                    if (code == 0)
                    {
                        // the term
                        tests.Add(BytesRef.DeepCopyOf(term));
                    }
                    else if (code == 1)
                    {
                        // truncated subsequence of term
                        term = BytesRef.DeepCopyOf(term);
                        if (term.Length > 0)
                        {
                            // truncate it
                            term.Length = random.Next(term.Length);
                        }
                    }
                    else if (code == 2)
                    {
                        // term, but ensure a non-zero offset
                        var newbytes = new byte[term.Length + 5];
                        Array.Copy(term.Bytes, term.Offset, newbytes, 5, term.Length);
                        tests.Add(new BytesRef(newbytes, 5, term.Length));
                    }
                    else if (code == 3)
                    {
                        switch (LuceneTestCase.Random.Next(3))
                        {
                            case 0:
                                tests.Add(new BytesRef()); // before the first term
                                break;

                            case 1:
                                tests.Add(new BytesRef(new byte[] { unchecked((byte)0xFF), unchecked((byte)0xFF) })); // past the last term
                                break;

                            case 2:
                                tests.Add(new BytesRef(TestUtil.RandomSimpleString(LuceneTestCase.Random))); // random term
                                break;

                            default:
                                throw new InvalidOperationException();
                        }
                    }
                }
                numPasses++;
            }

            rightEnum = rightTerms.GetEnumerator(rightEnum);

            IList<BytesRef> shuffledTests = new List<BytesRef>(tests);
            shuffledTests.Shuffle(Random);

            foreach (BytesRef b in shuffledTests)
            {
                if (Rarely())
                {
                    // reuse the enums
                    leftEnum = leftTerms.GetEnumerator(leftEnum);
                    rightEnum = rightTerms.GetEnumerator(rightEnum);
                }

                bool seekExact = LuceneTestCase.Random.NextBoolean();

                if (seekExact)
                {
                    Assert.AreEqual(leftEnum.SeekExact(b), rightEnum.SeekExact(b), info);
                }
                else
                {
                    TermsEnum.SeekStatus leftStatus = leftEnum.SeekCeil(b);
                    TermsEnum.SeekStatus rightStatus = rightEnum.SeekCeil(b);
                    Assert.AreEqual(leftStatus, rightStatus, info);
                    if (leftStatus != TermsEnum.SeekStatus.END)
                    {
                        Assert.AreEqual(leftEnum.Term, rightEnum.Term, info);
                        AssertTermStatsEquals(info, leftEnum, rightEnum);
                    }
                }
            }
        }

        /// <summary>
        /// Checks term-level statistics.
        /// </summary>
        public virtual void AssertTermStatsEquals(string info, TermsEnum leftTermsEnum, TermsEnum rightTermsEnum)
        {
            Assert.AreEqual(leftTermsEnum.DocFreq, rightTermsEnum.DocFreq, info);
            if (leftTermsEnum.TotalTermFreq != -1 && rightTermsEnum.TotalTermFreq != -1)
            {
                Assert.AreEqual(leftTermsEnum.TotalTermFreq, rightTermsEnum.TotalTermFreq, info);
            }
        }

        /// <summary>
        /// Checks that norms are the same across all fields.
        /// </summary>
        public virtual void AssertNormsEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            Fields leftFields = MultiFields.GetFields(leftReader);
            Fields rightFields = MultiFields.GetFields(rightReader);
            // Fields could be null if there are no postings,
            // but then it must be null for both
            if (leftFields == null || rightFields == null)
            {
                Assert.IsNull(leftFields, info);
                Assert.IsNull(rightFields, info);
                return;
            }

            foreach (string field in leftFields)
            {
                NumericDocValues leftNorms = MultiDocValues.GetNormValues(leftReader, field);
                NumericDocValues rightNorms = MultiDocValues.GetNormValues(rightReader, field);
                if (leftNorms != null && rightNorms != null)
                {
                    AssertDocValuesEquals(info, leftReader.MaxDoc, leftNorms, rightNorms);
                }
                else
                {
                    Assert.IsNull(leftNorms, info);
                    Assert.IsNull(rightNorms, info);
                }
            }
        }

        /// <summary>
        /// Checks that stored fields of all documents are the same.
        /// </summary>
        public virtual void AssertStoredFieldsEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(leftReader.MaxDoc == rightReader.MaxDoc);
            for (int i = 0; i < leftReader.MaxDoc; i++)
            {
                Document leftDoc = leftReader.Document(i);
                Document rightDoc = rightReader.Document(i);

                // TODO: I think this is bogus because we don't document what the order should be
                // from these iterators, etc. I think the codec/IndexReader should be free to order this stuff
                // in whatever way it wants (e.g. maybe it packs related fields together or something)
                // To fix this, we sort the fields in both documents by name, but
                // we still assume that all instances with same name are in order:
                var comp = Comparer<IIndexableField>.Create((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
                leftDoc.Fields.Sort(comp);
                rightDoc.Fields.Sort(comp);

                using var leftIterator = leftDoc.GetEnumerator();
                using var rightIterator = rightDoc.GetEnumerator();
                while (leftIterator.MoveNext())
                {
                    Assert.IsTrue(rightIterator.MoveNext(), info);
                    AssertStoredFieldEquals(info, leftIterator.Current, rightIterator.Current);
                }
                Assert.IsFalse(rightIterator.MoveNext(), info);
            }
        }

        /// <summary>
        /// Checks that two stored fields are equivalent.
        /// </summary>
        public virtual void AssertStoredFieldEquals(string info, IIndexableField leftField, IIndexableField rightField)
        {
            Assert.AreEqual(leftField.Name, rightField.Name, info);
            Assert.AreEqual(leftField.GetBinaryValue(), rightField.GetBinaryValue(), info);
            Assert.AreEqual(leftField.GetStringValue(), rightField.GetStringValue(), info);
#pragma warning disable 612, 618
            Assert.AreEqual(leftField.GetNumericValue(), rightField.GetNumericValue(), info);
#pragma warning restore 612, 618
            // TODO: should we check the FT at all?
        }

        /// <summary>
        /// Checks that term vectors across all fields are equivalent.
        /// </summary>
        public virtual void AssertTermVectorsEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(leftReader.MaxDoc == rightReader.MaxDoc);
            for (int i = 0; i < leftReader.MaxDoc; i++)
            {
                Fields leftFields = leftReader.GetTermVectors(i);
                Fields rightFields = rightReader.GetTermVectors(i);
                AssertFieldsEquals(info, leftReader, leftFields, rightFields, Rarely());
            }
        }

        private static ISet<string> GetDVFields(IndexReader reader)
        {
            ISet<string> fields = new JCG.HashSet<string>();
            foreach (FieldInfo fi in MultiFields.GetMergedFieldInfos(reader))
            {
                if (fi.HasDocValues)
                {
                    fields.Add(fi.Name);
                }
            }

            return fields;
        }

        /// <summary>
        /// Checks that docvalues across all fields are equivalent.
        /// </summary>
        public virtual void AssertDocValuesEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            ISet<string> leftFields = GetDVFields(leftReader);
            ISet<string> rightFields = GetDVFields(rightReader);
            Assert.AreEqual(leftFields, rightFields, info);

            foreach (string field in leftFields)
            {
                // TODO: clean this up... very messy
                {
                    NumericDocValues leftValues = MultiDocValues.GetNumericValues(leftReader, field);
                    NumericDocValues rightValues = MultiDocValues.GetNumericValues(rightReader, field);
                    if (leftValues != null && rightValues != null)
                    {
                        AssertDocValuesEquals(info, leftReader.MaxDoc, leftValues, rightValues);
                    }
                    else
                    {
                        Assert.IsNull(leftValues, info);
                        Assert.IsNull(rightValues, info);
                    }
                }

                {
                    BinaryDocValues leftValues = MultiDocValues.GetBinaryValues(leftReader, field);
                    BinaryDocValues rightValues = MultiDocValues.GetBinaryValues(rightReader, field);
                    if (leftValues != null && rightValues != null)
                    {
                        BytesRef scratchLeft = new BytesRef();
                        BytesRef scratchRight = new BytesRef();
                        for (int docID = 0; docID < leftReader.MaxDoc; docID++)
                        {
                            leftValues.Get(docID, scratchLeft);
                            rightValues.Get(docID, scratchRight);
                            Assert.AreEqual(scratchLeft, scratchRight, info);
                        }
                    }
                    else
                    {
                        Assert.IsNull(leftValues, info);
                        Assert.IsNull(rightValues, info);
                    }
                }

                {
                    SortedDocValues leftValues = MultiDocValues.GetSortedValues(leftReader, field);
                    SortedDocValues rightValues = MultiDocValues.GetSortedValues(rightReader, field);
                    if (leftValues != null && rightValues != null)
                    {
                        // numOrds
                        Assert.AreEqual(leftValues.ValueCount, rightValues.ValueCount, info);
                        // ords
                        BytesRef scratchLeft = new BytesRef();
                        BytesRef scratchRight = new BytesRef();
                        for (int i = 0; i < leftValues.ValueCount; i++)
                        {
                            leftValues.LookupOrd(i, scratchLeft);
                            rightValues.LookupOrd(i, scratchRight);
                            Assert.AreEqual(scratchLeft, scratchRight, info);
                        }
                        // bytes
                        for (int docID = 0; docID < leftReader.MaxDoc; docID++)
                        {
                            leftValues.Get(docID, scratchLeft);
                            rightValues.Get(docID, scratchRight);
                            Assert.AreEqual(scratchLeft, scratchRight, info);
                        }
                    }
                    else
                    {
                        Assert.IsNull(leftValues, info);
                        Assert.IsNull(rightValues, info);
                    }
                }

                {
                    SortedSetDocValues leftValues = MultiDocValues.GetSortedSetValues(leftReader, field);
                    SortedSetDocValues rightValues = MultiDocValues.GetSortedSetValues(rightReader, field);
                    if (leftValues != null && rightValues != null)
                    {
                        // numOrds
                        Assert.AreEqual(leftValues.ValueCount, rightValues.ValueCount, info);
                        // ords
                        BytesRef scratchLeft = new BytesRef();
                        BytesRef scratchRight = new BytesRef();
                        for (int i = 0; i < leftValues.ValueCount; i++)
                        {
                            leftValues.LookupOrd(i, scratchLeft);
                            rightValues.LookupOrd(i, scratchRight);
                            Assert.AreEqual(scratchLeft, scratchRight, info);
                        }
                        // ord lists
                        for (int docID = 0; docID < leftReader.MaxDoc; docID++)
                        {
                            leftValues.SetDocument(docID);
                            rightValues.SetDocument(docID);
                            long ord;
                            while ((ord = leftValues.NextOrd()) != SortedSetDocValues.NO_MORE_ORDS)
                            {
                                Assert.AreEqual(ord, rightValues.NextOrd(), info);
                            }
                            Assert.AreEqual(SortedSetDocValues.NO_MORE_ORDS, rightValues.NextOrd(), info);
                        }
                    }
                    else
                    {
                        Assert.IsNull(leftValues, info);
                        Assert.IsNull(rightValues, info);
                    }
                }

                {
                    IBits leftBits = MultiDocValues.GetDocsWithField(leftReader, field);
                    IBits rightBits = MultiDocValues.GetDocsWithField(rightReader, field);
                    if (leftBits != null && rightBits != null)
                    {
                        Assert.AreEqual(leftBits.Length, rightBits.Length, info);
                        for (int i = 0; i < leftBits.Length; i++)
                        {
                            Assert.AreEqual(leftBits.Get(i), rightBits.Get(i), info);
                        }
                    }
                    else
                    {
                        Assert.IsNull(leftBits, info);
                        Assert.IsNull(rightBits, info);
                    }
                }
            }
        }

        public virtual void AssertDocValuesEquals(string info, int num, NumericDocValues leftDocValues, NumericDocValues rightDocValues)
        {
            Assert.IsNotNull(leftDocValues, info);
            Assert.IsNotNull(rightDocValues, info);
            for (int docID = 0; docID < num; docID++)
            {
                Assert.AreEqual(leftDocValues.Get(docID), rightDocValues.Get(docID));
            }
        }

        // TODO: this is kinda stupid, we don't delete documents in the test.
        public virtual void AssertDeletedDocsEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(leftReader.NumDeletedDocs == rightReader.NumDeletedDocs);
            IBits leftBits = MultiFields.GetLiveDocs(leftReader);
            IBits rightBits = MultiFields.GetLiveDocs(rightReader);

            if (leftBits == null || rightBits == null)
            {
                Assert.IsNull(leftBits, info);
                Assert.IsNull(rightBits, info);
                return;
            }

            if (Debugging.AssertsEnabled) Debugging.Assert(leftReader.MaxDoc == rightReader.MaxDoc);
            Assert.AreEqual(leftBits.Length, rightBits.Length, info);
            for (int i = 0; i < leftReader.MaxDoc; i++)
            {
                Assert.AreEqual(leftBits.Get(i), rightBits.Get(i), info);
            }
        }

        public virtual void AssertFieldInfosEquals(string info, IndexReader leftReader, IndexReader rightReader)
        {
            FieldInfos leftInfos = MultiFields.GetMergedFieldInfos(leftReader);
            FieldInfos rightInfos = MultiFields.GetMergedFieldInfos(rightReader);

            // TODO: would be great to verify more than just the names of the fields!
            JCG.SortedSet<string> left = new JCG.SortedSet<string>(StringComparer.Ordinal);
            JCG.SortedSet<string> right = new JCG.SortedSet<string>(StringComparer.Ordinal);

            foreach (FieldInfo fi in leftInfos)
            {
                left.Add(fi.Name);
            }

            foreach (FieldInfo fi in rightInfos)
            {
                right.Add(fi.Name);
            }

            Assert.AreEqual(left, right, aggressive: false, info);
        }

        /// <summary>
        /// Returns true if the file exists (can be opened), false
        /// if it cannot be opened, and (unlike .NET's 
        /// <see cref="System.IO.File.Exists(string)"/>) if there's some
        /// unexpected error, returns <c>false</c>.
        /// </summary>
        public static bool SlowFileExists(Directory dir, string fileName)
        {
            try
            {
                dir.OpenInput(fileName, IOContext.DEFAULT).Dispose();
                return true;
            }
            catch (FileNotFoundException)
            {
                return false;
            }
            // LUCENENET specific - .NET (thankfully) only has one FileNotFoundException, so we don't need this
            //catch (NoSuchFileException)
            //{
            //    return false;
            //}
            // LUCENENET specific - since NoSuchDirectoryException subclasses FileNotFoundException
            // in Lucene, we need to catch it here to be on the safe side.
            catch (DirectoryNotFoundException)
            {
                return false;
            }
        }

        /// <summary>
        /// A base location for temporary files of a given test. Helps in figuring out
        /// which tests left which files and where.
        /// </summary>
        private static DirectoryInfo tempDirBase;

        /// <summary>
        /// Retry to create temporary file name this many times.
        /// </summary>
        private const int TEMP_NAME_RETRY_THRESHOLD = 9999;

        // LUCENENET: Not Implemented
        /////// <summary>
        /////// this method is deprecated for a reason. Do not use it. Call <seealso cref="#createTempDir()"/>
        /////// or <seealso cref="#createTempDir(String)"/> or <seealso cref="#createTempFile(String, String)"/>.
        /////// </summary>
        /////*[Obsolete]
        ////public static DirectoryInfo GetBaseTempDirForTestClass()
        ////{
        ////    lock (typeof(LuceneTestCase))
        ////    {
        ////        if (TempDirBase == null)
        ////        {
        ////            DirectoryInfo directory = new DirectoryInfo(System.IO.Path.GetTempPath());
        ////            //if (Debugging.AssertsEnabled) Debugging.Assert(directory.Exists && directory.Directory != null && directory.CanWrite());

        ////            RandomizedContext ctx = RandomizedContext.Current;
        ////            Type clazz = ctx.GetTargetType;
        ////            string prefix = clazz.Name;
        ////            prefix = prefix.replaceFirst("^org.apache.lucene.", "lucene.");
        ////            prefix = prefix.replaceFirst("^org.apache.solr.", "solr.");

        ////            int attempt = 0;
        ////            DirectoryInfo f;
        ////            bool iterate = true;
        ////            do
        ////            {
        ////                if (attempt++ >= TEMP_NAME_RETRY_THRESHOLD)
        ////                {
        ////                    throw new Exception("Failed to get a temporary name too many times, check your temp directory and consider manually cleaning it: " + directory.FullName);
        ////                }
        ////                f = new DirectoryInfo(Path.Combine(directory.FullName, prefix + "-" + ctx.RunnerSeed + "-" + string.Format(CultureInfo.InvariantCulture, "%03d", attempt)));

        ////                try
        ////                {
        ////                    f.Create();
        ////                }
        ////                catch (IOException)
        ////                {
        ////                    iterate = false;
        ////                }
        ////            } while (iterate);

        ////            TempDirBase = f;
        ////            RegisterToRemoveAfterSuite(TempDirBase);
        ////        }
        ////    }
        ////    return TempDirBase;
        ////}*/

        /// <summary>
        /// Creates an empty, temporary folder (when the name of the folder is of no importance).
        /// </summary>
        /// <seealso cref="CreateTempDir(string)"/>
        public static DirectoryInfo CreateTempDir()
        {
            return CreateTempDir("tempDir");
        }

        /// <summary>
        /// Creates an empty, temporary folder with the given name prefix under the
        /// system's <see cref="Path.GetTempPath()"/>.
        /// 
        /// <para/>The folder will be automatically removed after the
        /// test class completes successfully. The test should close any file handles that would prevent
        /// the folder from being removed.
        /// </summary>
        public static DirectoryInfo CreateTempDir(string prefix)
        {
            //DirectoryInfo @base = BaseTempDirForTestClass();

            int attempt = 0;
            DirectoryInfo f;
            bool iterate = true;
            do
            {
                if (attempt++ >= TEMP_NAME_RETRY_THRESHOLD)
                {
                    throw new Exception("Failed to get a temporary name too many times, check your temp directory and consider manually cleaning it: " + System.IO.Path.GetTempPath());
                }
                // LUCENENET specific - need to use a random file name instead of a sequential one or two threads may attempt to do 
                // two operations on a file at the same time.
                //f = new DirectoryInfo(Path.Combine(System.IO.Path.GetTempPath(), "LuceneTemp", prefix + "-" + attempt));
                f = new DirectoryInfo(Path.Combine(System.IO.Path.GetTempPath(), "LuceneTemp", prefix + "-" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName())));

                try
                {
                    if (!System.IO.Directory.Exists(f.FullName))
                    {
                        f.Create();
                        iterate = false;
                    }
                }
#pragma warning disable 168, IDE0059
                catch (IOException exc)
#pragma warning restore 168, IDE0059
                {
                    iterate = true;
                }
            } while (iterate);

            RegisterToRemoveAfterSuite(f);
            return f;
        }

        /// <summary>
        /// Creates an empty file with the given prefix and suffix under the
        /// system's <see cref="Path.GetTempPath()"/>.
        ///
        /// <para/>The file will be automatically removed after the
        /// test class completes successfully. The test should close any file handles that would prevent
        /// the folder from being removed.
        /// </summary>
        public static FileInfo CreateTempFile(string prefix, string suffix)
        {
            //DirectoryInfo @base = BaseTempDirForTestClass();

            //int attempt = 0;
            FileInfo f = FileSupport.CreateTempFile(prefix, suffix, new DirectoryInfo(Path.GetTempPath()));
            //do
            //{
            //    if (attempt++ >= TEMP_NAME_RETRY_THRESHOLD)
            //    {
            //        throw new Exception("Failed to get a temporary name too many times, check your temp directory and consider manually cleaning it: " + System.IO.Path.GetTempPath());
            //    }
            //    //f = new FileInfo(Path.Combine(System.IO.Path.GetTempPath(), prefix + "-" + string.Format(CultureInfo.InvariantCulture, "{0:D3}", attempt) + suffix));
            //    f = FileSupport.CreateTempFile(prefix, suffix, new DirectoryInfo(System.IO.Path.GetTempPath()));
            //} while (f.Create() == null);

            RegisterToRemoveAfterSuite(f);
            return f;
        }

        /// <summary>
        /// Creates an empty temporary file.
        /// </summary>
        /// <seealso cref="CreateTempFile(string, string)"/>
        public static FileInfo CreateTempFile()
        {
            return CreateTempFile("tempFile", ".tmp");
        }

        /// <summary>
        /// A queue of temporary resources to be removed after the
        /// suite completes. </summary>
        /// <seealso cref="RegisterToRemoveAfterSuite(FileSystemInfo)"/>
        private static readonly ConcurrentQueue<string> cleanupQueue = new ConcurrentQueue<string>();

        /// <summary>
        /// Register temporary folder for removal after the suite completes.
        /// </summary>
        private static void RegisterToRemoveAfterSuite(FileSystemInfo f)
        {
            if (Debugging.AssertsEnabled) Debugging.Assert(f != null);

            if (LuceneTestCase.LeaveTemporary)
            {
                Console.Error.WriteLine("INFO: Will leave temporary file: " + f.FullName);
                return;
            }

            cleanupQueue.Enqueue(f.FullName);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        protected string GetFullMethodName([CallerMemberName] string memberName = "")
        {
            return string.Format("{0}+{1}", this.GetType().Name, memberName);
        }

        private static void CleanupTemporaryFiles()
        {
            // Drain cleanup queue and clear it.
            var tempDirBasePath = tempDirBase?.FullName;
            tempDirBase = null;

            // Only check and throw an IOException on un-removable files if the test
            // was successful. Otherwise just report the path of temporary files
            // and leave them there.
            if (LuceneTestCase.SuiteFailureMarker /*.WasSuccessful()*/)
            {
                while (cleanupQueue.TryDequeue(out string f))
                {
                    try
                    {
                        if (System.IO.Directory.Exists(f))
                            System.IO.Directory.Delete(f, true);
                        else if (System.IO.File.Exists(f))
                            File.Delete(f);
                    }
                    // LUCENENET specific: UnauthorizedAccessException doesn't subclass IOException as
                    // AccessDeniedException does in Java, so we need a special case for it.
                    catch (UnauthorizedAccessException e)
                    {
                        //                    Type suiteClass = RandomizedContext.Current.GetTargetType;
                        //                    if (suiteClass.IsAnnotationPresent(typeof(SuppressTempFileChecks)))
                        //                    {
                        Console.Error.WriteLine("WARNING: Leftover undeleted temporary files " + e.Message);
                        return;
                        //                    }
                    }
                    catch (IOException e)
                    {
                        //                    Type suiteClass = RandomizedContext.Current.GetTargetType;
                        //                    if (suiteClass.IsAnnotationPresent(typeof(SuppressTempFileChecks)))
                        //                    {
                        Console.Error.WriteLine("WARNING: Leftover undeleted temporary files " + e.Message);
                        return;
                        //                    }
                    }
                }
            }
            else
            {
                if (tempDirBasePath != null)
                {
                    Console.Error.WriteLine("NOTE: leaving temporary files on disk at: " + tempDirBasePath);
                }
            }
        }

        /// <summary>
        /// Contains a list of the Func&lt;IConcurrentMergeSchedulers&gt; to be tested.
        /// Delegate method allows them to be created on their target thread instead of the test thread
        /// and also ensures a separate instance is created in each case (which can affect the result of the test).
        /// <para/>
        /// The <see cref="TaskMergeScheduler"/> is only rarely included.
        /// <para/>
        /// LUCENENET specific for injection into tests (i.e. using NUnit.Framework.ValueSourceAttribute)
        /// </summary>
        public static class ConcurrentMergeSchedulerFactories
        {
            public static IList<Func<IConcurrentMergeScheduler>> Values
            {
                get
                {
                    var schedulerFactories = new List<Func<IConcurrentMergeScheduler>>();
#if FEATURE_CONCURRENTMERGESCHEDULER
                    schedulerFactories.Add(() => new ConcurrentMergeScheduler());
                    //if (Rarely())
                    //    schedulerFactories.Add(() => new TaskMergeScheduler());
#else
                    schedulerFactories.Add(() => new TaskMergeScheduler());
#endif
                    return schedulerFactories;
                }
            }
        }

        internal static void LogNativeFSFactoryDebugInfo()
        {
            // LUCENENET specific - log the current locking strategy used and HResult values
            // for assistance troubleshooting problems on Linux/macOS
            SystemConsole.WriteLine($"Locking Strategy: {NativeFSLockFactory.LockingStrategy}");
            SystemConsole.WriteLine($"Share Violation HResult: {(NativeFSLockFactory.HRESULT_FILE_SHARE_VIOLATION.HasValue ? NativeFSLockFactory.HRESULT_FILE_SHARE_VIOLATION.ToString() : "null")}");
            SystemConsole.WriteLine($"Lock Violation HResult: {(NativeFSLockFactory.HRESULT_FILE_LOCK_VIOLATION.HasValue ? NativeFSLockFactory.HRESULT_FILE_LOCK_VIOLATION.ToString() : "null")}");

            string fileName;
            try
            {
                // This could throw, but we don't care about this HResult value.
                fileName = Path.GetTempFileName();
            }
            catch (Exception e)
            {
                SystemConsole.WriteLine($"Error while creating temp file: {e}");
                return;
            }

            Stream lockStream = null;
            try
            {
                lockStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, 1, FileOptions.None);
            }
            catch (Exception e)
            {
                SystemConsole.WriteLine($"Error while opening initial share stream: {e}");
                SystemConsole.WriteLine($"******* HResult: {e.HResult}");
                return;
            }
            finally
            {
                lockStream?.Dispose();
            }
            try
            {
                // Try to get an exclusive lock on the file - this should throw an IOException with the current platform's HResult value for FileShare violation
                using var stream = new FileStream(fileName, FileMode.Open, FileAccess.Write, FileShare.None, 1, FileOptions.None);
            }
            catch (IOException io) when (io.HResult != 0)
            {
                SystemConsole.WriteLine($"Successfully retrieved sharing violation.");
                SystemConsole.WriteLine($"******* HResult: {io.HResult}");
                SystemConsole.WriteLine($"Exception: {io}");
            }
            finally
            {
                lockStream?.Dispose();
            }
        }

        private double nextNextGaussian; // LUCENENET specific
        private bool haveNextNextGaussian = false; // LUCENENET specific

        /// <summary>
        /// Returns the next pseudorandom, Gaussian ("normally") distributed
        /// <c>double</c> value with mean <c>0.0</c> and standard
        /// deviation <c>1.0</c> from this random number generator's sequence.
        /// <para/>
        /// The general contract of <c>nextGaussian</c> is that one
        /// <see cref="double"/> value, chosen from (approximately) the usual
        /// normal distribution with mean <c>0.0</c> and standard deviation
        /// <c>1.0</c>, is pseudorandomly generated and returned.
        /// 
        /// <para/>This uses the <i>polar method</i> of G. E. P. Box, M. E. Muller, and
        /// G. Marsaglia, as described by Donald E. Knuth in <i>The Art of
        /// Computer Programming</i>, Volume 3: <i>Seminumerical Algorithms</i>,
        /// section 3.4.1, subsection C, algorithm P. Note that it generates two
        /// independent values at the cost of only one call to <c>StrictMath.log</c>
        /// and one call to <c>StrictMath.sqrt</c>.
        /// </summary>
        /// <returns>The next pseudorandom, Gaussian ("normally") distributed
        /// <see cref="double"/> value with mean <c>0.0</c> and
        /// standard deviation <c>1.0</c> from this random number
        /// generator's sequence.</returns>
        // LUCENENET specific - moved this here, since this requires instance variables
        // in order to work. Was originally in carrotsearch.randomizedtesting.RandomizedTest.
        public double RandomGaussian()
        {
            // See Knuth, ACP, Section 3.4.1 Algorithm C.
            if (haveNextNextGaussian)
            {
                haveNextNextGaussian = false;
                return nextNextGaussian;
            }
            else
            {
                double v1, v2, s;
                do
                {
                    v1 = 2 * Random.NextDouble() - 1; // between -1 and 1
                    v2 = 2 * Random.NextDouble() - 1; // between -1 and 1
                    s = v1 * v1 + v2 * v2;
                } while (s >= 1 || s == 0);
                double multiplier = Math.Sqrt(-2 * Math.Log(s) / s);
                nextNextGaussian = v2 * multiplier;
                haveNextNextGaussian = true;
                return v1 * multiplier;
            }
        }
    }

    //internal class ReaderClosedListenerAnonymousInnerClassHelper : IndexReader.IReaderClosedListener
    //{
    //    private TaskScheduler ex;

    //    public ReaderClosedListenerAnonymousInnerClassHelper(TaskScheduler ex)
    //    {
    //        this.ex = ex;
    //    }

    //    public void OnClose(IndexReader reader)
    //    {
    //        TestUtil.ShutdownExecutorService(ex);
    //    }
    //}
}