| using Lucene.Net.Codecs; |
| using Lucene.Net.Configuration; |
| using NUnit.Framework; |
| using System; |
| using System.Diagnostics.CodeAnalysis; |
| using System.IO; |
| |
| 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> |
| /// <see cref="LuceneTestFrameworkInitializer"/> may be subclassed in order to |
| /// use standard dependency injection techniques for classes under test. A subclass of <see cref="LuceneTestFrameworkInitializer"/> |
| /// will be executed automatically. |
| /// <para/> |
| /// Only one subclass per assembly is allowed, and by convention these subclasses are usually named "Startup". |
| /// <para/> |
| /// The following abstract factories can be overridden for testing purposes: |
| /// <list type="table"> |
| /// <listheader> |
| /// <term>Abstraction</term> |
| /// <term>Factory Method Name</term> |
| /// <term>Default</term> |
| /// </listheader> |
| /// <item> |
| /// <term><see cref="ICodecFactory"/></term> |
| /// <term><see cref="CodecFactory"/></term> |
| /// <term><see cref="TestCodecFactory"/></term> |
| /// </item> |
| /// <item> |
| /// <term><see cref="IDocValuesFormatFactory"/></term> |
| /// <term><see cref="DocValuesFormatFactory"/></term> |
| /// <term><see cref="TestDocValuesFormatFactory"/></term> |
| /// </item> |
| /// <item> |
| /// <term><see cref="IPostingsFormatFactory"/></term> |
| /// <term><see cref="PostingsFormatFactory"/></term> |
| /// <term><see cref="TestPostingsFormatFactory"/></term> |
| /// </item> |
| /// <item> |
| /// <term><see cref="IConfigurationFactory"/></term> |
| /// <term><see cref="ConfigurationFactory"/></term> |
| /// <term><see cref="TestConfigurationFactory"/></term> |
| /// </item> |
| /// </list> |
| /// <para/> |
| /// Methods are executed one time per assembly, and are executed in the following order: |
| /// <list type="table"> |
| /// <listheader> |
| /// <term>Method</term> |
| /// <term>Description</term> |
| /// </listheader> |
| /// <item> |
| /// <term><see cref="Initialize()"/></term> |
| /// <term>Used to set the factories in the above table. In general, dependency injection for the test assembly is |
| /// setup in this method. No randomized context is available.</term> |
| /// </item> |
| /// <item> |
| /// <term><see cref="TestFrameworkSetUp()"/></term> |
| /// <term>Used to set assembly-level test setup. Executed before all tests and class-level setup in the assembly. |
| /// Repeatable randomized content can be generated using the <see cref="Random"/> property.</term> |
| /// </item> |
| /// <item> |
| /// <term><see cref="TestFrameworkTearDown()"/></term> |
| /// <term>Used to tear down assembly-level test setup. Executed after all tests and class-level tear down in the assembly. |
| /// Repeatable randomized content can be generated using the <see cref="Random"/> property.</term> |
| /// </item> |
| /// </list> |
| /// <para/> |
| /// <b>Example:</b> |
| /// <code> |
| /// using RandomizedTesting.Generators; |
| /// |
| /// public class Startup : LuceneTestFrameworkInitializer |
| /// { |
| /// // Run first |
| /// protected override void Initialize() |
| /// { |
| /// // Inject a custom configuration factory |
| /// ConfigurationFactory = new MyConfigurationFactory(); |
| /// |
| /// // Do any additional dependency injection setup here |
| /// } |
| /// |
| /// // Run before all tests in the assembly and before any class-level setup |
| /// protected overide void TestFrameworkSetUp() |
| /// { |
| /// // Get the random instance for the current context |
| /// var random = Random; |
| /// |
| /// // Generate random content |
| /// string content = random.NextSimpleString(); |
| /// |
| /// // Use randomization from LuceneTestCase |
| /// int numberOfDocuments = LuceneTestCase.AtLeast(30); |
| /// } |
| /// |
| /// // Run after all tests in the assembly and after any class-level setup |
| /// protected override void TestFrameworkTearDown() |
| /// { |
| /// // Tear down everything here |
| /// } |
| /// } |
| /// </code> |
| /// </summary> |
| public abstract class LuceneTestFrameworkInitializer |
| { |
| private bool isInitializing = false; |
| private ICodecFactory codecFactory; |
| private IDocValuesFormatFactory docValuesFormatFactory; |
| private IPostingsFormatFactory postingsFormatFactory; |
| private IConfigurationFactory configurationFactory; |
| |
| private readonly AbstractBeforeAfterRule useTempLineDocsFileRule; |
| |
| protected LuceneTestFrameworkInitializer() |
| { |
| codecFactory = new TestCodecFactory(); |
| docValuesFormatFactory = new TestDocValuesFormatFactory(); |
| postingsFormatFactory = new TestPostingsFormatFactory(); |
| configurationFactory = new TestConfigurationFactory(); |
| |
| useTempLineDocsFileRule = new UseTempLineDocsFileRule(); |
| } |
| |
| // LUCENENET specific factory methods to scan the test framework for codecs/docvaluesformats/postingsformats only once |
| |
| /// <summary> |
| /// The <see cref="ICodecFactory"/> implementation to use to load codecs during testing. |
| /// </summary> |
| protected ICodecFactory CodecFactory |
| { |
| get => codecFactory; |
| set |
| { |
| if (!isInitializing) |
| throw new InvalidOperationException("CodecFactory must be set in the Initialize() method."); |
| codecFactory = value ?? throw new ArgumentNullException(nameof(CodecFactory)); |
| } |
| } |
| |
| /// <summary> |
| /// The <see cref="IDocValuesFormatFactory"/> implementation to use to load doc values during testing. |
| /// </summary> |
| protected IDocValuesFormatFactory DocValuesFormatFactory |
| { |
| get => docValuesFormatFactory; |
| set |
| { |
| if (!isInitializing) |
| throw new InvalidOperationException("DocValuesFormatFactory must be set in the Initialize() method."); |
| docValuesFormatFactory = value ?? throw new ArgumentNullException(nameof(DocValuesFormatFactory)); |
| } |
| } |
| |
| /// <summary> |
| /// The <see cref="IPostingsFormatFactory"/> implementation to use to load postings formats during testing. |
| /// </summary> |
| protected IPostingsFormatFactory PostingsFormatFactory |
| { |
| get => postingsFormatFactory; |
| set |
| { |
| if (!isInitializing) |
| throw new InvalidOperationException("PostingsFormatFactory must be set in the Initialize() method."); |
| postingsFormatFactory = value ?? throw new ArgumentNullException(nameof(PostingsFormatFactory)); |
| } |
| } |
| |
| /// <summary> |
| /// The <see cref="IConfigurationFactory"/> implementation to use to load configuration settings during testing. |
| /// See: <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/"> |
| /// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/</a>. |
| /// </summary> |
| [CLSCompliant(false)] |
| protected IConfigurationFactory ConfigurationFactory |
| { |
| get => configurationFactory; |
| set |
| { |
| if (!isInitializing) |
| throw new InvalidOperationException("ConfigurationFactory must be set in the Initialize() method."); |
| configurationFactory = value ?? throw new ArgumentNullException(nameof(ConfigurationFactory)); |
| } |
| } |
| |
| /// <summary> |
| /// Called by <see cref="LuceneTestCase.SetUpFixture"/> to run the initialization phase for the test assembly. |
| /// </summary> |
| internal void DoInitialize() |
| { |
| try |
| { |
| isInitializing = true; |
| Initialize(); |
| isInitializing = false; |
| InitializeStaticState(); |
| AfterInitialization(); // Hook for our tests to check the result |
| } |
| catch (Exception ex) |
| { |
| // Write the stack trace so we have something to go on if an error occurs here. |
| throw RuntimeException.Create($"An exception occurred during initialization:\n{ex}", ex); |
| } |
| } |
| |
| /// <summary> |
| /// Called by <see cref="LuceneTestCase.SetUpFixture"/> to run the one time set up phase for the test assembly. |
| /// </summary> |
| internal void DoTestFrameworkSetUp() |
| { |
| useTempLineDocsFileRule.Before(); |
| |
| try |
| { |
| TestFrameworkSetUp(); |
| } |
| catch (Exception ex) |
| { |
| // Write the stack trace so we have something to go on if an error occurs here. |
| throw RuntimeException.Create($"An exception occurred during TestFrameworkSetUp:\n{ex}", ex); |
| } |
| } |
| |
| |
| /// <summary> |
| /// Called by <see cref="LuceneTestCase.SetUpFixture"/> to run the one time tear down phase for the test assembly. |
| /// </summary> |
| internal void DoTestFrameworkTearDown() |
| { |
| try |
| { |
| TestFrameworkTearDown(); |
| } |
| catch (Exception ex) |
| { |
| // Write the stack trace so we have something to go on if an error occurs here. |
| throw RuntimeException.Create($"An exception occurred during TestFrameworkTearDown:\n{ex}", ex); |
| } |
| |
| useTempLineDocsFileRule.After(); |
| } |
| |
| /// <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="LuceneTestCase.OneTimeSetUp()"/> hook (static |
| /// suite scope) and within <see cref="OneTimeSetUpAttribute"/>/ <see cref="OneTimeTearDownAttribute"/> 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 J2N.Randomizer(Random.NextInt64()); |
| /// // tight loop with many invocations. |
| /// </code> |
| /// </summary> |
| [SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "By design")] |
| protected Random Random => LuceneTestCase.Random; |
| |
| /// <summary> |
| /// Overridden in a derived class, provides a way to set <see cref="CodecFactory"/>, |
| /// <see cref="PostingsFormatFactory"/>, <see cref="DocValuesFormatFactory"/> and <see cref="ConfigurationFactory"/> as well as |
| /// other dependency injection setup. |
| /// <para/> |
| /// This method is called only one time per assembly. |
| /// <para/> |
| /// Using the <see cref="Random"/> property here will result in a test failure. To build randomized global setup, |
| /// use <see cref="TestFrameworkSetUp()"/> instead. |
| /// </summary> |
| protected virtual void Initialize() |
| { |
| // Runs only once per test framework before OneTimeSetUp on any tests, and before TestFrameworkSetUp |
| } |
| |
| /// <summary> |
| /// Overridden in a derived class, can be used to perform one-time initialization |
| /// of the test framework setup. |
| /// <para/> |
| /// Repeatable random setup can be done by calling <see cref="Random"/> or by using methods of <see cref="LuceneTestCase"/>. |
| /// <para/> |
| /// It is not possible to set <see cref="CodecFactory"/>, <see cref="PostingsFormatFactory"/>, |
| /// <see cref="DocValuesFormatFactory"/> and <see cref="ConfigurationFactory"/> |
| /// from this method. Those must be called in <see cref="Initialize()"/>. |
| /// </summary> |
| protected virtual void TestFrameworkSetUp() |
| { |
| // Runs only once per test framework run before all tests |
| } |
| |
| /// <summary> |
| /// Overridden in a derived class, can be used to perform one-time tear down |
| /// of the test framework setup (whether the setup was done in <see cref="Initialize()"/> |
| /// or <see cref="TestFrameworkSetUp()"/> doesn't matter). |
| /// <para/> |
| /// Repeatable random setup can be done by calling <see cref="Random"/> or by using methods of <see cref="LuceneTestCase"/>. |
| /// </summary> |
| protected virtual void TestFrameworkTearDown() |
| { |
| // Runs only once per test framework run after all tests |
| } |
| |
| /// <summary> |
| /// The default implementation of the initializer that is simply used to initialize with the |
| /// default test factory implementations. |
| /// </summary> |
| internal class DefaultLuceneTestFrameworkInitializer : LuceneTestFrameworkInitializer { } |
| |
| /// <summary> |
| /// Called after the factory properties have been set (possibly by using dependency injection) which allows customization |
| /// of codecs and system properties during testing. |
| /// </summary> |
| internal void InitializeStaticState() |
| { |
| // Setup the factories |
| ConfigurationSettings.SetConfigurationFactory(ConfigurationFactory); |
| Codec.SetCodecFactory(CodecFactory); |
| DocValuesFormat.SetDocValuesFormatFactory(DocValuesFormatFactory); |
| PostingsFormat.SetPostingsFormatFactory(PostingsFormatFactory); |
| |
| // Enable "asserts" for tests. In Java, these were actual asserts, |
| // but in .NET we simply mock this as a boolean static setting that can be |
| // toggled on and off, even in release mode. Note this must be done after |
| // the ConfigurationFactory is set. |
| Lucene.Net.Diagnostics.Debugging.AssertsEnabled = SystemProperties.GetPropertyAsBoolean("assert", true); |
| |
| // Identify NUnit exceptions down in Lucene.Net so they can be ignored in catch blocks that |
| // catch Java "Exception" types that do subclass Error (for the ExceptionExtensions.IsException() method). |
| Lucene.ExceptionExtensions.NUnitResultStateExceptionType = typeof(NUnit.Framework.ResultStateException); |
| Lucene.ExceptionExtensions.NUnitAssertionExceptionType = typeof(NUnit.Framework.AssertionException); |
| Lucene.ExceptionExtensions.NUnitMultipleAssertExceptionType = typeof(NUnit.Framework.MultipleAssertException); |
| Lucene.ExceptionExtensions.NUnitInconclusiveExceptionType = typeof(NUnit.Framework.InconclusiveException); |
| Lucene.ExceptionExtensions.NUnitSuccessExceptionType = typeof(NUnit.Framework.SuccessException); |
| Lucene.ExceptionExtensions.NUnitInvalidPlatformException = Type.GetType("NUnit.Framework.Internal.InvalidPlatformException, NUnit.Framework"); |
| |
| // Identify the Debug.Assert() exception so it can be excluded from being swallowed by catch blocks. |
| // These types are internal, so we can identify them using Reflection. |
| Lucene.ExceptionExtensions.DebugAssertExceptionType = |
| // .NET 5/.NET Core 3.x |
| Type.GetType("System.Diagnostics.DebugProvider+DebugAssertException, System.Private.CoreLib") |
| // .NET Core 2.x |
| ?? Type.GetType("System.Diagnostics.Debug+DebugAssertException, System.Private.CoreLib"); |
| // .NET Framework doesn't throw in this case |
| } |
| |
| /// <summary> |
| /// Checkpoint to allow tests to check the results of <see cref="InitializeStaticState()"/>. |
| /// </summary> |
| internal virtual void AfterInitialization() // Called only by tests |
| { |
| } |
| } |
| } |