You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by ni...@apache.org on 2016/11/10 11:33:40 UTC

[29/58] [abbrv] lucenenet git commit: WIP on Grouping

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9d72bcb3/src/Lucene.Net.Tests.Grouping/GroupFacetCollectorTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Grouping/GroupFacetCollectorTest.cs b/src/Lucene.Net.Tests.Grouping/GroupFacetCollectorTest.cs
new file mode 100644
index 0000000..8a01ee0
--- /dev/null
+++ b/src/Lucene.Net.Tests.Grouping/GroupFacetCollectorTest.cs
@@ -0,0 +1,943 @@
+\ufeffusing Lucene.Net.Analysis;
+using Lucene.Net.Documents;
+using Lucene.Net.Search.Grouping.Terms;
+using Lucene.Net.Index;
+using Lucene.Net.Store;
+using Lucene.Net.Support;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+
+namespace Lucene.Net.Search.Grouping
+{
+    public class GroupFacetCollectorTest : AbstractGroupingTestCase
+    {
+        [Test]
+        public void TestSimple()
+        {
+            string groupField = "hotel";
+            FieldType customType = new FieldType();
+            customType.Stored = true;
+
+            Directory dir = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(
+                Random(),
+                dir,
+                NewIndexWriterConfig(TEST_VERSION_CURRENT,
+                    new MockAnalyzer(Random())).SetMergePolicy(NewLogMergePolicy()));
+            bool canUseDV = !"Lucene3x".equals(w.w.Config.Codec.Name);
+            bool useDv = canUseDV && Random().nextBoolean();
+
+            // 0
+            Document doc = new Document();
+            AddField(doc, groupField, "a", useDv);
+            AddField(doc, "airport", "ams", useDv);
+            AddField(doc, "duration", "5", useDv);
+            w.AddDocument(doc);
+
+            // 1
+            doc = new Document();
+            AddField(doc, groupField, "a", useDv);
+            AddField(doc, "airport", "dus", useDv);
+            AddField(doc, "duration", "10", useDv);
+            w.AddDocument(doc);
+
+            // 2
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            AddField(doc, "airport", "ams", useDv);
+            AddField(doc, "duration", "10", useDv);
+            w.AddDocument(doc);
+            w.Commit(); // To ensure a second segment
+
+            // 3
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            AddField(doc, "airport", "ams", useDv);
+            AddField(doc, "duration", "5", useDv);
+            w.AddDocument(doc);
+
+            // 4
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            AddField(doc, "airport", "ams", useDv);
+            AddField(doc, "duration", "5", useDv);
+            w.AddDocument(doc);
+
+            IndexSearcher indexSearcher = NewSearcher(w.Reader);
+
+            List<TermGroupFacetCollector.FacetEntry> entries = null;
+            AbstractGroupFacetCollector groupedAirportFacetCollector = null;
+            TermGroupFacetCollector.GroupedFacetResult airportResult = null;
+
+            foreach (int limit in new int[] { 2, 10, 100, int.MaxValue
+    })
+            {
+                // any of these limits is plenty for the data we have
+
+                groupedAirportFacetCollector = CreateRandomCollector
+                  (useDv ? "hotel_dv" : "hotel",
+                   useDv ? "airport_dv" : "airport", null, false);
+                indexSearcher.Search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
+                int maxOffset = 5;
+                airportResult = groupedAirportFacetCollector.MergeSegmentResults
+                    (int.MaxValue == limit ? limit : maxOffset + limit, 0, false);
+
+
+                assertEquals(3, airportResult.TotalCount);
+                assertEquals(0, airportResult.TotalMissingCount);
+
+                entries = airportResult.GetFacetEntries(maxOffset, limit);
+                assertEquals(0, entries.size());
+
+                entries = airportResult.GetFacetEntries(0, limit);
+                assertEquals(2, entries.size());
+                assertEquals("ams", entries[0].Value.Utf8ToString());
+                assertEquals(2, entries[0].Count);
+                assertEquals("dus", entries[1].Value.Utf8ToString());
+                assertEquals(1, entries[1].Count);
+
+                entries = airportResult.GetFacetEntries(1, limit);
+                assertEquals(1, entries.size());
+                assertEquals("dus", entries[0].Value.Utf8ToString());
+                assertEquals(1, entries[0].Count);
+            }
+
+            AbstractGroupFacetCollector groupedDurationFacetCollector = CreateRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", null, false);
+            indexSearcher.Search(new MatchAllDocsQuery(), groupedDurationFacetCollector);
+            TermGroupFacetCollector.GroupedFacetResult durationResult = groupedDurationFacetCollector.MergeSegmentResults(10, 0, false);
+            assertEquals(4, durationResult.TotalCount);
+            assertEquals(0, durationResult.TotalMissingCount);
+
+            entries = durationResult.GetFacetEntries(0, 10);
+            assertEquals(2, entries.size());
+            assertEquals("10", entries[0].Value.Utf8ToString());
+            assertEquals(2, entries[0].Count);
+            assertEquals("5", entries[1].Value.Utf8ToString());
+            assertEquals(2, entries[1].Count);
+
+            // 5
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            // missing airport
+            if (useDv)
+            {
+                AddField(doc, "airport", "", useDv);
+            }
+            AddField(doc, "duration", "5", useDv);
+            w.AddDocument(doc);
+
+            // 6
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            AddField(doc, "airport", "bru", useDv);
+            AddField(doc, "duration", "10", useDv);
+            w.AddDocument(doc);
+
+            // 7
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            AddField(doc, "airport", "bru", useDv);
+            AddField(doc, "duration", "15", useDv);
+            w.AddDocument(doc);
+
+            // 8
+            doc = new Document();
+            AddField(doc, groupField, "a", useDv);
+            AddField(doc, "airport", "bru", useDv);
+            AddField(doc, "duration", "10", useDv);
+            w.AddDocument(doc);
+
+            indexSearcher.IndexReader.Dispose();
+            indexSearcher = NewSearcher(w.Reader);
+            groupedAirportFacetCollector = CreateRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "airport_dv" : "airport", null, !useDv);
+            indexSearcher.Search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
+            airportResult = groupedAirportFacetCollector.MergeSegmentResults(3, 0, true);
+            entries = airportResult.GetFacetEntries(1, 2);
+            assertEquals(2, entries.size());
+            if (useDv)
+            {
+                assertEquals(6, airportResult.TotalCount);
+                assertEquals(0, airportResult.TotalMissingCount);
+                assertEquals("bru", entries[0].Value.Utf8ToString());
+                assertEquals(2, entries[0].Count);
+                assertEquals("", entries[1].Value.Utf8ToString());
+                assertEquals(1, entries[1].Count);
+            }
+            else
+            {
+                assertEquals(5, airportResult.TotalCount);
+                assertEquals(1, airportResult.TotalMissingCount);
+                assertEquals("bru", entries[0].Value.Utf8ToString());
+                assertEquals(2, entries[0].Count);
+                assertEquals("dus", entries[1].Value.Utf8ToString());
+                assertEquals(1, entries[1].Count);
+            }
+
+            groupedDurationFacetCollector = CreateRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", null, false);
+            indexSearcher.Search(new MatchAllDocsQuery(), groupedDurationFacetCollector);
+            durationResult = groupedDurationFacetCollector.MergeSegmentResults(10, 2, true);
+            assertEquals(5, durationResult.TotalCount);
+            assertEquals(0, durationResult.TotalMissingCount);
+
+            entries = durationResult.GetFacetEntries(1, 1);
+            assertEquals(1, entries.size());
+            assertEquals("5", entries[0].Value.Utf8ToString());
+            assertEquals(2, entries[0].Count);
+
+            // 9
+            doc = new Document();
+            AddField(doc, groupField, "c", useDv);
+            AddField(doc, "airport", "bru", useDv);
+            AddField(doc, "duration", "15", useDv);
+            w.AddDocument(doc);
+
+            // 10
+            doc = new Document();
+            AddField(doc, groupField, "c", useDv);
+            AddField(doc, "airport", "dus", useDv);
+            AddField(doc, "duration", "10", useDv);
+            w.AddDocument(doc);
+
+            indexSearcher.IndexReader.Dispose();
+            indexSearcher = NewSearcher(w.Reader);
+            groupedAirportFacetCollector = CreateRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "airport_dv" : "airport", null, false);
+            indexSearcher.Search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
+            airportResult = groupedAirportFacetCollector.MergeSegmentResults(10, 0, false);
+            entries = airportResult.GetFacetEntries(0, 10);
+            if (useDv)
+            {
+                assertEquals(8, airportResult.TotalCount);
+                assertEquals(0, airportResult.TotalMissingCount);
+                assertEquals(4, entries.size());
+                assertEquals("", entries[0].Value.Utf8ToString());
+                assertEquals(1, entries[0].Count);
+                assertEquals("ams", entries[1].Value.Utf8ToString());
+                assertEquals(2, entries[1].Count);
+                assertEquals("bru", entries[2].Value.Utf8ToString());
+                assertEquals(3, entries[2].Count);
+                assertEquals("dus", entries[3].Value.Utf8ToString());
+                assertEquals(2, entries[3].Count);
+            }
+            else
+            {
+                assertEquals(7, airportResult.TotalCount);
+                assertEquals(1, airportResult.TotalMissingCount);
+                assertEquals(3, entries.size());
+                assertEquals("ams", entries[0].Value.Utf8ToString());
+                assertEquals(2, entries[0].Count);
+                assertEquals("bru", entries[1].Value.Utf8ToString());
+                assertEquals(3, entries[1].Count);
+                assertEquals("dus", entries[2].Value.Utf8ToString());
+                assertEquals(2, entries[2].Count);
+            }
+
+            groupedDurationFacetCollector = CreateRandomCollector(useDv ? "hotel_dv" : "hotel", useDv ? "duration_dv" : "duration", "1", false);
+            indexSearcher.Search(new MatchAllDocsQuery(), groupedDurationFacetCollector);
+            durationResult = groupedDurationFacetCollector.MergeSegmentResults(10, 0, true);
+            assertEquals(5, durationResult.TotalCount);
+            assertEquals(0, durationResult.TotalMissingCount);
+
+            entries = durationResult.GetFacetEntries(0, 10);
+            assertEquals(2, entries.size());
+            assertEquals("10", entries[0].Value.Utf8ToString());
+            assertEquals(3, entries[0].Count);
+            assertEquals("15", entries[1].Value.Utf8ToString());
+            assertEquals(2, entries[1].Count);
+
+            w.Dispose();
+            indexSearcher.IndexReader.Dispose();
+            dir.Dispose();
+        }
+
+        [Test]
+        public void TestMVGroupedFacetingWithDeletes()
+        {
+            string groupField = "hotel";
+            FieldType customType = new FieldType();
+            customType.Stored = (true);
+
+            Directory dir = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(
+                Random(),
+                dir,
+                NewIndexWriterConfig(TEST_VERSION_CURRENT,
+                    new MockAnalyzer(Random())).SetMergePolicy(NoMergePolicy.COMPOUND_FILES));
+            bool useDv = false;
+
+            // Cannot assert this since we use NoMergePolicy:
+            w.DoRandomForceMergeAssert = (false);
+
+            // 0
+            Document doc = new Document();
+            doc.Add(new StringField("x", "x", Field.Store.NO));
+            w.AddDocument(doc);
+
+            // 1
+            doc = new Document();
+            AddField(doc, groupField, "a", useDv);
+            doc.Add(new StringField("airport", "ams", Field.Store.NO));
+            w.AddDocument(doc);
+
+            w.Commit();
+            w.DeleteDocuments(new TermQuery(new Term("airport", "ams")));
+
+            // 2
+            doc = new Document();
+            AddField(doc, groupField, "a", useDv);
+            doc.Add(new StringField("airport", "ams", Field.Store.NO));
+            w.AddDocument(doc);
+
+            // 3
+            doc = new Document();
+            AddField(doc, groupField, "a", useDv);
+            doc.Add(new StringField("airport", "dus", Field.Store.NO));
+
+            w.AddDocument(doc);
+
+            // 4
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            doc.Add(new StringField("airport", "ams", Field.Store.NO));
+            w.AddDocument(doc);
+
+            // 5
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            doc.Add(new StringField("airport", "ams", Field.Store.NO));
+            w.AddDocument(doc);
+
+            // 6
+            doc = new Document();
+            AddField(doc, groupField, "b", useDv);
+            doc.Add(new StringField("airport", "ams", Field.Store.NO));
+            w.AddDocument(doc);
+            w.Commit();
+
+            // 7
+            doc = new Document();
+            doc.Add(new StringField("x", "x", Field.Store.NO));
+            w.AddDocument(doc);
+            w.Commit();
+
+            w.Dispose();
+            IndexSearcher indexSearcher = NewSearcher(DirectoryReader.Open(dir));
+            AbstractGroupFacetCollector groupedAirportFacetCollector = CreateRandomCollector(groupField, "airport", null, true);
+            indexSearcher.Search(new MatchAllDocsQuery(), groupedAirportFacetCollector);
+            TermGroupFacetCollector.GroupedFacetResult airportResult = groupedAirportFacetCollector.MergeSegmentResults(10, 0, false);
+            assertEquals(3, airportResult.TotalCount);
+            assertEquals(1, airportResult.TotalMissingCount);
+
+            List<TermGroupFacetCollector.FacetEntry> entries = airportResult.GetFacetEntries(0, 10);
+            assertEquals(2, entries.size());
+            assertEquals("ams", entries[0].Value.Utf8ToString());
+            assertEquals(2, entries[0].Count);
+            assertEquals("dus", entries[1].Value.Utf8ToString());
+            assertEquals(1, entries[1].Count);
+
+            indexSearcher.IndexReader.Dispose();
+            dir.Dispose();
+        }
+
+        private void AddField(Document doc, string field, string value, bool canUseIDV)
+        {
+            doc.Add(new StringField(field, value, Field.Store.NO));
+            if (canUseIDV)
+            {
+                doc.Add(new SortedDocValuesField(field + "_dv", new BytesRef(value)));
+            }
+        }
+
+        [Test]
+        public void TestRandom()
+        {
+            Random random = Random();
+            int numberOfRuns = TestUtil.NextInt(random, 3, 6);
+            for (int indexIter = 0; indexIter < numberOfRuns; indexIter++)
+            {
+                bool multipleFacetsPerDocument = random.nextBoolean();
+                IndexContext context = CreateIndexContext(multipleFacetsPerDocument);
+                IndexSearcher searcher = NewSearcher(context.indexReader);
+
+                if (VERBOSE)
+                {
+                    Console.WriteLine("TEST: searcher=" + searcher);
+                }
+
+                for (int searchIter = 0; searchIter < 100; searchIter++)
+                {
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("TEST: searchIter=" + searchIter);
+                    }
+                    bool useDv = !multipleFacetsPerDocument && context.useDV && random.nextBoolean();
+                    string searchTerm = context.contentStrings[random.nextInt(context.contentStrings.Length)];
+                    int limit = random.nextInt(context.facetValues.size());
+                    int offset = random.nextInt(context.facetValues.size() - limit);
+                    int size = offset + limit;
+                    int minCount = random.nextBoolean() ? 0 : random.nextInt(1 + context.facetWithMostGroups / 10);
+                    bool orderByCount = random.nextBoolean();
+                    string randomStr = GetFromSet(context.facetValues, random.nextInt(context.facetValues.size()));
+                    string facetPrefix;
+                    if (randomStr == null)
+                    {
+                        facetPrefix = null;
+                    }
+                    else
+                    {
+                        int codePointLen = randomStr.CodePointCount(0, randomStr.Length);
+                        int randomLen = random.nextInt(codePointLen);
+                        if (codePointLen == randomLen - 1)
+                        {
+                            facetPrefix = null;
+                        }
+                        else
+                        {
+                            int end = randomStr.OffsetByCodePoints(0, randomLen);
+                            facetPrefix = random.nextBoolean() ? null : randomStr.Substring(end);
+                        }
+                    }
+
+                    GroupedFacetResult expectedFacetResult = CreateExpectedFacetResult(searchTerm, context, offset, limit, minCount, orderByCount, facetPrefix);
+                    AbstractGroupFacetCollector groupFacetCollector = CreateRandomCollector(useDv ? "group_dv" : "group", useDv ? "facet_dv" : "facet", facetPrefix, multipleFacetsPerDocument);
+                    searcher.Search(new TermQuery(new Term("content", searchTerm)), groupFacetCollector);
+                    TermGroupFacetCollector.GroupedFacetResult actualFacetResult = groupFacetCollector.MergeSegmentResults(size, minCount, orderByCount);
+
+                    List<TermGroupFacetCollector.FacetEntry> expectedFacetEntries = expectedFacetResult.getFacetEntries();
+                    List<TermGroupFacetCollector.FacetEntry> actualFacetEntries = actualFacetResult.GetFacetEntries(offset, limit);
+
+                    if (VERBOSE)
+                    {
+                        Console.WriteLine("Use DV: " + useDv);
+                        Console.WriteLine("Collector: " + groupFacetCollector.GetType().Name);
+                        Console.WriteLine("Num group: " + context.numGroups);
+                        Console.WriteLine("Num doc: " + context.numDocs);
+                        Console.WriteLine("Index iter: " + indexIter);
+                        Console.WriteLine("multipleFacetsPerDocument: " + multipleFacetsPerDocument);
+                        Console.WriteLine("Search iter: " + searchIter);
+
+                        Console.WriteLine("Search term: " + searchTerm);
+                        Console.WriteLine("Min count: " + minCount);
+                        Console.WriteLine("Facet offset: " + offset);
+                        Console.WriteLine("Facet limit: " + limit);
+                        Console.WriteLine("Facet prefix: " + facetPrefix);
+                        Console.WriteLine("Order by count: " + orderByCount);
+
+                        Console.WriteLine("\n=== Expected: \n");
+                        Console.WriteLine("Total count " + expectedFacetResult.getTotalCount());
+                        Console.WriteLine("Total missing count " + expectedFacetResult.getTotalMissingCount());
+                        int counter = 0;
+                        foreach (TermGroupFacetCollector.FacetEntry expectedFacetEntry in expectedFacetEntries)
+                        {
+                            Console.WriteLine(
+                                string.Format(CultureInfo.InvariantCulture,
+                                    "{0}. Expected facet value {1} with count {2}",
+                                    counter++, expectedFacetEntry.Value.Utf8ToString(), expectedFacetEntry.Count
+                                )
+                            );
+                        }
+
+                        Console.WriteLine("\n=== Actual: \n");
+                        Console.WriteLine("Total count " + actualFacetResult.TotalCount);
+                        Console.WriteLine("Total missing count " + actualFacetResult.TotalMissingCount);
+                        counter = 0;
+                        foreach (TermGroupFacetCollector.FacetEntry actualFacetEntry in actualFacetEntries)
+                        {
+                            Console.WriteLine(
+                                string.Format(CultureInfo.InvariantCulture,
+                                    "{0}. Actual facet value {1} with count {2}",
+                                    counter++, actualFacetEntry.Value.Utf8ToString(), actualFacetEntry.Count
+                                )
+                            );
+                        }
+                        Console.WriteLine("\n===================================================================================");
+                    }
+
+                    assertEquals(expectedFacetResult.getTotalCount(), actualFacetResult.TotalCount);
+                    assertEquals(expectedFacetResult.getTotalMissingCount(), actualFacetResult.TotalMissingCount);
+                    assertEquals(expectedFacetEntries.size(), actualFacetEntries.size());
+                    for (int i = 0; i < expectedFacetEntries.size(); i++)
+                    {
+                        TermGroupFacetCollector.FacetEntry expectedFacetEntry = expectedFacetEntries[i];
+                        TermGroupFacetCollector.FacetEntry actualFacetEntry = actualFacetEntries[i];
+                        assertEquals("i=" + i + ": " + expectedFacetEntry.Value.Utf8ToString() + " != " + actualFacetEntry.Value.Utf8ToString(), expectedFacetEntry.Value, actualFacetEntry.Value);
+                        assertEquals("i=" + i + ": " + expectedFacetEntry.Count + " != " + actualFacetEntry.Count, expectedFacetEntry.Count, actualFacetEntry.Count);
+                    }
+                }
+
+                context.indexReader.Dispose();
+                context.dir.Dispose();
+            }
+        }
+
+        internal class ComparerAnonymousHelper1 : IComparer<string>
+        {
+            public int Compare(string a, string b)
+            {
+                if (a == b)
+                {
+                    return 0;
+                }
+                else if (a == null)
+                {
+                    return -1;
+                }
+                else if (b == null)
+                {
+                    return 1;
+                }
+                else
+                {
+                    return a.CompareToOrdinal(b);
+                }
+            }
+        }
+
+        private IndexContext CreateIndexContext(bool multipleFacetValuesPerDocument)
+        {
+            Random random = Random();
+            int numDocs = TestUtil.NextInt(random, 138, 1145) * RANDOM_MULTIPLIER;
+            int numGroups = TestUtil.NextInt(random, 1, numDocs / 4);
+            int numFacets = TestUtil.NextInt(random, 1, numDocs / 6);
+
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: numDocs=" + numDocs + " numGroups=" + numGroups);
+            }
+
+            List<string> groups = new List<string>();
+            for (int i = 0; i < numGroups; i++)
+            {
+                groups.Add(GenerateRandomNonEmptyString());
+            }
+            List<string> facetValues = new List<string>();
+            for (int i = 0; i < numFacets; i++)
+            {
+                facetValues.Add(GenerateRandomNonEmptyString());
+            }
+            string[] contentBrs = new string[TestUtil.NextInt(random, 2, 20)];
+            if (VERBOSE)
+            {
+                Console.WriteLine("TEST: create fake content");
+            }
+            for (int contentIDX = 0; contentIDX < contentBrs.Length; contentIDX++)
+            {
+                contentBrs[contentIDX] = GenerateRandomNonEmptyString();
+                if (VERBOSE)
+                {
+                    Console.WriteLine("  content=" + contentBrs[contentIDX]);
+                }
+            }
+
+            Directory dir = NewDirectory();
+            RandomIndexWriter writer = new RandomIndexWriter(
+                random,
+                dir,
+                NewIndexWriterConfig(
+                    TEST_VERSION_CURRENT,
+                    new MockAnalyzer(random)
+                )
+            );
+            bool canUseDV = !"Lucene3x".equals(writer.w.Config.Codec.Name);
+            bool useDv = canUseDV && !multipleFacetValuesPerDocument && random.nextBoolean();
+
+            Document doc = new Document();
+            Document docNoGroup = new Document();
+            Document docNoFacet = new Document();
+            Document docNoGroupNoFacet = new Document();
+            Field group = NewStringField("group", "", Field.Store.NO);
+            Field groupDc = new SortedDocValuesField("group_dv", new BytesRef());
+            if (useDv)
+            {
+                doc.Add(groupDc);
+                docNoFacet.Add(groupDc);
+            }
+            doc.Add(group);
+            docNoFacet.Add(group);
+            Field[] facetFields;
+            if (useDv)
+            {
+                Debug.Assert(!multipleFacetValuesPerDocument);
+                facetFields = new Field[2];
+                facetFields[0] = NewStringField("facet", "", Field.Store.NO);
+                doc.Add(facetFields[0]);
+                docNoGroup.Add(facetFields[0]);
+                facetFields[1] = new SortedDocValuesField("facet_dv", new BytesRef());
+                doc.Add(facetFields[1]);
+                docNoGroup.Add(facetFields[1]);
+            }
+            else
+            {
+                facetFields = multipleFacetValuesPerDocument ? new Field[2 + random.nextInt(6)] : new Field[1];
+                for (int i = 0; i < facetFields.Length; i++)
+                {
+                    facetFields[i] = NewStringField("facet", "", Field.Store.NO);
+                    doc.Add(facetFields[i]);
+                    docNoGroup.Add(facetFields[i]);
+                }
+            }
+            Field content = NewStringField("content", "", Field.Store.NO);
+            doc.Add(content);
+            docNoGroup.Add(content);
+            docNoFacet.Add(content);
+            docNoGroupNoFacet.Add(content);
+
+            ISet<string> uniqueFacetValues = new SortedSet<string>(new ComparerAnonymousHelper1());
+            //    ISet<string> uniqueFacetValues = new SortedSet<string>(new Comparator<String>() {
+
+            //      @Override
+            //      public int compare(String a, String b)
+            //{
+            //    if (a == b)
+            //    {
+            //        return 0;
+            //    }
+            //    else if (a == null)
+            //    {
+            //        return -1;
+            //    }
+            //    else if (b == null)
+            //    {
+            //        return 1;
+            //    }
+            //    else
+            //    {
+            //        return a.compareTo(b);
+            //    }
+            //}
+
+            //    });
+            // LUCENENET NOTE: Need HashMap here because of null keys
+            IDictionary<string, HashMap<string, ISet<string>>> searchTermToFacetToGroups = new Dictionary<string, HashMap<string, ISet<string>>>();
+            int facetWithMostGroups = 0;
+            for (int i = 0; i < numDocs; i++)
+            {
+                string groupValue;
+                if (random.nextInt(24) == 17)
+                {
+                    // So we test the "doc doesn't have the group'd
+                    // field" case:
+                    if (useDv)
+                    {
+                        groupValue = "";
+                    }
+                    else
+                    {
+                        groupValue = null;
+                    }
+                }
+                else
+                {
+                    groupValue = groups[random.nextInt(groups.size())];
+                }
+
+                string contentStr = contentBrs[random.nextInt(contentBrs.Length)];
+                if (!searchTermToFacetToGroups.ContainsKey(contentStr))
+                {
+                    searchTermToFacetToGroups[contentStr] = new HashMap<string, ISet<string>>();
+                }
+                IDictionary<string, ISet<string>> facetToGroups = searchTermToFacetToGroups[contentStr];
+
+                List<string> facetVals = new List<string>();
+                if (useDv || random.nextInt(24) != 18)
+                {
+                    if (useDv)
+                    {
+                        String facetValue = facetValues[random.nextInt(facetValues.size())];
+                        uniqueFacetValues.add(facetValue);
+                        if (!facetToGroups.ContainsKey(facetValue))
+                        {
+                            facetToGroups[facetValue] = new HashSet<string>();
+                        }
+                        ISet<string> groupsInFacet = facetToGroups[facetValue];
+                        groupsInFacet.add(groupValue);
+                        if (groupsInFacet.size() > facetWithMostGroups)
+                        {
+                            facetWithMostGroups = groupsInFacet.size();
+                        }
+                        facetFields[0].StringValue = (facetValue);
+                        facetFields[1].BytesValue = (new BytesRef(facetValue));
+                        facetVals.Add(facetValue);
+                    }
+                    else
+                    {
+                        foreach (Field facetField in facetFields)
+                        {
+                            string facetValue = facetValues[random.nextInt(facetValues.size())];
+                            uniqueFacetValues.add(facetValue);
+                            if (!facetToGroups.ContainsKey(facetValue))
+                            {
+                                facetToGroups[facetValue] = new HashSet<string>();
+                            }
+                            ISet<string> groupsInFacet = facetToGroups[facetValue];
+                            groupsInFacet.add(groupValue);
+                            if (groupsInFacet.size() > facetWithMostGroups)
+                            {
+                                facetWithMostGroups = groupsInFacet.size();
+                            }
+                            facetField.StringValue = (facetValue);
+                            facetVals.Add(facetValue);
+                        }
+                    }
+                }
+                else
+                {
+                    uniqueFacetValues.add(null);
+                    if (!facetToGroups.ContainsKey(null))
+                    {
+                        facetToGroups.Put(null, new HashSet<string>());
+                    }
+                    ISet<string> groupsInFacet = facetToGroups[null];
+                    groupsInFacet.add(groupValue);
+                    if (groupsInFacet.size() > facetWithMostGroups)
+                    {
+                        facetWithMostGroups = groupsInFacet.size();
+                    }
+                }
+
+                if (VERBOSE)
+                {
+                    Console.WriteLine("  doc content=" + contentStr + " group=" + (groupValue == null ? "null" : groupValue) + " facetVals=" + facetVals);
+                }
+
+                if (groupValue != null)
+                {
+                    if (useDv)
+                    {
+                        groupDc.BytesValue = (new BytesRef(groupValue));
+                    }
+                    group.StringValue = (groupValue);
+                }
+                else if (useDv)
+                {
+                    // DV cannot have missing values:
+                    groupDc.BytesValue = (new BytesRef());
+                }
+                content.StringValue = (contentStr);
+                if (groupValue == null && !facetVals.Any())
+                {
+                    writer.AddDocument(docNoGroupNoFacet);
+                }
+                else if (!facetVals.Any())
+                {
+                    writer.AddDocument(docNoFacet);
+                }
+                else if (groupValue == null)
+                {
+                    writer.AddDocument(docNoGroup);
+                }
+                else
+                {
+                    writer.AddDocument(doc);
+                }
+            }
+
+            DirectoryReader reader = writer.Reader;
+            writer.Dispose();
+
+            return new IndexContext(searchTermToFacetToGroups, reader, numDocs, dir, facetWithMostGroups, numGroups, contentBrs, uniqueFacetValues, useDv);
+        }
+
+        internal class ComparerAnonymousHelper2 : IComparer<TermGroupFacetCollector.FacetEntry>
+        {
+            private readonly bool orderByCount;
+
+            public ComparerAnonymousHelper2(bool orderByCount)
+            {
+                this.orderByCount = orderByCount;
+            }
+
+            public int Compare(AbstractGroupFacetCollector.FacetEntry a, AbstractGroupFacetCollector.FacetEntry b)
+            {
+                if (orderByCount)
+                {
+                    int cmp = b.Count - a.Count;
+                    if (cmp != 0)
+                    {
+                        return cmp;
+                    }
+                }
+                return a.Value.CompareTo(b.Value);
+            }
+        }
+
+        private GroupedFacetResult CreateExpectedFacetResult(string searchTerm, IndexContext context, int offset, int limit, int minCount, bool orderByCount, string facetPrefix)
+        {
+            //IDictionary<string, ISet<string>> facetGroups = context.searchTermToFacetGroups.get(searchTerm);
+            //if (facetGroups == null)
+            HashMap<string, ISet<string>> facetGroups;
+            if (!context.searchTermToFacetGroups.TryGetValue(searchTerm, out facetGroups))
+            {
+                facetGroups = new HashMap<string, ISet<string>>();
+            }
+
+            int totalCount = 0;
+            int totalMissCount = 0;
+            ISet<string> facetValues;
+            if (facetPrefix != null)
+            {
+                facetValues = new HashSet<string>();
+                foreach (string facetValue in context.facetValues)
+                {
+                    if (facetValue != null && facetValue.StartsWith(facetPrefix))
+                    {
+                        facetValues.add(facetValue);
+                    }
+                }
+            }
+            else
+            {
+                facetValues = context.facetValues;
+            }
+
+            List<TermGroupFacetCollector.FacetEntry> entries = new List<TermGroupFacetCollector.FacetEntry>(facetGroups.size());
+            // also includes facets with count 0
+            foreach (string facetValue in facetValues)
+            {
+                if (facetValue == null)
+                {
+                    continue;
+                }
+
+                ISet<string> groups = facetGroups.ContainsKey(facetValue) ? facetGroups[facetValue] : null;
+                int count = groups != null ? groups.size() : 0;
+                if (count >= minCount)
+                {
+                    entries.Add(new TermGroupFacetCollector.FacetEntry(new BytesRef(facetValue), count));
+                }
+                totalCount += count;
+            }
+
+            // Only include null count when no facet prefix is specified
+            if (facetPrefix == null)
+            {
+                ISet<string> groups = facetGroups[null];
+                if (groups != null)
+                {
+                    totalMissCount = groups.size();
+                }
+            }
+
+            entries.Sort(new ComparerAnonymousHelper2(orderByCount));
+
+            //    Collections.sort(entries, new Comparator<TermGroupFacetCollector.FacetEntry>() {
+
+            //      @Override
+            //      public int compare(TermGroupFacetCollector.FacetEntry a, TermGroupFacetCollector.FacetEntry b)
+            //{
+            //    if (orderByCount)
+            //    {
+            //        int cmp = b.getCount() - a.getCount();
+            //        if (cmp != 0)
+            //        {
+            //            return cmp;
+            //        }
+            //    }
+            //    return a.getValue().compareTo(b.getValue());
+            //}
+
+            //    });
+
+            int endOffset = offset + limit;
+            List<TermGroupFacetCollector.FacetEntry> entriesResult;
+            if (offset >= entries.size())
+            {
+                entriesResult = new List<TermGroupFacetCollector.FacetEntry>();
+            }
+            else if (endOffset >= entries.size())
+            {
+                //entriesResult = (List<TermGroupFacetCollector.FacetEntry>)entries.SubList(offset, entries.size());
+                entriesResult = entries.GetRange(offset, entries.size() - offset);
+            }
+            else
+            {
+                //entriesResult = (List<TermGroupFacetCollector.FacetEntry>)entries.SubList(offset, endOffset);
+                entriesResult = entries.GetRange(offset, endOffset - offset);
+            }
+            return new GroupedFacetResult(totalCount, totalMissCount, entriesResult);
+        }
+
+        private AbstractGroupFacetCollector CreateRandomCollector(string groupField, string facetField, string facetPrefix, bool multipleFacetsPerDocument)
+        {
+            BytesRef facetPrefixBR = facetPrefix == null ? null : new BytesRef(facetPrefix);
+            // DocValues cannot be multi-valued:
+            Debug.Assert(!multipleFacetsPerDocument || !groupField.EndsWith("_dv"));
+            return TermGroupFacetCollector.CreateTermGroupFacetCollector(groupField, facetField, multipleFacetsPerDocument, facetPrefixBR, Random().nextInt(1024));
+        }
+
+        private string GetFromSet(ISet<string> set, int index)
+        {
+            int currentIndex = 0;
+            foreach (string bytesRef in set)
+            {
+                if (currentIndex++ == index)
+                {
+                    return bytesRef;
+                }
+            }
+
+            return null;
+        }
+
+        internal class IndexContext
+        {
+            internal readonly int numDocs;
+            internal readonly DirectoryReader indexReader;
+            internal readonly IDictionary<string, HashMap<string, ISet<string>>> searchTermToFacetGroups;
+            internal readonly ISet<string> facetValues;
+            internal readonly Directory dir;
+            internal readonly int facetWithMostGroups;
+            internal readonly int numGroups;
+            internal readonly string[] contentStrings;
+            internal readonly bool useDV;
+
+            public IndexContext(IDictionary<string, HashMap<string, ISet<string>>> searchTermToFacetGroups, DirectoryReader r,
+                                int numDocs, Directory dir, int facetWithMostGroups, int numGroups, string[] contentStrings, ISet<string> facetValues, bool useDV)
+            {
+                this.searchTermToFacetGroups = searchTermToFacetGroups;
+                this.indexReader = r;
+                this.numDocs = numDocs;
+                this.dir = dir;
+                this.facetWithMostGroups = facetWithMostGroups;
+                this.numGroups = numGroups;
+                this.contentStrings = contentStrings;
+                this.facetValues = facetValues;
+                this.useDV = useDV;
+            }
+        }
+
+        internal class GroupedFacetResult
+        {
+
+            internal readonly int totalCount;
+            internal readonly int totalMissingCount;
+            internal readonly List<TermGroupFacetCollector.FacetEntry> facetEntries;
+
+            internal GroupedFacetResult(int totalCount, int totalMissingCount, List<TermGroupFacetCollector.FacetEntry> facetEntries)
+            {
+                this.totalCount = totalCount;
+                this.totalMissingCount = totalMissingCount;
+                this.facetEntries = facetEntries;
+            }
+
+            public int getTotalCount()
+            {
+                return totalCount;
+            }
+
+            public int getTotalMissingCount()
+            {
+                return totalMissingCount;
+            }
+
+            public List<TermGroupFacetCollector.FacetEntry> getFacetEntries()
+            {
+                return facetEntries;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9d72bcb3/src/Lucene.Net.Tests.Grouping/GroupingSearchTest.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Grouping/GroupingSearchTest.cs b/src/Lucene.Net.Tests.Grouping/GroupingSearchTest.cs
new file mode 100644
index 0000000..8f49614
--- /dev/null
+++ b/src/Lucene.Net.Tests.Grouping/GroupingSearchTest.cs
@@ -0,0 +1,245 @@
+\ufeffusing Lucene.Net.Analysis;
+using Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.Queries.Function;
+using Lucene.Net.Queries.Function.ValueSources;
+using Lucene.Net.Store;
+using Lucene.Net.Util;
+using Lucene.Net.Util.Mutable;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Lucene.Net.Search.Grouping
+{
+    public class GroupingSearchTest : LuceneTestCase
+    {
+        // Tests some very basic usages...
+        [Test]
+        public void testBasic()
+        {
+
+            string groupField = "author";
+
+            FieldType customType = new FieldType();
+            customType.Stored = (true);
+
+            Directory dir = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(
+                Random(),
+                dir,
+                NewIndexWriterConfig(TEST_VERSION_CURRENT,
+                    new MockAnalyzer(Random())).SetMergePolicy(NewLogMergePolicy()));
+            bool canUseIDV = !"Lucene3x".equals(w.w.Config.Codec.Name);
+            List<Document> documents = new List<Document>();
+            // 0
+            Document doc = new Document();
+            addGroupField(doc, groupField, "author1", canUseIDV);
+            doc.Add(new TextField("content", "random text", Field.Store.YES));
+            doc.Add(new Field("id", "1", customType));
+            documents.Add(doc);
+
+            // 1
+            doc = new Document();
+            addGroupField(doc, groupField, "author1", canUseIDV);
+            doc.Add(new TextField("content", "some more random text", Field.Store.YES));
+            doc.Add(new Field("id", "2", customType));
+            documents.Add(doc);
+
+            // 2
+            doc = new Document();
+            addGroupField(doc, groupField, "author1", canUseIDV);
+            doc.Add(new TextField("content", "some more random textual data", Field.Store.YES));
+            doc.Add(new Field("id", "3", customType));
+            doc.Add(new StringField("groupend", "x", Field.Store.NO));
+            documents.Add(doc);
+            w.AddDocuments(documents);
+            documents.Clear();
+
+            // 3
+            doc = new Document();
+            addGroupField(doc, groupField, "author2", canUseIDV);
+            doc.Add(new TextField("content", "some random text", Field.Store.YES));
+            doc.Add(new Field("id", "4", customType));
+            doc.Add(new StringField("groupend", "x", Field.Store.NO));
+            w.AddDocument(doc);
+
+            // 4
+            doc = new Document();
+            addGroupField(doc, groupField, "author3", canUseIDV);
+            doc.Add(new TextField("content", "some more random text", Field.Store.YES));
+            doc.Add(new Field("id", "5", customType));
+            documents.Add(doc);
+
+            // 5
+            doc = new Document();
+            addGroupField(doc, groupField, "author3", canUseIDV);
+            doc.Add(new TextField("content", "random", Field.Store.YES));
+            doc.Add(new Field("id", "6", customType));
+            doc.Add(new StringField("groupend", "x", Field.Store.NO));
+            documents.Add(doc);
+            w.AddDocuments(documents);
+            documents.Clear();
+
+            // 6 -- no author field
+            doc = new Document();
+            doc.Add(new TextField("content", "random word stuck in alot of other text", Field.Store.YES));
+            doc.Add(new Field("id", "6", customType));
+            doc.Add(new StringField("groupend", "x", Field.Store.NO));
+
+            w.AddDocument(doc);
+
+            IndexSearcher indexSearcher = NewSearcher(w.Reader);
+            w.Dispose();
+
+            Sort groupSort = Sort.RELEVANCE;
+            GroupingSearch groupingSearch = createRandomGroupingSearch(groupField, groupSort, 5, canUseIDV);
+
+            var groups = groupingSearch.Search(indexSearcher, (Filter)null, new TermQuery(new Index.Term("content", "random")), 0, 10);
+
+            assertEquals(7, groups.totalHitCount);
+            assertEquals(7, groups.totalGroupedHitCount);
+            assertEquals(4, groups.groups.length);
+
+            // relevance order: 5, 0, 3, 4, 1, 2, 6
+
+            // the later a document is added the higher this docId
+            // value
+            GroupDocs <?> group = groups.groups[0];
+            compareGroupValue("author3", group);
+            assertEquals(2, group.scoreDocs.length);
+            assertEquals(5, group.scoreDocs[0].doc);
+            assertEquals(4, group.scoreDocs[1].doc);
+            assertTrue(group.scoreDocs[0].score > group.scoreDocs[1].score);
+
+            group = groups.groups[1];
+            compareGroupValue("author1", group);
+            assertEquals(3, group.scoreDocs.length);
+            assertEquals(0, group.scoreDocs[0].doc);
+            assertEquals(1, group.scoreDocs[1].doc);
+            assertEquals(2, group.scoreDocs[2].doc);
+            assertTrue(group.scoreDocs[0].score > group.scoreDocs[1].score);
+            assertTrue(group.scoreDocs[1].score > group.scoreDocs[2].score);
+
+            group = groups.groups[2];
+            compareGroupValue("author2", group);
+            assertEquals(1, group.scoreDocs.length);
+            assertEquals(3, group.scoreDocs[0].doc);
+
+            group = groups.groups[3];
+            compareGroupValue(null, group);
+            assertEquals(1, group.scoreDocs.length);
+            assertEquals(6, group.scoreDocs[0].doc);
+
+            Filter lastDocInBlock = new CachingWrapperFilter(new QueryWrapperFilter(new TermQuery(new Index.Term("groupend", "x"))));
+            groupingSearch = new GroupingSearch(lastDocInBlock);
+            groups = groupingSearch.Search(indexSearcher, null, new TermQuery(new Index.Term("content", "random")), 0, 10);
+
+            assertEquals(7, groups.totalHitCount);
+            assertEquals(7, groups.totalGroupedHitCount);
+            assertEquals(4, groups.totalGroupCount.longValue());
+            assertEquals(4, groups.groups.length);
+
+            indexSearcher.IndexReader.Dispose();
+            dir.Dispose();
+        }
+
+        private void addGroupField(Document doc, string groupField, string value, bool canUseIDV)
+        {
+            doc.Add(new TextField(groupField, value, Field.Store.YES));
+            if (canUseIDV)
+            {
+                doc.Add(new SortedDocValuesField(groupField, new BytesRef(value)));
+            }
+        }
+
+        private void compareGroupValue(string expected, GroupDocs<?> group)
+        {
+            if (expected == null)
+            {
+                if (group.groupValue == null)
+                {
+                    return;
+                }
+                else if (group.groupValue.GetType().IsAssignableFrom(typeof(MutableValueStr)))
+                {
+                    return;
+                }
+                else if (((BytesRef)group.groupValue).length == 0)
+                {
+                    return;
+                }
+                fail();
+            }
+
+            if (group.groupValue.GetType().IsAssignableFrom(typeof(BytesRef)))
+            {
+                assertEquals(new BytesRef(expected), group.groupValue);
+            }
+            else if (group.groupValue.GetType().IsAssignableFrom(typeof(MutableValueStr)))
+            {
+                MutableValueStr v = new MutableValueStr();
+                v.value = new BytesRef(expected);
+                assertEquals(v, group.groupValue);
+            }
+            else
+            {
+                fail();
+            }
+        }
+
+        private GroupingSearch createRandomGroupingSearch(String groupField, Sort groupSort, int docsInGroup, bool canUseIDV)
+        {
+            GroupingSearch groupingSearch;
+            if (Random().nextBoolean())
+            {
+                ValueSource vs = new BytesRefFieldSource(groupField);
+                groupingSearch = new GroupingSearch(vs, new Dictionary());
+            }
+            else
+            {
+                groupingSearch = new GroupingSearch(groupField);
+            }
+
+            groupingSearch.SetGroupSort(groupSort);
+            groupingSearch.SetGroupDocsLimit(docsInGroup);
+
+            if (Random().nextBoolean())
+            {
+                groupingSearch.SetCachingInMB(4.0, true);
+            }
+
+            return groupingSearch;
+        }
+
+        [Test]
+        public void testSetAllGroups()
+        {
+            Directory dir = NewDirectory();
+            RandomIndexWriter w = new RandomIndexWriter(
+                Random(),
+                dir,
+                NewIndexWriterConfig(TEST_VERSION_CURRENT,
+                    new MockAnalyzer(Random())).SetMergePolicy(NewLogMergePolicy()));
+            Document doc = new Document();
+            doc.Add(NewField("group", "foo", StringField.TYPE_NOT_STORED));
+            w.AddDocument(doc);
+
+            IndexSearcher indexSearcher = NewSearcher(w.Reader);
+            w.Dispose();
+
+            GroupingSearch gs = new GroupingSearch("group");
+            gs.SetAllGroups(true);
+            TopGroups <?> groups = gs.Search(indexSearcher, null, new TermQuery(new Index.Term("group", "foo")), 0, 10);
+            assertEquals(1, groups.totalHitCount);
+            //assertEquals(1, groups.totalGroupCount.intValue());
+            assertEquals(1, groups.totalGroupedHitCount);
+            assertEquals(1, gs.GetAllMatchingGroups().size());
+            indexSearcher.IndexReader.Dispose();
+            dir.Dispose();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9d72bcb3/src/Lucene.Net.Tests.Grouping/Lucene.Net.Tests.Grouping.csproj
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Grouping/Lucene.Net.Tests.Grouping.csproj b/src/Lucene.Net.Tests.Grouping/Lucene.Net.Tests.Grouping.csproj
new file mode 100644
index 0000000..806839d
--- /dev/null
+++ b/src/Lucene.Net.Tests.Grouping/Lucene.Net.Tests.Grouping.csproj
@@ -0,0 +1,85 @@
+\ufeff<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{C2349F0D-FB66-4544-9C33-4D87F73C6004}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Lucene.Net.Tests.Grouping</RootNamespace>
+    <AssemblyName>Lucene.Net.Tests.Grouping</AssemblyName>
+    <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="AbstractGroupingTestCase.cs" />
+    <Compile Include="AllGroupHeadsCollectorTest.cs" />
+    <Compile Include="AllGroupsCollectorTest.cs" />
+    <Compile Include="GroupFacetCollectorTest.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Lucene.Net.Core\Lucene.Net.csproj">
+      <Project>{5d4ad9be-1ffb-41ab-9943-25737971bf57}</Project>
+      <Name>Lucene.Net</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net.Grouping\Lucene.Net.Grouping.csproj">
+      <Project>{02bab603-067d-48b1-aedd-316849652568}</Project>
+      <Name>Lucene.Net.Grouping</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net.Queries\Lucene.Net.Queries.csproj">
+      <Project>{69D7956C-C2CC-4708-B399-A188FEC384C4}</Project>
+      <Name>Lucene.Net.Queries</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Lucene.Net.TestFramework\Lucene.Net.TestFramework.csproj">
+      <Project>{B2C0D749-CE34-4F62-A15E-00CB2FF5DDB3}</Project>
+      <Name>Lucene.Net.TestFramework</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/lucenenet/blob/9d72bcb3/src/Lucene.Net.Tests.Grouping/Properties/AssemblyInfo.cs
----------------------------------------------------------------------
diff --git a/src/Lucene.Net.Tests.Grouping/Properties/AssemblyInfo.cs b/src/Lucene.Net.Tests.Grouping/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4944609
--- /dev/null
+++ b/src/Lucene.Net.Tests.Grouping/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+\ufeffusing System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Lucene.Net.Tests.Grouping")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Lucene.Net.Tests.Grouping")]
+[assembly: AssemblyCopyright("Copyright �  2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("c2349f0d-fb66-4544-9c33-4d87f73c6004")]
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]