You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by da...@apache.org on 2018/11/02 11:17:02 UTC

[53/56] lucene-solr:jira/gradle: Adding solr:contrib:clustering module

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java
new file mode 100644
index 0000000..31be33a
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/CarrotClusteringEngineTest.java
@@ -0,0 +1,542 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TermQuery;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.handler.clustering.AbstractClusteringTestCase;
+import org.apache.solr.handler.clustering.ClusteringComponent;
+import org.apache.solr.handler.clustering.ClusteringEngine;
+import org.apache.solr.handler.clustering.SearchClusteringEngine;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.search.DocList;
+import org.carrot2.clustering.lingo.LingoClusteringAlgorithm;
+import org.carrot2.core.LanguageCode;
+import org.carrot2.util.attribute.AttributeUtils;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class CarrotClusteringEngineTest extends AbstractClusteringTestCase {
+  @Test
+  public void testCarrotLingo() throws Exception {
+    // Note: the expected number of clusters may change after upgrading Carrot2
+    // due to e.g. internal improvements or tuning of Carrot2 clustering.
+    final int expectedNumClusters = 10;
+    checkEngine(getClusteringEngine("default"), expectedNumClusters);
+  }
+
+  @Test
+  public void testProduceSummary() throws Exception {
+    // We'll make two queries, one with- and another one without summary
+    // and assert that documents are shorter when highlighter is in use.
+    final List<NamedList<Object>> noSummaryClusters = clusterWithHighlighting(false, 80);
+    final List<NamedList<Object>> summaryClusters = clusterWithHighlighting(true, 80);
+
+    assertEquals("Equal number of clusters", noSummaryClusters.size(), summaryClusters.size());
+    for (int i = 0; i < noSummaryClusters.size(); i++) {
+      assertTrue("Summary shorter than original document", 
+          getLabels(noSummaryClusters.get(i)).get(1).length() > 
+          getLabels(summaryClusters.get(i)).get(1).length()); 
+    }
+  }
+  
+  @Test
+  public void testSummaryFragSize() throws Exception {
+    // We'll make two queries, one short summaries and another one with longer
+    // summaries and will check that the results differ.
+    final List<NamedList<Object>> shortSummaryClusters = clusterWithHighlighting(true, 30);
+    final List<NamedList<Object>> longSummaryClusters = clusterWithHighlighting(true, 80);
+    
+    assertEquals("Equal number of clusters", shortSummaryClusters.size(), longSummaryClusters.size());
+    for (int i = 0; i < shortSummaryClusters.size(); i++) {
+      assertTrue("Summary shorter than original document", 
+          getLabels(shortSummaryClusters.get(i)).get(1).length() < 
+      getLabels(longSummaryClusters.get(i)).get(1).length()); 
+    }
+  }
+
+  private List<NamedList<Object>> clusterWithHighlighting(
+      boolean enableHighlighting, int fragSize) throws IOException {
+    // Some documents don't have mining in the snippet
+    return clusterWithHighlighting(enableHighlighting, fragSize, 1, "mine", numberOfDocs - 7);
+  }
+
+  private List<NamedList<Object>> clusterWithHighlighting(
+      boolean enableHighlighting, int fragSize, int summarySnippets,
+      String term, int expectedNumDocuments) throws IOException {
+    
+    final TermQuery query = new TermQuery(new Term("snippet", term));
+
+    final ModifiableSolrParams summaryParams = new ModifiableSolrParams();
+    summaryParams.add(CarrotParams.SNIPPET_FIELD_NAME, "snippet");
+    summaryParams.add(CarrotParams.PRODUCE_SUMMARY,
+        Boolean.toString(enableHighlighting));
+    summaryParams
+        .add(CarrotParams.SUMMARY_FRAGSIZE, Integer.toString(fragSize));
+    summaryParams
+        .add(CarrotParams.SUMMARY_SNIPPETS, Integer.toString(summarySnippets));
+    final List<NamedList<Object>> summaryClusters = checkEngine(
+        getClusteringEngine("echo"), expectedNumDocuments,
+        expectedNumDocuments, query, summaryParams);
+    
+    return summaryClusters;
+  }
+
+  @Test
+  public void testCarrotStc() throws Exception {
+    checkEngine(getClusteringEngine("stc"), 3);
+  }
+
+  @Test
+  public void testWithoutSubclusters() throws Exception {
+    checkClusters(checkEngine(getClusteringEngine("mock"), AbstractClusteringTestCase.numberOfDocs),
+        1, 1, 0);
+  }
+
+  @Test
+  public void testExternalXmlAttributesFile() throws Exception {
+    checkClusters(
+        checkEngine(getClusteringEngine("mock-external-attrs"), 13),
+        1, 4, 0);
+  }
+
+  @Test
+  public void testWithSubclusters() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set(CarrotParams.OUTPUT_SUB_CLUSTERS, true);
+    checkClusters(checkEngine(getClusteringEngine("mock"), AbstractClusteringTestCase.numberOfDocs), 1, 1, 2);
+  }
+
+  @Test
+  public void testNumDescriptions() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set(AttributeUtils.getKey(MockClusteringAlgorithm.class, "labels"), 5);
+    params.set(CarrotParams.NUM_DESCRIPTIONS, 3);
+    checkClusters(checkEngine(getClusteringEngine("mock"), AbstractClusteringTestCase.numberOfDocs,
+            params), 1, 3, 0);
+  }
+
+  @Test
+  public void testClusterScores() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set(AttributeUtils.getKey(MockClusteringAlgorithm.class, "depth"), 1);
+    List<NamedList<Object>> clusters = checkEngine(getClusteringEngine("mock"),
+        AbstractClusteringTestCase.numberOfDocs, params);
+    int i = 1;
+    for (NamedList<Object> cluster : clusters) {
+      final Double score = getScore(cluster);
+      assertNotNull(score);
+      assertEquals(0.25 * i++, score, 0);
+    }
+  }
+
+  @Test
+  public void testOtherTopics() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set(AttributeUtils.getKey(MockClusteringAlgorithm.class, "depth"), 1);
+    params.set(AttributeUtils.getKey(MockClusteringAlgorithm.class, "otherTopicsModulo"), 2);
+    List<NamedList<Object>> clusters = checkEngine(getClusteringEngine("mock"),
+        AbstractClusteringTestCase.numberOfDocs, params);
+    int i = 1;
+    for (NamedList<Object> cluster : clusters) {
+      assertEquals(i++ % 2 == 0 ? true : null, isOtherTopics(cluster));
+    }
+  }
+
+  @Test
+  public void testCarrotAttributePassing() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set(AttributeUtils.getKey(MockClusteringAlgorithm.class, "depth"), 1);
+    params.set(AttributeUtils.getKey(MockClusteringAlgorithm.class, "labels"), 3);
+    checkClusters(checkEngine(getClusteringEngine("mock"), AbstractClusteringTestCase.numberOfDocs,
+            params), 1, 3, 0);
+  }
+
+  @Test
+  public void testLexicalResourcesFromSolrConfigDefaultDir() throws Exception {
+    checkLexicalResourcesFromSolrConfig("lexical-resource-check",
+        "online,customsolrstopword,customsolrstoplabel");
+  }
+
+  @Test
+  public void testLexicalResourcesFromSolrConfigCustomDir() throws Exception {
+    checkLexicalResourcesFromSolrConfig("lexical-resource-check-custom-resource-dir",
+        "online,customsolrstopwordcustomdir,customsolrstoplabelcustomdir");
+  }
+
+  private void checkLexicalResourcesFromSolrConfig(String engineName, String wordsToCheck)
+      throws IOException {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set("merge-resources", false);
+    params.set(AttributeUtils.getKey(
+        LexicalResourcesCheckClusteringAlgorithm.class, "wordsToCheck"),
+        wordsToCheck);
+
+    // "customsolrstopword" is in stopwords.en, "customsolrstoplabel" is in
+    // stoplabels.mt, so we're expecting only one cluster with label "online".
+    final List<NamedList<Object>> clusters = checkEngine(
+        getClusteringEngine(engineName), 1, params);
+    assertEquals(getLabels(clusters.get(0)), Collections.singletonList("online"));
+  }
+
+  @Test
+  public void testSolrStopWordsUsedInCarrot2Clustering() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    params.set("merge-resources", false);
+    params.set(AttributeUtils.getKey(
+        LexicalResourcesCheckClusteringAlgorithm.class, "wordsToCheck"),
+    "online,solrownstopword");
+
+    // "solrownstopword" is in stopwords.txt, so we're expecting
+    // only one cluster with label "online".
+    final List<NamedList<Object>> clusters = checkEngine(
+        getClusteringEngine("lexical-resource-check"), 1, params);
+    assertEquals(getLabels(clusters.get(0)), Collections.singletonList("online"));
+  }
+
+  @Test
+  public void testSolrStopWordsNotDefinedOnAFieldForClustering() throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+    // Force string fields to be used for clustering. Does not make sense
+    // in a real word, but does the job in the test.
+    params.set(CarrotParams.TITLE_FIELD_NAME, "url");
+    params.set(CarrotParams.SNIPPET_FIELD_NAME, "url");
+    params.set("merge-resources", false);
+    params.set(AttributeUtils.getKey(
+        LexicalResourcesCheckClusteringAlgorithm.class, "wordsToCheck"),
+    "online,solrownstopword");
+
+    final List<NamedList<Object>> clusters = checkEngine(
+        getClusteringEngine("lexical-resource-check"), 2, params);
+    assertEquals(Collections.singletonList("online"), getLabels(clusters.get(0)));
+    assertEquals(Collections.singletonList("solrownstopword"), getLabels(clusters.get(1)));
+  }
+  
+  @Test
+  public void testHighlightingOfMultiValueField() throws Exception {
+    final String snippetWithoutSummary = getLabels(clusterWithHighlighting(
+        false, 30, 3, "multi", 1).get(0)).get(1);
+    assertTrue("Snippet contains first value", snippetWithoutSummary.contains("First"));
+    assertTrue("Snippet contains second value", snippetWithoutSummary.contains("Second"));
+    assertTrue("Snippet contains third value", snippetWithoutSummary.contains("Third"));
+
+    final String snippetWithSummary = getLabels(clusterWithHighlighting(
+        true, 30, 3, "multi", 1).get(0)).get(1);
+    assertTrue("Snippet with summary shorter than full snippet",
+        snippetWithoutSummary.length() > snippetWithSummary.length());
+    assertTrue("Summary covers first value", snippetWithSummary.contains("First"));
+    assertTrue("Summary covers second value", snippetWithSummary.contains("Second"));
+    assertTrue("Summary covers third value", snippetWithSummary.contains("Third"));
+  }
+  
+  @Test
+  public void testConcatenatingMultipleFields() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.TITLE_FIELD_NAME, "title,heading");
+    params.add(CarrotParams.SNIPPET_FIELD_NAME, "snippet,body");
+
+    final List<String> labels = getLabels(checkEngine(
+        getClusteringEngine("echo"), 1, 1, new TermQuery(new Term("body",
+            "snippet")), params).get(0));
+    assertTrue("Snippet contains third value", labels.get(0).contains("Title field"));
+    assertTrue("Snippet contains third value", labels.get(0).contains("Heading field"));
+    assertTrue("Snippet contains third value", labels.get(1).contains("Snippet field"));
+    assertTrue("Snippet contains third value", labels.get(1).contains("Body field"));
+  }
+
+  @Test
+  public void testHighlightingMultipleFields() throws Exception {
+    final TermQuery query = new TermQuery(new Term("snippet", "content"));
+
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.TITLE_FIELD_NAME, "title,heading");
+    params.add(CarrotParams.SNIPPET_FIELD_NAME, "snippet,body");
+    params.add(CarrotParams.PRODUCE_SUMMARY, Boolean.toString(false));
+    
+    final String snippetWithoutSummary = getLabels(checkEngine(
+        getClusteringEngine("echo"), 1, 1, query, params).get(0)).get(1);
+    assertTrue("Snippet covers snippet field", snippetWithoutSummary.contains("snippet field"));
+    assertTrue("Snippet covers body field", snippetWithoutSummary.contains("body field"));
+
+    params.set(CarrotParams.PRODUCE_SUMMARY, Boolean.toString(true));
+    params.add(CarrotParams.SUMMARY_FRAGSIZE, Integer.toString(30));
+    params.add(CarrotParams.SUMMARY_SNIPPETS, Integer.toString(2));
+    final String snippetWithSummary = getLabels(checkEngine(
+        getClusteringEngine("echo"), 1, 1, query, params).get(0)).get(1);    
+    assertTrue("Snippet with summary shorter than full snippet",
+        snippetWithoutSummary.length() > snippetWithSummary.length());
+    assertTrue("Snippet covers snippet field", snippetWithSummary.contains("snippet field"));
+    assertTrue("Snippet covers body field", snippetWithSummary.contains("body field"));
+
+  }
+
+  @Test
+  public void testOneCarrot2SupportedLanguage() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.LANGUAGE_FIELD_NAME, "lang");
+
+    final List<String> labels = getLabels(checkEngine(
+        getClusteringEngine("echo"), 1, 1, new TermQuery(new Term("url",
+            "one_supported_language")), params).get(0));
+    assertEquals(3, labels.size());
+    assertEquals("Correct Carrot2 language", LanguageCode.CHINESE_SIMPLIFIED.name(), labels.get(2));
+  }
+  
+  @Test
+  public void testOneCarrot2SupportedLanguageOfMany() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.LANGUAGE_FIELD_NAME, "lang");
+    
+    final List<String> labels = getLabels(checkEngine(
+        getClusteringEngine("echo"), 1, 1, new TermQuery(new Term("url",
+            "one_supported_language_of_many")), params).get(0));
+    assertEquals(3, labels.size());
+    assertEquals("Correct Carrot2 language", LanguageCode.GERMAN.name(), labels.get(2));
+  }
+  
+  @Test
+  public void testLanguageCodeMapping() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.LANGUAGE_FIELD_NAME, "lang");
+    params.add(CarrotParams.LANGUAGE_CODE_MAP, "POLISH:pl");
+    
+    final List<String> labels = getLabels(checkEngine(
+        getClusteringEngine("echo"), 1, 1, new TermQuery(new Term("url",
+            "one_supported_language_of_many")), params).get(0));
+    assertEquals(3, labels.size());
+    assertEquals("Correct Carrot2 language", LanguageCode.POLISH.name(), labels.get(2));
+  }
+  
+  @Test
+  public void testPassingOfCustomFields() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.CUSTOM_FIELD_NAME, "intfield_i:intfield");
+    params.add(CarrotParams.CUSTOM_FIELD_NAME, "floatfield_f:floatfield");
+    params.add(CarrotParams.CUSTOM_FIELD_NAME, "heading:multi");
+    
+    // Let the echo mock clustering algorithm know which custom field to echo
+    params.add("custom-fields", "intfield,floatfield,multi");
+    
+    final List<String> labels = getLabels(checkEngine(
+        getClusteringEngine("echo"), 1, 1, new TermQuery(new Term("url",
+            "custom_fields")), params).get(0));
+    assertEquals(5, labels.size());
+    assertEquals("Integer field", "10", labels.get(2));
+    assertEquals("Float field", "10.5", labels.get(3));
+    assertEquals("List field", "[first, second]", labels.get(4));
+  }
+
+  @Test
+  public void testCustomTokenizer() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.TITLE_FIELD_NAME, "title");
+    params.add(CarrotParams.SNIPPET_FIELD_NAME, "snippet");
+
+    final List<String> labels = getLabels(checkEngine(
+        getClusteringEngine("custom-duplicating-tokenizer"), 1, 15, new TermQuery(new Term("title",
+            "field")), params).get(0));
+    
+    // The custom test tokenizer duplicates each token's text
+    assertTrue("First token", labels.get(0).contains("TitleTitle"));
+  }
+  
+  @Test
+  public void testCustomStemmer() throws Exception {
+    final ModifiableSolrParams params = new ModifiableSolrParams();
+    params.add(CarrotParams.TITLE_FIELD_NAME, "title");
+    params.add(CarrotParams.SNIPPET_FIELD_NAME, "snippet");
+    
+    final List<String> labels = getLabels(checkEngine(
+        getClusteringEngine("custom-duplicating-stemmer"), 1, 12, new TermQuery(new Term("title",
+            "field")), params).get(0));
+    
+    // The custom test stemmer duplicates and lowercases each token's text
+    assertTrue("First token", labels.get(0).contains("titletitle"));
+  }
+
+  @Test
+  public void testDefaultEngineOrder() throws Exception {
+    ClusteringComponent comp = (ClusteringComponent) h.getCore().getSearchComponent("clustering-name-default");
+    Map<String,SearchClusteringEngine> engines = getSearchClusteringEngines(comp);
+    assertEquals(
+        Arrays.asList("stc", "default", "mock"),
+        new ArrayList<>(engines.keySet()));
+    assertEquals(
+        LingoClusteringAlgorithm.class,
+        ((CarrotClusteringEngine) engines.get(ClusteringEngine.DEFAULT_ENGINE_NAME)).getClusteringAlgorithmClass());
+  }
+
+  @Test
+  public void testDeclarationEngineOrder() throws Exception {
+    ClusteringComponent comp = (ClusteringComponent) h.getCore().getSearchComponent("clustering-name-decl-order");
+    Map<String,SearchClusteringEngine> engines = getSearchClusteringEngines(comp);
+    assertEquals(
+        Arrays.asList("unavailable", "lingo", "stc", "mock", "default"),
+        new ArrayList<>(engines.keySet()));
+    assertEquals(
+        LingoClusteringAlgorithm.class,
+        ((CarrotClusteringEngine) engines.get(ClusteringEngine.DEFAULT_ENGINE_NAME)).getClusteringAlgorithmClass());
+  }
+
+  @Test
+  public void testDeclarationNameDuplicates() throws Exception {
+    ClusteringComponent comp = (ClusteringComponent) h.getCore().getSearchComponent("clustering-name-dups");
+    Map<String,SearchClusteringEngine> engines = getSearchClusteringEngines(comp);
+    assertEquals(
+        Arrays.asList("", "default"),
+        new ArrayList<>(engines.keySet()));
+    assertEquals(
+        MockClusteringAlgorithm.class,
+        ((CarrotClusteringEngine) engines.get(ClusteringEngine.DEFAULT_ENGINE_NAME)).getClusteringAlgorithmClass());
+  }
+
+  private CarrotClusteringEngine getClusteringEngine(String engineName) {
+    ClusteringComponent comp = (ClusteringComponent) h.getCore()
+            .getSearchComponent("clustering");
+    assertNotNull("clustering component should not be null", comp);
+    CarrotClusteringEngine engine = 
+        (CarrotClusteringEngine) getSearchClusteringEngines(comp).get(engineName);
+    assertNotNull("clustering engine for name: " + engineName
+            + " should not be null", engine);
+    return engine;
+  }
+
+  private List<NamedList<Object>> checkEngine(CarrotClusteringEngine engine,
+                            int expectedNumClusters) throws IOException {
+    return checkEngine(engine, numberOfDocs, expectedNumClusters, new MatchAllDocsQuery(), new ModifiableSolrParams());
+  }
+
+  private List<NamedList<Object>> checkEngine(CarrotClusteringEngine engine,
+                            int expectedNumClusters, SolrParams clusteringParams) throws IOException {
+    return checkEngine(engine, numberOfDocs, expectedNumClusters, new MatchAllDocsQuery(), clusteringParams);
+  }
+
+
+  private List<NamedList<Object>> checkEngine(CarrotClusteringEngine engine, int expectedNumDocs,
+                           int expectedNumClusters, Query query, SolrParams clusteringParams) throws IOException {
+    // Get all documents to cluster
+    return h.getCore().withSearcher(searcher -> {
+      DocList docList = searcher.getDocList(query, (Query) null, new Sort(), 0,
+          numberOfDocs);
+      assertEquals("docList size", expectedNumDocs, docList.matches());
+
+      ModifiableSolrParams solrParams = new ModifiableSolrParams();
+      solrParams.add(clusteringParams);
+
+      // Perform clustering
+      LocalSolrQueryRequest req = new LocalSolrQueryRequest(h.getCore(), solrParams);
+      Map<SolrDocument,Integer> docIds = new HashMap<>(docList.size());
+      SolrDocumentList solrDocList = ClusteringComponent.docListToSolrDocumentList( docList, searcher, engine.getFieldsToLoad(req), docIds );
+
+      @SuppressWarnings("unchecked")
+      List<NamedList<Object>> results = (List<NamedList<Object>>) engine.cluster(query, solrDocList, docIds, req);
+      req.close();
+      assertEquals("number of clusters: " + results, expectedNumClusters, results.size());
+      checkClusters(results, false);
+      return results;
+    });
+  }
+
+  private void checkClusters(List<NamedList<Object>> results, int expectedDocCount,
+                             int expectedLabelCount, int expectedSubclusterCount) {
+    for (int i = 0; i < results.size(); i++) {
+      NamedList<Object> cluster = results.get(i);
+      checkCluster(cluster, expectedDocCount, expectedLabelCount,
+              expectedSubclusterCount);
+    }
+  }
+
+  private void checkClusters(List<NamedList<Object>> results, boolean hasSubclusters) {
+    for (int i = 0; i < results.size(); i++) {
+      checkCluster(results.get(i), hasSubclusters);
+    }
+  }
+
+  private void checkCluster(NamedList<Object> cluster, boolean hasSubclusters) {
+    List<Object> docs = getDocs(cluster);
+    assertNotNull("docs is null and it shouldn't be", docs);
+    for (int j = 0; j < docs.size(); j++) {
+      Object id = docs.get(j);
+      assertNotNull("id is null and it shouldn't be", id);
+    }
+
+    List<String> labels = getLabels(cluster);
+    assertNotNull("labels is null but it shouldn't be", labels);
+
+    if (hasSubclusters) {
+      List<NamedList<Object>> subclusters = getSubclusters(cluster);
+      assertNotNull("subclusters is null but it shouldn't be", subclusters);
+    }
+  }
+
+  private void checkCluster(NamedList<Object> cluster, int expectedDocCount,
+                            int expectedLabelCount, int expectedSubclusterCount) {
+    checkCluster(cluster, expectedSubclusterCount > 0);
+    assertEquals("number of docs in cluster", expectedDocCount,
+            getDocs(cluster).size());
+    assertEquals("number of labels in cluster", expectedLabelCount,
+            getLabels(cluster).size());
+
+    if (expectedSubclusterCount > 0) {
+      List<NamedList<Object>> subclusters = getSubclusters(cluster);
+      assertEquals("numClusters", expectedSubclusterCount, subclusters.size());
+      assertEquals("number of subclusters in cluster",
+              expectedSubclusterCount, subclusters.size());
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private List<NamedList<Object>> getSubclusters(NamedList<Object> cluster) {
+    return (List<NamedList<Object>>) cluster.get("clusters");
+  }
+
+  @SuppressWarnings("unchecked")
+  private List<String> getLabels(NamedList<Object> cluster) {
+    return (List<String>) cluster.get("labels");
+  }
+
+  private Double getScore(NamedList<Object> cluster) {
+    return (Double) cluster.get("score");
+  }
+
+  private Boolean isOtherTopics(NamedList<Object> cluster) {
+    return (Boolean)cluster.get("other-topics");
+  }
+
+  @SuppressWarnings("unchecked")
+  private List<Object> getDocs(NamedList<Object> cluster) {
+    return (List<Object>) cluster.get("docs");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingStemmerFactory.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingStemmerFactory.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingStemmerFactory.java
new file mode 100644
index 0000000..f1f883d
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingStemmerFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+
+import org.carrot2.core.LanguageCode;
+import org.carrot2.text.linguistic.IStemmer;
+import org.carrot2.text.linguistic.IStemmerFactory;
+
+public class DuplicatingStemmerFactory implements IStemmerFactory {
+  @Override
+  public IStemmer getStemmer(LanguageCode language) {
+    return new IStemmer() {
+      @Override
+      public CharSequence stem(CharSequence word) {
+        return word.toString() + word.toString();
+      }
+    };
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingTokenizerFactory.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingTokenizerFactory.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingTokenizerFactory.java
new file mode 100644
index 0000000..51820e7
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/DuplicatingTokenizerFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+
+import java.io.IOException;
+import java.io.Reader;
+
+import org.carrot2.core.LanguageCode;
+import org.carrot2.text.analysis.ExtendedWhitespaceTokenizer;
+import org.carrot2.text.analysis.ITokenizer;
+import org.carrot2.text.linguistic.ITokenizerFactory;
+import org.carrot2.text.util.MutableCharArray;
+
+public class DuplicatingTokenizerFactory implements ITokenizerFactory {
+  @Override
+  public ITokenizer getTokenizer(LanguageCode language) {
+    return new ITokenizer() {
+      private final ExtendedWhitespaceTokenizer delegate = new ExtendedWhitespaceTokenizer();
+      
+      @Override
+      public void setTermBuffer(MutableCharArray buffer) {
+        delegate.setTermBuffer(buffer);
+        buffer.reset(buffer.toString() + buffer.toString());
+      }
+      
+      @Override
+      public void reset(Reader input) {
+        delegate.reset(input);
+      }
+      
+      @Override
+      public short nextToken() throws IOException {
+        return delegate.nextToken();
+      }
+    };
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoClusteringAlgorithm.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoClusteringAlgorithm.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoClusteringAlgorithm.java
new file mode 100644
index 0000000..2c95da3
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoClusteringAlgorithm.java
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.carrot2.core.Cluster;
+import org.carrot2.core.Document;
+import org.carrot2.core.IClusteringAlgorithm;
+import org.carrot2.core.ProcessingComponentBase;
+import org.carrot2.core.ProcessingException;
+import org.carrot2.core.attribute.AttributeNames;
+import org.carrot2.core.attribute.Processing;
+import org.carrot2.util.attribute.Attribute;
+import org.carrot2.util.attribute.Bindable;
+import org.carrot2.util.attribute.Input;
+import org.carrot2.util.attribute.Output;
+
+/**
+ * A mock Carrot2 clustering algorithm that outputs input documents as clusters.
+ * Useful only in tests.
+ */
+@Bindable(prefix = "EchoClusteringAlgorithm")
+public class EchoClusteringAlgorithm extends ProcessingComponentBase implements
+        IClusteringAlgorithm {
+  @Input
+  @Processing
+  @Attribute(key = AttributeNames.DOCUMENTS)
+  public List<Document> documents;
+
+  @Output
+  @Processing
+  @Attribute(key = AttributeNames.CLUSTERS)
+  public List<Cluster> clusters;
+
+  @Input
+  @Processing
+  @Attribute(key = "custom-fields")
+  public String customFields = "";
+
+  
+  @Override
+  public void process() throws ProcessingException {
+    clusters = new ArrayList<>();
+    
+    for (Document document : documents) {
+      final Cluster cluster = new Cluster();
+      cluster.addPhrases(document.getTitle(), document.getSummary());
+      if (document.getLanguage() != null) {
+        cluster.addPhrases(document.getLanguage().name());
+      }
+      for (String field : customFields.split(",")) {
+        Object value = document.getField(field);
+        if (value != null) {
+          cluster.addPhrases(value.toString());
+        }
+      }
+      cluster.addDocuments(document);
+      clusters.add(cluster);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoStemsClusteringAlgorithm.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoStemsClusteringAlgorithm.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoStemsClusteringAlgorithm.java
new file mode 100644
index 0000000..f39fcd9
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoStemsClusteringAlgorithm.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.carrot2.core.Cluster;
+import org.carrot2.core.Document;
+import org.carrot2.core.IClusteringAlgorithm;
+import org.carrot2.core.LanguageCode;
+import org.carrot2.core.ProcessingComponentBase;
+import org.carrot2.core.ProcessingException;
+import org.carrot2.core.attribute.AttributeNames;
+import org.carrot2.core.attribute.Processing;
+import org.carrot2.text.preprocessing.PreprocessingContext;
+import org.carrot2.text.preprocessing.PreprocessingContext.AllStems;
+import org.carrot2.text.preprocessing.PreprocessingContext.AllTokens;
+import org.carrot2.text.preprocessing.PreprocessingContext.AllWords;
+import org.carrot2.text.preprocessing.pipeline.BasicPreprocessingPipeline;
+import org.carrot2.util.attribute.Attribute;
+import org.carrot2.util.attribute.Bindable;
+import org.carrot2.util.attribute.Input;
+import org.carrot2.util.attribute.Output;
+
+/**
+ * A mock Carrot2 clustering algorithm that outputs stem of each token of each
+ * document as a separate cluster. Useful only in tests.
+ */
+@Bindable(prefix = "EchoTokensClusteringAlgorithm")
+public class EchoStemsClusteringAlgorithm extends ProcessingComponentBase
+    implements IClusteringAlgorithm {
+  @Input
+  @Processing
+  @Attribute(key = AttributeNames.DOCUMENTS)
+  public List<Document> documents;
+  
+  @Output
+  @Processing
+  @Attribute(key = AttributeNames.CLUSTERS)
+  public List<Cluster> clusters;
+
+  public BasicPreprocessingPipeline preprocessing = new BasicPreprocessingPipeline();
+  
+  @Override
+  public void process() throws ProcessingException {
+    final PreprocessingContext preprocessingContext = preprocessing.preprocess(
+        documents, "", LanguageCode.ENGLISH);
+    final AllTokens allTokens = preprocessingContext.allTokens;
+    final AllWords allWords = preprocessingContext.allWords;
+    final AllStems allStems = preprocessingContext.allStems;
+    clusters = new ArrayList<>();
+    for (int i = 0; i < allTokens.image.length; i++) {
+      if (allTokens.wordIndex[i] >= 0) {
+        clusters.add(new Cluster(new String(
+            allStems.image[allWords.stemIndex[allTokens.wordIndex[i]]])));
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoTokensClusteringAlgorithm.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoTokensClusteringAlgorithm.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoTokensClusteringAlgorithm.java
new file mode 100644
index 0000000..32e47d8
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/EchoTokensClusteringAlgorithm.java
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.carrot2.core.Cluster;
+import org.carrot2.core.Document;
+import org.carrot2.core.IClusteringAlgorithm;
+import org.carrot2.core.LanguageCode;
+import org.carrot2.core.ProcessingComponentBase;
+import org.carrot2.core.ProcessingException;
+import org.carrot2.core.attribute.AttributeNames;
+import org.carrot2.core.attribute.Processing;
+import org.carrot2.text.preprocessing.PreprocessingContext;
+import org.carrot2.text.preprocessing.pipeline.BasicPreprocessingPipeline;
+import org.carrot2.util.attribute.Attribute;
+import org.carrot2.util.attribute.Bindable;
+import org.carrot2.util.attribute.Input;
+import org.carrot2.util.attribute.Output;
+
+
+/**
+ * A mock Carrot2 clustering algorithm that outputs each token of each document
+ * as a separate cluster. Useful only in tests.
+ */
+@Bindable(prefix = "EchoTokensClusteringAlgorithm")
+public class EchoTokensClusteringAlgorithm extends ProcessingComponentBase
+    implements IClusteringAlgorithm {
+  @Input
+  @Processing
+  @Attribute(key = AttributeNames.DOCUMENTS)
+  public List<Document> documents;
+  
+  @Output
+  @Processing
+  @Attribute(key = AttributeNames.CLUSTERS)
+  public List<Cluster> clusters;
+  
+  public BasicPreprocessingPipeline preprocessing = new BasicPreprocessingPipeline();
+  
+  @Override
+  public void process() throws ProcessingException {
+    final PreprocessingContext preprocessingContext = preprocessing.preprocess(
+        documents, "", LanguageCode.ENGLISH);
+    clusters = new ArrayList<>();
+    for (char[] token : preprocessingContext.allTokens.image) {
+      if (token != null) {
+        clusters.add(new Cluster(new String(token)));
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/LexicalResourcesCheckClusteringAlgorithm.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/LexicalResourcesCheckClusteringAlgorithm.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/LexicalResourcesCheckClusteringAlgorithm.java
new file mode 100644
index 0000000..9f69040
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/LexicalResourcesCheckClusteringAlgorithm.java
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.carrot2.core.Cluster;
+import org.carrot2.core.IClusteringAlgorithm;
+import org.carrot2.core.LanguageCode;
+import org.carrot2.core.ProcessingComponentBase;
+import org.carrot2.core.ProcessingException;
+import org.carrot2.core.attribute.AttributeNames;
+import org.carrot2.core.attribute.Processing;
+import org.carrot2.text.linguistic.ILexicalData;
+import org.carrot2.text.preprocessing.pipeline.BasicPreprocessingPipeline;
+import org.carrot2.text.util.MutableCharArray;
+import org.carrot2.util.attribute.Attribute;
+import org.carrot2.util.attribute.Bindable;
+import org.carrot2.util.attribute.Input;
+import org.carrot2.util.attribute.Output;
+
+/**
+ * A mock implementation of Carrot2 clustering algorithm for testing whether the
+ * customized lexical resource lookup works correctly. This algorithm ignores
+ * the input documents and instead for each word from {@link #wordsToCheck}, it
+ * outputs a cluster labeled with the word only if the word is neither a stop
+ * word nor a stop label.
+ */
+@Bindable(prefix = "LexicalResourcesCheckClusteringAlgorithm")
+public class LexicalResourcesCheckClusteringAlgorithm extends
+    ProcessingComponentBase implements IClusteringAlgorithm {
+
+  @Output
+  @Processing
+  @Attribute(key = AttributeNames.CLUSTERS)
+  public List<Cluster> clusters;
+
+  @Input
+  @Processing
+  @Attribute
+  public String wordsToCheck;
+
+  public BasicPreprocessingPipeline preprocessing = new BasicPreprocessingPipeline();
+
+  @Override
+  public void process() throws ProcessingException {
+    clusters = new ArrayList<>();
+    if (wordsToCheck == null) {
+      return;
+    }
+
+    // Test with Maltese so that the English clustering performed in other tests
+    // is not affected by the test stopwords and stoplabels.
+    ILexicalData lexicalData = preprocessing.lexicalDataFactory
+        .getLexicalData(LanguageCode.MALTESE);
+
+    for (String word : wordsToCheck.split(",")) {
+      if (!lexicalData.isCommonWord(new MutableCharArray(word))
+          && !lexicalData.isStopLabel(word)) {
+        clusters.add(new Cluster(word));
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/MockClusteringAlgorithm.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/MockClusteringAlgorithm.java b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/MockClusteringAlgorithm.java
new file mode 100644
index 0000000..ba978a5
--- /dev/null
+++ b/solr/contrib/clustering/src/test/java/org/apache/solr/handler/clustering/carrot2/MockClusteringAlgorithm.java
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+package org.apache.solr.handler.clustering.carrot2;
+import org.carrot2.core.*;
+import org.carrot2.core.attribute.AttributeNames;
+import org.carrot2.core.attribute.Processing;
+import org.carrot2.util.attribute.*;
+import org.carrot2.util.attribute.constraint.IntRange;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Bindable(prefix = "MockClusteringAlgorithm")
+public class MockClusteringAlgorithm extends ProcessingComponentBase implements
+        IClusteringAlgorithm {
+  @Input
+  @Processing
+  @Attribute(key = AttributeNames.DOCUMENTS)
+  public List<Document> documents;
+
+  @Output
+  @Processing
+  @Attribute(key = AttributeNames.CLUSTERS)
+  public List<Cluster> clusters;
+
+  @Input
+  @Processing
+  @Attribute
+  @IntRange(min = 1, max = 5)
+  public int depth = 2;
+
+  @Input
+  @Processing
+  @Attribute
+  @IntRange(min = 1, max = 5)
+  public int labels = 1;
+
+  @Input
+  @Processing
+  @Attribute
+  @IntRange(min = 0)
+  public int maxClusters = 0;
+
+  @Input
+  @Processing
+  @Attribute
+  public int otherTopicsModulo = 0;
+
+  @Override
+  public void process() throws ProcessingException {
+    clusters = new ArrayList<>();
+    if (documents == null) {
+      return;
+    }
+
+    if (maxClusters > 0) {
+      documents = documents.subList(0, maxClusters);
+    }
+
+    int documentIndex = 1;
+    for (Document document : documents) {
+      StringBuilder label = new StringBuilder("Cluster " + documentIndex);
+      Cluster cluster = createCluster(label.toString(), documentIndex, document);
+      clusters.add(cluster);
+      for (int i = 1; i <= depth; i++) {
+        label.append(".");
+        label.append(i);
+        Cluster newCluster = createCluster(label.toString(), documentIndex, document);
+        cluster.addSubclusters(createCluster(label.toString(), documentIndex, document), newCluster);
+        cluster = newCluster;
+      }
+      documentIndex++;
+    }
+  }
+
+  private Cluster createCluster(String labelBase, int documentIndex, Document... documents) {
+    Cluster cluster = new Cluster();
+    cluster.setScore(documentIndex * 0.25);
+    if (otherTopicsModulo != 0 && documentIndex % otherTopicsModulo == 0)
+    {
+      cluster.setOtherTopics(true);
+    }
+    for (int i = 0; i < labels; i++) {
+      cluster.addPhrases(labelBase + "#" + (i + 1));
+    }
+    cluster.addDocuments(documents);
+    return cluster;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/AbstractClusteringTestCase.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/AbstractClusteringTestCase.java b/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/AbstractClusteringTestCase.java
deleted file mode 100644
index ca2f424..0000000
--- a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/AbstractClusteringTestCase.java
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.handler.clustering;
-import java.io.File;
-import java.util.Map;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.SolrInputDocument;
-import org.junit.BeforeClass;
-
-
-/**
- *
- */
-public abstract class AbstractClusteringTestCase extends SolrTestCaseJ4 {
-  protected static int numberOfDocs = 0;
-
-  @BeforeClass
-  public static void beforeClass() throws Exception {
-    File testHome = createTempDir().toFile();
-    FileUtils.copyDirectory(getFile("clustering/solr"), testHome);
-    initCore("solrconfig.xml", "schema.xml", testHome.getAbsolutePath());
-    numberOfDocs = 0;
-    for (String[] doc : DOCUMENTS) {
-      assertNull(h.validateUpdate(adoc("id", Integer.toString(numberOfDocs), "url", doc[0], "title", doc[1], "snippet", doc[2])));
-      numberOfDocs++;
-    }
-    
-    // Add a multi-valued snippet
-    final SolrInputDocument multiValuedSnippet = new SolrInputDocument();
-    multiValuedSnippet.addField("id", numberOfDocs++);
-    multiValuedSnippet.addField("title", "Title");
-    multiValuedSnippet.addField("url", "URL");
-    multiValuedSnippet.addField("snippet", "First value of multi field. Some more text. And still more.");
-    multiValuedSnippet.addField("snippet", "Second value of multi field. Some more text. And still more.");
-    multiValuedSnippet.addField("snippet", "Third value of multi field. Some more text. And still more.");
-    assertNull(h.validateUpdate(adoc(multiValuedSnippet)));
-
-    // Add a document with multi-field title and snippet
-    final SolrInputDocument multiFieldDoc = new SolrInputDocument();
-    multiFieldDoc.addField("id", numberOfDocs++);
-    multiFieldDoc.addField("title", "Title field");
-    multiFieldDoc.addField("heading", "Heading field");
-    multiFieldDoc.addField("url", "URL");
-    multiFieldDoc.addField("snippet", "Snippet field: this is the contents of the snippet field.");
-    multiFieldDoc.addField("body", "Body field: this is the contents of the body field that will get clustered together with snippet.");
-    assertNull(h.validateUpdate(adoc(multiFieldDoc)));
-    
-    // Add a document with one language supported by Carrot2
-    final SolrInputDocument docWithOneSupprtedLanguage = new SolrInputDocument();
-    docWithOneSupprtedLanguage.addField("id", numberOfDocs++);
-    docWithOneSupprtedLanguage.addField("title", "");
-    docWithOneSupprtedLanguage.addField("url", "one_supported_language");
-    docWithOneSupprtedLanguage.addField("lang", "zh-cn");
-    assertNull(h.validateUpdate(adoc(docWithOneSupprtedLanguage)));
-    
-    // Add a document with more languages, one supported by Carrot2
-    final SolrInputDocument docWithOneSupprtedLanguageOfMany = new SolrInputDocument();
-    docWithOneSupprtedLanguageOfMany.addField("id", numberOfDocs++);
-    docWithOneSupprtedLanguageOfMany.addField("url", "one_supported_language_of_many");
-    docWithOneSupprtedLanguageOfMany.addField("lang", "zh-tw");
-    docWithOneSupprtedLanguageOfMany.addField("lang", "POLISH");
-    docWithOneSupprtedLanguageOfMany.addField("lang", "de");
-    assertNull(h.validateUpdate(adoc(docWithOneSupprtedLanguageOfMany)));
-    
-    // Add a document with more languages, one supported by Carrot2
-    final SolrInputDocument docWithCustomFields = new SolrInputDocument();
-    docWithCustomFields.addField("id", numberOfDocs++);
-    docWithCustomFields.addField("url", "custom_fields");
-    docWithCustomFields.addField("intfield_i", 10);
-    docWithCustomFields.addField("floatfield_f", 10.5);
-    docWithCustomFields.addField("heading", "first");
-    docWithCustomFields.addField("heading", "second");
-    assertNull(h.validateUpdate(adoc(docWithCustomFields)));
-    assertNull(h.validateUpdate(commit()));
-  }
-
-  /**
-   * Expose package-scope methods from {@link ClusteringComponent} to tests.
-   */
-  protected final Map<String,SearchClusteringEngine> getSearchClusteringEngines(ClusteringComponent comp) {
-    return comp.getSearchClusteringEngines();
-  }
-
-  final static String[][] DOCUMENTS = new String[][]{
-          {"http://en.wikipedia.org/wiki/Data_mining",
-                  "Data Mining - Wikipedia",
-                  "Article about knowledge-discovery in databases (KDD), the practice of automatically searching large stores of data for patterns."},
-
-
-          {"http://en.wikipedia.org/wiki/Datamining",
-                  "Data mining - Wikipedia, the free encyclopedia",
-                  "Data mining is the entire process of applying computer-based methodology, ... Moreover, some data-mining systems such as neural networks are inherently geared ..."},
-
-
-          {"http://www.statsoft.com/textbook/stdatmin.html",
-                  "Electronic Statistics Textbook: Data Mining Techniques",
-                  "Outlines the crucial concepts in data mining, defines the data warehousing process, and offers examples of computational and graphical exploratory data analysis techniques."},
-
-
-          {"http://www.thearling.com/text/dmwhite/dmwhite.htm",
-                  "An Introduction to Data Mining",
-                  "Data mining, the extraction of hidden predictive information from large ... Data mining tools predict future trends and behaviors, allowing businesses to ..."},
-
-
-          {"http://www.anderson.ucla.edu/faculty/jason.frand/teacher/technologies/palace/datamining.htm",
-                  "Data Mining: What is Data Mining?",
-                  "Outlines what knowledge discovery, the process of analyzing data from different perspectives and summarizing it into useful information, can do and how it works."},
-
-
-          {"http://www.spss.com/datamine",
-                  "Data Mining Software, Data Mining Applications and Data Mining Solutions",
-                  "The patterns uncovered using data mining help organizations make better and ... data mining customer ... Data mining applications, on the other hand, embed ..."},
-
-
-          {"http://www.kdnuggets.com/",
-                  "KD Nuggets",
-                  "Newsletter on the data mining and knowledge industries, offering information on data mining, knowledge discovery, text mining, and web mining software, courses, jobs, publications, and meetings."},
-
-
-          {"http://www.answers.com/topic/data-mining",
-                  "data mining: Definition from Answers.com",
-                  "data mining n. The automatic extraction of useful, often previously unknown information from large databases or data ... Data Mining For Investing ..."},
-
-
-          {"http://www.statsoft.com/products/dataminer.htm",
-                  "STATISTICA Data Mining and Predictive Modeling Solutions",
-                  "GRC site-wide menuing system research and development. ... Contact a Data Mining Solutions Consultant. News and Success Stories. Events ..."},
-
-
-          {"http://datamining.typepad.com/",
-                  "Data Mining: Text Mining, Visualization and Social Media",
-                  "Commentary on text mining, data mining, social media and data visualization. ... While mining Twitter data for business and marketing intelligence (trend/buzz ..."},
-
-
-          {"http://www.twocrows.com/",
-                  "Two Crows Corporation",
-                  "Dedicated to the development, marketing, sales and support of tools for knowledge discovery to make data mining accessible and easy to use."},
-
-
-          {"http://www.thearling.com/",
-                  "Thearling.com",
-                  "Kurt Thearling's site dedicated to sharing information about data mining, the automated extraction of hidden predictive information from databases, and other analytic technologies."},
-
-
-          {"http://www.ccsu.edu/datamining/",
-                  "CCSU - Data Mining",
-                  "Offers degrees and certificates in data mining. Allows students to explore cutting-edge data mining techniques and applications: market basket analysis, decision trees, neural networks, machine learning, web mining, and data modeling."},
-
-
-          {"http://www.oracle.com/technology/products/bi/odm",
-                  "Oracle Data Mining",
-                  "Oracle Data Mining Product Center ... New Oracle Data Mining Powers New Social CRM Application (more information ... Mining High-Dimensional Data for ..."},
-
-
-          {"http://databases.about.com/od/datamining/a/datamining.htm",
-                  "Data Mining: An Introduction",
-                  "About.com article on how businesses are discovering new trends and patterns of behavior that previously went unnoticed through data mining, automated statistical analysis techniques."},
-
-
-          {"http://www.dmoz.org/Computers/Software/Databases/Data_Mining/",
-                  "Open Directory - Computers: Software: Databases: Data Mining",
-                  "Data Mining and Knowledge Discovery - A peer-reviewed journal publishing ... Data mining creates information assets that an organization can leverage to ..."},
-
-
-          {"http://www.cs.wisc.edu/dmi/",
-                  "DMI:Data Mining Institute",
-                  "Data Mining Institute at UW-Madison ... The Data Mining Institute (DMI) was started on June 1, 1999 at the Computer ... of the Data Mining Group of Microsoft ..."},
-
-
-          {"http://www.the-data-mine.com/",
-                  "The Data Mine",
-                  "Provides information about data mining also known as knowledge discovery in databases (KDD) or simply knowledge discovery. List software, events, organizations, and people working in data mining."},
-
-
-          {"http://www.statserv.com/datamining.html",
-                  "St@tServ - About Data Mining",
-                  "St@tServ Data Mining page ... Data mining in molecular biology, by Alvis Brazma. Graham Williams page. Knowledge Discovery and Data Mining Resources, ..."},
-
-
-          {"http://ocw.mit.edu/OcwWeb/Sloan-School-of-Management/15-062Data-MiningSpring2003/CourseHome/index.htm",
-                  "MIT OpenCourseWare | Sloan School of Management | 15.062 Data Mining ...",
-                  "Introduces students to a class of methods known as data mining that assists managers in recognizing patterns and making intelligent use of massive amounts of ..."},
-
-
-          {"http://www.pentaho.com/products/data_mining/",
-                  "Pentaho Commercial Open Source Business Intelligence: Data Mining",
-                  "For example, data mining can warn you there's a high probability a specific ... Pentaho Data Mining is differentiated by its open, standards-compliant nature, ..."},
-
-
-          {"http://www.investorhome.com/mining.htm",
-                  "Investor Home - Data Mining",
-                  "Data Mining or Data Snooping is the practice of searching for relationships and ... Data mining involves searching through databases for correlations and patterns ..."},
-
-
-          {"http://www.datamining.com/",
-                  "Predictive Modeling and Predictive Analytics Solutions | Enterprise ...",
-                  "Insightful Enterprise Miner - Enterprise data mining for predictive modeling and predictive analytics."},
-
-
-          {"http://www.sourcewatch.org/index.php?title=Data_mining",
-                  "Data mining - SourceWatch",
-                  "These agencies reported 199 data mining projects, of which 68 ... Office, \"DATA MINING. ... powerful technology known as data mining -- and how, in the ..."},
-
-
-          {"http://www.autonlab.org/tutorials/",
-                  "Statistical Data Mining Tutorials",
-                  "Includes a set of tutorials on many aspects of statistical data mining, including the foundations of probability, the foundations of statistical data analysis, and most of the classic machine learning and data mining algorithms."},
-
-
-          {"http://www.microstrategy.com/data-mining/index.asp",
-                  "Data Mining",
-                  "With MicroStrategy, data mining scoring is fully integrated into mainstream ... The integration of data mining models from other applications is accomplished by ..."},
-
-
-          {"http://www.datamininglab.com/",
-                  "Elder Research",
-                  "Provides consulting and short courses in data mining and pattern discovery patterns in data."},
-
-
-          {"http://www.sqlserverdatamining.com/",
-                  "SQL Server Data Mining > Home",
-                  "SQL Server Data Mining Portal ... Data Mining as an Application Platform (Whitepaper) Creating a Web Cross-sell Application with SQL Server 2005 Data Mining (Article) ..."},
-
-
-          {"http://databases.about.com/cs/datamining/g/dmining.htm",
-                  "Data Mining",
-                  "What is data mining? Find out here! ... Book Review: Data Mining and Statistical Analysis Using SQL. What is Data Mining, and What Does it Have to Do with ..."},
-
-
-          {"http://www.sas.com/technologies/analytics/datamining/index.html",
-                  "Data Mining Software and Text Mining | SAS",
-                  "... raw data to smarter ... Data Mining is an iterative process of creating ... The knowledge gleaned from data and text mining can be used to fuel ..."}
-  };
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/ClusteringComponentTest.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/ClusteringComponentTest.java b/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/ClusteringComponentTest.java
deleted file mode 100644
index e481a11..0000000
--- a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/ClusteringComponentTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.handler.clustering;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.solr.common.SolrDocument;
-import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.handler.component.QueryComponent;
-import org.apache.solr.handler.component.SearchComponent;
-import org.apache.solr.request.LocalSolrQueryRequest;
-import org.apache.solr.request.SolrQueryRequest;
-import org.apache.solr.request.SolrRequestHandler;
-import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.search.DocList;
-import org.apache.solr.search.QueryCommand;
-import org.apache.solr.search.QueryResult;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- *
- *
- **/
-public class ClusteringComponentTest extends AbstractClusteringTestCase {
-
-  @Before
-  public void doBefore() {
-    clearIndex();
-  }
-
-  @Test
-  public void testComponent() throws Exception {
-    SolrCore core = h.getCore();
-
-    SearchComponent sc = core.getSearchComponent("clustering");
-    assertTrue("sc is null and it shouldn't be", sc != null);
-    ModifiableSolrParams params = new ModifiableSolrParams();
-
-    params.add(ClusteringComponent.COMPONENT_NAME, "true");
-    params.add(CommonParams.Q, "*:*");
-
-    params.add(ClusteringParams.USE_SEARCH_RESULTS, "true");
-
-
-    SolrRequestHandler handler = core.getRequestHandler("/select");
-    SolrQueryResponse rsp;
-    rsp = new SolrQueryResponse();
-    rsp.addResponseHeader(new SimpleOrderedMap<>());
-    SolrQueryRequest req = new LocalSolrQueryRequest(core, params);
-    handler.handleRequest(req, rsp);
-    NamedList<?> values = rsp.getValues();
-    Object clusters = values.get("clusters");
-    //System.out.println("Clusters: " + clusters);
-    assertTrue("clusters is null and it shouldn't be", clusters != null);
-    req.close();
-
-    params = new ModifiableSolrParams();
-    params.add(ClusteringComponent.COMPONENT_NAME, "true");
-    params.add(ClusteringParams.ENGINE_NAME, "mock");
-    params.add(ClusteringParams.USE_COLLECTION, "true");
-    params.add(QueryComponent.COMPONENT_NAME, "false");
-
-    handler = core.getRequestHandler("docClustering");
-
-    rsp = new SolrQueryResponse();
-    rsp.addResponseHeader(new SimpleOrderedMap<>());
-    req = new LocalSolrQueryRequest(core, params);
-    handler.handleRequest(req, rsp);
-    values = rsp.getValues();
-    clusters = values.get("clusters");
-    //System.out.println("Clusters: " + clusters);
-    assertTrue("clusters is null and it shouldn't be", clusters != null);
-    req.close();
-  }
-
-
-  // tests ClusteringComponent.docListToSolrDocumentList
-  @Test
-  public void testDocListConversion() throws Exception {
-    assertU("", adoc("id", "3234", "url", "ignoreme", "val_i", "1",
-        "val_dynamic", "quick red fox"));
-    assertU("", adoc("id", "3235", "url", "ignoreme", "val_i", "1",
-        "val_dynamic", "quick green fox"));
-    assertU("", adoc("id", "3236", "url", "ignoreme", "val_i", "1",
-        "val_dynamic", "quick brown fox"));
-    assertU("", commit());
-
-    h.getCore().withSearcher(srchr -> {
-      QueryResult qr = new QueryResult();
-      QueryCommand cmd = new QueryCommand();
-      cmd.setQuery(new MatchAllDocsQuery());
-      cmd.setLen(10);
-      qr = srchr.search(qr, cmd);
-
-      DocList docs = qr.getDocList();
-      assertEquals("wrong docs size", 3, docs.size());
-      Set<String> fields = new HashSet<>();
-      fields.add("val_dynamic");
-      fields.add("dynamic_val");
-      fields.add("range_facet_l"); // copied from id
-
-      SolrDocumentList list = ClusteringComponent.docListToSolrDocumentList(docs, srchr, fields, null);
-      assertEquals("wrong list Size", docs.size(), list.size());
-      for (SolrDocument document : list) {
-
-        assertTrue("unexpected field", ! document.containsKey("val_i"));
-        assertTrue("unexpected id field", ! document.containsKey("id"));
-
-        assertTrue("original field", document.containsKey("val_dynamic"));
-        assertTrue("dyn copy field", document.containsKey("dynamic_val"));
-        assertTrue("copy field", document.containsKey("range_facet_l"));
-
-        assertNotNull("original field null", document.get("val_dynamic"));
-        assertNotNull("dyn copy field null", document.get("dynamic_val"));
-        assertNotNull("copy field null", document.get("range_facet_l"));
-      }
-      return null;
-    });
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/DistributedClusteringComponentTest.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/DistributedClusteringComponentTest.java b/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/DistributedClusteringComponentTest.java
deleted file mode 100644
index 89d3ddf..0000000
--- a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/DistributedClusteringComponentTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.handler.clustering;
-
-import org.apache.solr.BaseDistributedSearchTestCase;
-import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
-import org.apache.solr.common.params.CommonParams;
-import org.junit.Test;
-
-@SuppressSSL
-public class DistributedClusteringComponentTest extends
-    BaseDistributedSearchTestCase {
-
-  @Override
-  public String getSolrHome() {
-    return getFile("clustering/solr/collection1").getParent();
-  }
-
-  @Test
-  public void test() throws Exception {
-    del("*:*");
-    int numberOfDocs = 0;
-    for (String[] doc : AbstractClusteringTestCase.DOCUMENTS) {
-      index(id, Integer.toString(numberOfDocs++), "url", doc[0], "title", doc[1], "snippet", doc[2]);
-    }
-    commit();
-    handle.clear();
-    // Only really care about the clusters for this test case, so drop the header and response
-    handle.put("responseHeader", SKIP);
-    handle.put("response", SKIP);
-    query(                                                                                                   
-        ClusteringComponent.COMPONENT_NAME, "true",
-        CommonParams.Q, "*:*",
-        CommonParams.SORT, id + " desc",
-        ClusteringParams.USE_SEARCH_RESULTS, "true");
-    // destroy is not needed because distribTearDown method of base class does it.
-    //destroyServers();
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/887b8089/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java
----------------------------------------------------------------------
diff --git a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java b/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java
deleted file mode 100644
index dc98266..0000000
--- a/solr/contrib/clustering/src/test/org/apache/solr/handler/clustering/MockDocumentClusteringEngine.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.
- */
-package org.apache.solr.handler.clustering;
-import org.apache.solr.common.params.SolrParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.search.DocSet;
-
-
-/**
- *
- *
- **/
-public class MockDocumentClusteringEngine extends DocumentClusteringEngine {
-  @Override
-  public NamedList<?> cluster(DocSet docs, SolrParams solrParams) {
-    return new NamedList<>();
-  }
-
-  @Override
-  public NamedList<?> cluster(SolrParams solrParams) {
-    return new NamedList<>();
-  }
-  
-  @Override
-  public boolean isAvailable() {
-    return true;
-  }
-}