| using Lucene.Net.Analysis; |
| using Lucene.Net.Documents; |
| using Lucene.Net.Index; |
| using Lucene.Net.Store; |
| using Lucene.Net.Util; |
| using Lucene.Net.Util.Automaton; |
| using System; |
| using System.Globalization; |
| using System.Text; |
| using Assert = Lucene.Net.TestFramework.Assert; |
| using BitSet = Lucene.Net.Util.OpenBitSet; |
| |
| namespace Lucene.Net.Search |
| { |
| /* |
| * 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> |
| /// Simple base class for checking search equivalence. |
| /// Extend it, and write tests that create <see cref="RandomTerm()"/>s |
| /// (all terms are single characters a-z), and use |
| /// <see cref="AssertSameSet(Query, Query)"/> and |
| /// <see cref="AssertSubsetOf(Query, Query)"/>. |
| /// </summary> |
| public abstract class SearchEquivalenceTestBase : LuceneTestCase |
| #if TESTFRAMEWORK_XUNIT |
| , Xunit.IClassFixture<BeforeAfterClass> |
| { |
| public SearchEquivalenceTestBase(BeforeAfterClass beforeAfter) |
| : base(beforeAfter) |
| { |
| #if !FEATURE_INSTANCE_TESTDATA_INITIALIZATION |
| beforeAfter.SetBeforeAfterClassActions(BeforeClass, AfterClass); |
| #endif |
| } |
| #else |
| { |
| #endif |
| protected static IndexSearcher m_s1, m_s2; |
| protected static Directory m_directory; |
| protected static IndexReader m_reader; |
| protected static Analyzer m_analyzer; |
| protected static string m_stopword; // we always pick a character as a stopword |
| |
| //#if TESTFRAMEWORK_MSTEST |
| // private static readonly IList<string> initalizationLock = new List<string>(); |
| |
| // // 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)] |
| // new public static void BeforeClass(Microsoft.VisualStudio.TestTools.UnitTesting.TestContext context) |
| // { |
| // lock (initalizationLock) |
| // { |
| // if (!initalizationLock.Contains(context.FullyQualifiedTestClassName)) |
| // initalizationLock.Add(context.FullyQualifiedTestClassName); |
| // else |
| // return; // Only allow this class to initialize once (MSTest bug) |
| // } |
| //#else |
| #if TESTFRAMEWORK_NUNIT |
| [NUnit.Framework.OneTimeSetUp] |
| #endif |
| // new public static void BeforeClass() |
| // { |
| //#endif |
| |
| |
| // LUCENENET specific |
| // Is non-static because ClassEnvRule is no longer static. |
| ////[OneTimeSetUp] |
| public override void BeforeClass() |
| { |
| base.BeforeClass(); |
| |
| |
| Random random = Random; |
| m_directory = NewDirectory(); |
| m_stopword = "" + GetRandomChar(); |
| CharacterRunAutomaton stopset = new CharacterRunAutomaton(BasicAutomata.MakeString(m_stopword)); |
| m_analyzer = new MockAnalyzer(random, MockTokenizer.WHITESPACE, false, stopset); |
| RandomIndexWriter iw = new RandomIndexWriter( |
| #if FEATURE_INSTANCE_TESTDATA_INITIALIZATION |
| this, |
| #endif |
| random, m_directory, m_analyzer); |
| Document doc = new Document(); |
| Field id = new StringField("id", "", Field.Store.NO); |
| Field field = new TextField("field", "", Field.Store.NO); |
| doc.Add(id); |
| doc.Add(field); |
| |
| // index some docs |
| int numDocs = AtLeast(1000); |
| for (int i = 0; i < numDocs; i++) |
| { |
| id.SetStringValue(Convert.ToString(i, CultureInfo.InvariantCulture)); |
| field.SetStringValue(RandomFieldContents()); |
| iw.AddDocument(doc); |
| } |
| |
| // delete some docs |
| int numDeletes = numDocs / 20; |
| for (int i = 0; i < numDeletes; i++) |
| { |
| Term toDelete = new Term("id", Convert.ToString(random.Next(numDocs), CultureInfo.InvariantCulture)); |
| if (random.NextBoolean()) |
| { |
| iw.DeleteDocuments(toDelete); |
| } |
| else |
| { |
| iw.DeleteDocuments(new TermQuery(toDelete)); |
| } |
| } |
| |
| m_reader = iw.GetReader(); |
| m_s1 = NewSearcher(m_reader); |
| m_s2 = NewSearcher(m_reader); |
| iw.Dispose(); |
| } |
| |
| |
| //#if TESTFRAMEWORK_MSTEST |
| // // 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)] |
| //# el |
| #if TESTFRAMEWORK_NUNIT |
| [NUnit.Framework.OneTimeTearDown] |
| #endif |
| // new public static void AfterClass() |
| //#else |
| //[OneTimeTearDown] |
| public override void AfterClass() |
| { |
| m_reader.Dispose(); |
| m_directory.Dispose(); |
| m_analyzer.Dispose(); |
| m_reader = null; |
| m_directory = null; |
| m_analyzer = null; |
| m_s1 = m_s2 = null; |
| base.AfterClass(); |
| } |
| |
| /// <summary> |
| /// Populate a field with random contents. |
| /// Terms should be single characters in lowercase (a-z) |
| /// tokenization can be assumed to be on whitespace. |
| /// </summary> |
| internal static string RandomFieldContents() |
| { |
| // TODO: zipf-like distribution |
| StringBuilder sb = new StringBuilder(); |
| int numTerms = Random.Next(15); |
| for (int i = 0; i < numTerms; i++) |
| { |
| if (sb.Length > 0) |
| { |
| sb.Append(' '); // whitespace |
| } |
| sb.Append(GetRandomChar()); |
| } |
| return sb.ToString(); |
| } |
| |
| /// <summary> |
| /// Returns random character (a-z) |
| /// </summary> |
| internal static char GetRandomChar() |
| { |
| return (char)TestUtil.NextInt32(Random, 'a', 'z'); |
| } |
| |
| /// <summary> |
| /// Returns a term suitable for searching. |
| /// Terms are single characters in lowercase (a-z). |
| /// </summary> |
| protected virtual Term RandomTerm() |
| { |
| return new Term("field", "" + GetRandomChar()); |
| } |
| |
| /// <summary> |
| /// Returns a random filter over the document set. |
| /// </summary> |
| protected virtual Filter RandomFilter() |
| { |
| return new QueryWrapperFilter(TermRangeQuery.NewStringRange("field", "a", "" + GetRandomChar(), true, true)); |
| } |
| |
| /// <summary> |
| /// Asserts that the documents returned by <paramref name="q1"/> |
| /// are the same as of those returned by <paramref name="q2"/>. |
| /// </summary> |
| public virtual void AssertSameSet(Query q1, Query q2) |
| { |
| AssertSubsetOf(q1, q2); |
| AssertSubsetOf(q2, q1); |
| } |
| |
| /// <summary> |
| /// Asserts that the documents returned by <paramref name="q1"/> |
| /// are a subset of those returned by <paramref name="q2"/>. |
| /// </summary> |
| public virtual void AssertSubsetOf(Query q1, Query q2) |
| { |
| // test without a filter |
| AssertSubsetOf(q1, q2, null); |
| |
| // test with a filter (this will sometimes cause advance'ing enough to test it) |
| AssertSubsetOf(q1, q2, RandomFilter()); |
| } |
| |
| /// <summary> |
| /// Asserts that the documents returned by <paramref name="q1"/> |
| /// are a subset of those returned by <paramref name="q2"/>. |
| /// <para/> |
| /// Both queries will be filtered by <paramref name="filter"/>. |
| /// </summary> |
| protected virtual void AssertSubsetOf(Query q1, Query q2, Filter filter) |
| { |
| // TRUNK ONLY: test both filter code paths |
| if (filter != null && Random.NextBoolean()) |
| { |
| q1 = new FilteredQuery(q1, filter, TestUtil.RandomFilterStrategy(Random)); |
| q2 = new FilteredQuery(q2, filter, TestUtil.RandomFilterStrategy(Random)); |
| filter = null; |
| } |
| |
| // not efficient, but simple! |
| TopDocs td1 = m_s1.Search(q1, filter, m_reader.MaxDoc); |
| TopDocs td2 = m_s2.Search(q2, filter, m_reader.MaxDoc); |
| Assert.IsTrue(td1.TotalHits <= td2.TotalHits); |
| |
| // fill the superset into a bitset |
| var bitset = new BitSet(td2.ScoreDocs.Length); |
| for (int i = 0; i < td2.ScoreDocs.Length; i++) |
| { |
| bitset.Set(td2.ScoreDocs[i].Doc); |
| } |
| |
| // check in the subset, that every bit was set by the super |
| for (int i = 0; i < td1.ScoreDocs.Length; i++) |
| { |
| Assert.IsTrue(bitset.Get(td1.ScoreDocs[i].Doc)); |
| } |
| } |
| } |
| } |