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

svn commit: r1714234 [2/2] - in /lucene/dev/branches/branch_5x: ./ solr/ solr/core/ solr/core/src/java/org/apache/solr/search/ solr/core/src/java/org/apache/solr/search/function/ solr/core/src/test-files/solr/collection1/conf/ solr/core/src/test/org/ap...

Modified: lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java?rev=1714234&r1=1714233&r2=1714234&view=diff
==============================================================================
--- lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java (original)
+++ lucene/dev/branches/branch_5x/solr/core/src/test/org/apache/solr/search/TestCollapseQParserPlugin.java Fri Nov 13 16:11:51 2015
@@ -29,6 +29,9 @@ import org.apache.lucene.util.LuceneTest
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.search.CollapsingQParserPlugin.GroupHeadSelector;
+import org.apache.solr.search.CollapsingQParserPlugin.GroupHeadSelectorType;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -52,29 +55,175 @@ public class TestCollapseQParserPlugin e
     assertU(commit());
   }
 
+  public void testMultiSort() throws Exception {
+    assertU(adoc("id", "1", "group_s", "group1", "test_ti", "5", "test_tl", "10"));
+    assertU(commit());
+    assertU(adoc("id", "2", "group_s", "group1", "test_ti", "5", "test_tl", "1000"));
+    assertU(adoc("id", "3", "group_s", "group1", "test_ti", "5", "test_tl", "1000"));
+    assertU(adoc("id", "4", "group_s", "group1", "test_ti", "10", "test_tl", "100"));
+    //
+    assertU(adoc("id", "5", "group_s", "group2", "test_ti", "5", "test_tl", "10", "term_s", "YYYY"));
+    assertU(commit());
+    assertU(adoc("id", "6", "group_s", "group2", "test_ti", "5", "test_tl","1000"));
+    assertU(adoc("id", "7", "group_s", "group2", "test_ti", "5", "test_tl","1000", "term_s", "XXXX"));
+    assertU(adoc("id", "8", "group_s", "group2", "test_ti", "10","test_tl", "100"));
+    assertU(commit());
+    
+    ModifiableSolrParams params;
+    
+    // group heads are selected using the same sort that is then applied to the final groups
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort=$sort}");
+    params.add("sort", "test_ti asc, test_tl desc, id desc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='7.0']"
+            ,"//result/doc[2]/float[@name='id'][.='3.0']"
+            );
+    
+    // group heads are selected using a complex sort, simpler sort used for final groups
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort='test_ti asc, test_tl desc, id desc'}");
+    params.add("sort", "id asc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='3.0']"
+            ,"//result/doc[2]/float[@name='id'][.='7.0']"
+            );
+
+    // diff up the sort directions, only first clause matters with our data
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort='test_ti desc, test_tl asc, id asc'}");
+    params.add("sort", "id desc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='8.0']"
+            ,"//result/doc[2]/float[@name='id'][.='4.0']"
+            );
+    
+    // tie broken by index order
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort='test_tl desc'}");
+    params.add("sort", "id desc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='6.0']"
+            ,"//result/doc[2]/float[@name='id'][.='2.0']"
+            );
+
+    // score, then tiebreakers; note top level sort by score ASCENDING (just for weirdness)
+    params = new ModifiableSolrParams();
+    params.add("q", "*:* term_s:YYYY");
+    params.add("fq", "{!collapse field=group_s sort='score desc, test_tl desc, test_ti asc, id asc'}");
+    params.add("sort", "score asc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='2.0']"
+            ,"//result/doc[2]/float[@name='id'][.='5.0']"
+            );
+
+    // score, then tiebreakers; note no score in top level sort/fl to check needsScores logic
+    params = new ModifiableSolrParams();
+    params.add("q", "*:* term_s:YYYY");
+    params.add("fq", "{!collapse field=group_s sort='score desc, test_tl desc, test_ti asc, id asc'}");
+    params.add("sort", "id desc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='5.0']"
+            ,"//result/doc[2]/float[@name='id'][.='2.0']"
+            );
+    
+    // term_s desc -- term_s is missing from many docs, and uses sortMissingLast=true
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort='term_s desc, test_tl asc'}");
+    params.add("sort", "id asc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='1.0']"
+            ,"//result/doc[2]/float[@name='id'][.='5.0']"
+            );
+
+    // term_s asc -- term_s is missing from many docs, and uses sortMissingLast=true
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort='term_s asc, test_tl asc'}");
+    params.add("sort", "id asc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='1.0']"
+            ,"//result/doc[2]/float[@name='id'][.='7.0']"
+            );
+
+    // collapse on int field
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=test_ti sort='term_s asc, group_s asc'}");
+    params.add("sort", "id asc");
+    assertQ(req(params)
+            , "*[count(//doc)=2]"
+            ,"//result/doc[1]/float[@name='id'][.='4.0']"
+            ,"//result/doc[2]/float[@name='id'][.='7.0']"
+            );
+    
+    // collapse on term_s (very sparse) with nullPolicy=collapse
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=term_s nullPolicy=collapse sort='test_ti asc, test_tl desc, id asc'}");
+    params.add("sort", "test_tl asc, id asc");
+    assertQ(req(params)
+            , "*[count(//doc)=3]"
+            ,"//result/doc[1]/float[@name='id'][.='5.0']"
+            ,"//result/doc[2]/float[@name='id'][.='2.0']"
+            ,"//result/doc[3]/float[@name='id'][.='7.0']"
+            );
+    
+    // sort local param + elevation
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort='term_s desc, test_tl asc'}");
+    params.add("sort", "test_tl asc");
+    params.add("qt", "/elevate");
+    params.add("forceElevation", "true");
+    params.add("elevateIds", "4.0");
+    assertQ(req(params),
+            "*[count(//doc)=2]",
+            "//result/doc[1]/float[@name='id'][.='4.0']",
+            "//result/doc[2]/float[@name='id'][.='5.0']");
+    //
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field=group_s sort='term_s desc, test_tl asc'}");
+    params.add("sort", "test_tl asc");
+    params.add("qt", "/elevate");
+    params.add("forceElevation", "true");
+    params.add("elevateIds", "7.0");
+    assertQ(req(params),
+            "*[count(//doc)=2]",
+            "//result/doc[1]/float[@name='id'][.='7.0']",
+            "//result/doc[2]/float[@name='id'][.='1.0']");
+
+  }
+
   @Test
   public void testStringCollapse() throws Exception {
-    List<String> types = new ArrayList();
-    types.add("group_s");
-    types.add("group_s_dv");
-    Collections.shuffle(types, random());
-    String group = types.get(0);
-    String hint = (random().nextBoolean() ? " hint="+CollapsingQParserPlugin.HINT_TOP_FC : "");
-    testCollapseQueries(group, hint, false);
+    for (final String hint : new String[] {"", " hint="+CollapsingQParserPlugin.HINT_TOP_FC}) {
+      testCollapseQueries("group_s", hint, false);
+      testCollapseQueries("group_s_dv", hint, false);
+    }
   }
 
-
   @Test
   public void testNumericCollapse() throws Exception {
-    List<String> types = new ArrayList();
-    types.add("group_i");
-    types.add("group_ti_dv");
-    types.add("group_f");
-    types.add("group_tf_dv");
-    Collections.shuffle(types, random());
-    String group = types.get(0);
-    String hint = "";
-    testCollapseQueries(group, hint, true);
+    final String hint = "";
+    testCollapseQueries("group_i", hint, true);
+    testCollapseQueries("group_ti_dv", hint, true);
+    testCollapseQueries("group_f", hint, true);
+    testCollapseQueries("group_tf_dv", hint, true);
   }
 
   @Test
@@ -210,9 +359,6 @@ public class TestCollapseQParserPlugin e
     assertU(commit());
 
 
-
-
-
     //Test collapse by score and following sort by score
     ModifiableSolrParams params = new ModifiableSolrParams();
     params.add("q", "*:*");
@@ -261,6 +407,20 @@ public class TestCollapseQParserPlugin e
         "//result/doc[2]/float[@name='id'][.='1.0']",
         "//result/doc[3]/float[@name='id'][.='5.0']"
     );
+    
+    // Test value source collapse criteria with cscore function but no top level score sort
+    params = new ModifiableSolrParams();
+    params.add("q", "*:*");
+    params.add("fq", "{!collapse field="+group+" nullPolicy=collapse min=cscore()"+hint+"}");
+    params.add("defType", "edismax");
+    params.add("bf", "field(test_ti)");
+    params.add("fl", "id");
+    params.add("sort", "id desc");
+    assertQ(req(params), "*[count(//doc)=3]",
+        "//result/doc[1]/float[@name='id'][.='5.0']",
+        "//result/doc[2]/float[@name='id'][.='4.0']",
+        "//result/doc[3]/float[@name='id'][.='1.0']"
+    );
 
     // Test value source collapse criteria with compound cscore function
     params = new ModifiableSolrParams();
@@ -290,34 +450,56 @@ public class TestCollapseQParserPlugin e
                          "//result/doc[4]/float[@name='id'][.='6.0']");
 
     //Test SOLR-5773 with score collapse criteria
-    params = new ModifiableSolrParams();
-    params.add("q", "YYYY");
-    params.add("fq", "{!collapse field="+group+" nullPolicy=collapse"+hint+"}");
-    params.add("defType", "edismax");
-    params.add("bf", "field(test_ti)");
-    params.add("qf", "term_s");
-    params.add("qt", "/elevate");
-    params.add("elevateIds", "1,5");
-    assertQ(req(params), "*[count(//doc)=3]",
-        "//result/doc[1]/float[@name='id'][.='1.0']",
-        "//result/doc[2]/float[@name='id'][.='5.0']",
-        "//result/doc[3]/float[@name='id'][.='3.0']");
-
+    // try both default & sort localparams as alternate ways to ask for max score
+    for (String maxscore : new String[] {"  ", " sort='score desc' "}) {
+      params = new ModifiableSolrParams();
+      params.add("q", "YYYY");
+      params.add("fq", "{!collapse field="+group + maxscore + " nullPolicy=collapse"+hint+"}");
+      params.add("defType", "edismax");
+      params.add("bf", "field(test_ti)");
+      params.add("qf", "term_s");
+      params.add("qt", "/elevate");
+      params.add("elevateIds", "1,5");
+      assertQ(req(params), "*[count(//doc)=3]",
+              "//result/doc[1]/float[@name='id'][.='1.0']",
+              "//result/doc[2]/float[@name='id'][.='5.0']",
+              "//result/doc[3]/float[@name='id'][.='3.0']");
+    }
+    
     //Test SOLR-5773 with max field collapse criteria
-    params = new ModifiableSolrParams();
-    params.add("q", "YYYY");
-    params.add("fq", "{!collapse field="+group+" min=test_ti nullPolicy=collapse"+hint+"}");
-    params.add("defType", "edismax");
-    params.add("bf", "field(test_ti)");
-    params.add("qf", "term_s");
-    params.add("qt", "/elevate");
-    params.add("elevateIds", "1,5");
-    assertQ(req(params), "*[count(//doc)=3]",
-        "//result/doc[1]/float[@name='id'][.='1.0']",
-        "//result/doc[2]/float[@name='id'][.='5.0']",
-        "//result/doc[3]/float[@name='id'][.='4.0']");
-
-
+    // try both max & sort localparams as alternate ways to ask for max group head
+    for (String max : new String[] {" max=test_ti ", " sort='test_ti desc' "}) {
+      params = new ModifiableSolrParams();
+      params.add("q", "YYYY");
+      params.add("fq", "{!collapse field=" + group + max + "nullPolicy=collapse"+hint+"}");
+      params.add("defType", "edismax");
+      params.add("bf", "field(test_ti)");
+      params.add("qf", "term_s");
+      params.add("qt", "/elevate");
+      params.add("elevateIds", "1,5");
+      assertQ(req(params), "*[count(//doc)=3]",
+              "//result/doc[1]/float[@name='id'][.='1.0']",
+              "//result/doc[2]/float[@name='id'][.='5.0']",
+              "//result/doc[3]/float[@name='id'][.='3.0']");
+    }
+    
+    //Test SOLR-5773 with min field collapse criteria
+    // try both min & sort localparams as alternate ways to ask for min group head
+    for (String min : new String[] {" min=test_ti ", " sort='test_ti asc' "}) {
+      params = new ModifiableSolrParams();
+      params.add("q", "YYYY");
+      params.add("fq", "{!collapse field=" + group + min + "nullPolicy=collapse"+hint+"}");
+      params.add("defType", "edismax");
+      params.add("bf", "field(test_ti)");
+      params.add("qf", "term_s");
+      params.add("qt", "/elevate");
+      params.add("elevateIds", "1,5");
+      assertQ(req(params), "*[count(//doc)=3]",
+              "//result/doc[1]/float[@name='id'][.='1.0']",
+              "//result/doc[2]/float[@name='id'][.='5.0']",
+              "//result/doc[3]/float[@name='id'][.='4.0']");
+    }
+    
     //Test SOLR-5773 elevating documents with null group
     params = new ModifiableSolrParams();
     params.add("q", "YYYY");
@@ -334,45 +516,72 @@ public class TestCollapseQParserPlugin e
         "//result/doc[4]/float[@name='id'][.='6.0']");
 
 
-
-    //Test collapse by min int field and sort
+    // Non trivial sort local param for picking group head
     params = new ModifiableSolrParams();
     params.add("q", "*:*");
-    params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
+    params.add("fq", "{!collapse field="+group+" nullPolicy=collapse sort='term_s asc, test_ti asc' "+hint+"}");
     params.add("sort", "id desc");
-    assertQ(req(params), "*[count(//doc)=2]",
-                           "//result/doc[1]/float[@name='id'][.='5.0']",
-                           "//result/doc[2]/float[@name='id'][.='1.0']");
-
-    params = new ModifiableSolrParams();
-    params.add("q", "*:*");
-    params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
-    params.add("sort", "id asc");
-    assertQ(req(params), "*[count(//doc)=2]",
-                         "//result/doc[1]/float[@name='id'][.='1.0']",
-                         "//result/doc[2]/float[@name='id'][.='5.0']");
-
+    assertQ(req(params),
+            "*[count(//doc)=3]",
+            "//result/doc[1]/float[@name='id'][.='5.0']",
+            "//result/doc[2]/float[@name='id'][.='4.0']",
+            "//result/doc[3]/float[@name='id'][.='1.0']"
+    );
+    // 
     params = new ModifiableSolrParams();
     params.add("q", "*:*");
-    params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
-    params.add("sort", "test_tl asc,id desc");
-    assertQ(req(params), "*[count(//doc)=2]",
-        "//result/doc[1]/float[@name='id'][.='5.0']",
-        "//result/doc[2]/float[@name='id'][.='1.0']");
-
+    params.add("fq", "{!collapse field="+group+" nullPolicy=collapse sort='term_s asc, test_ti desc' "+hint+"}");
+    params.add("sort", "id desc");
+    assertQ(req(params),
+            "*[count(//doc)=3]",
+            "//result/doc[1]/float[@name='id'][.='6.0']",
+            "//result/doc[2]/float[@name='id'][.='3.0']",
+            "//result/doc[3]/float[@name='id'][.='2.0']"
+    );
+    
 
 
-    params = new ModifiableSolrParams();
-    params.add("q", "*:*");
-    params.add("fq", "{!collapse field="+group+" min=test_ti"+hint+"}");
-    params.add("sort", "score desc,id asc");
-    params.add("defType", "edismax");
-    params.add("bf", "field(id)");
-    assertQ(req(params), "*[count(//doc)=2]",
-                          "//result/doc[1]/float[@name='id'][.='5.0']",
-                          "//result/doc[2]/float[@name='id'][.='1.0']");
+    // Test collapse by min int field and top level sort
+    // try both min & sort localparams as alternate ways to ask for min group head
+    for (String min : new String[] {" min=test_ti ", " sort='test_ti asc' "}) {
+      params = new ModifiableSolrParams();
+      params.add("q", "*:*");
+      params.add("fq", "{!collapse field="+group + min + hint+"}");
+      params.add("sort", "id desc");
+      assertQ(req(params),
+              "*[count(//doc)=2]",
+              "//result/doc[1]/float[@name='id'][.='5.0']",
+              "//result/doc[2]/float[@name='id'][.='1.0']");
 
+      params = new ModifiableSolrParams();
+      params.add("q", "*:*");
+      params.add("fq", "{!collapse field="+group + min + hint+"}");
+      params.add("sort", "id asc");
+      assertQ(req(params),
+              "*[count(//doc)=2]",
+              "//result/doc[1]/float[@name='id'][.='1.0']",
+              "//result/doc[2]/float[@name='id'][.='5.0']");
+      
+      params = new ModifiableSolrParams();
+      params.add("q", "*:*");
+      params.add("fq", "{!collapse field="+group + min + hint+"}");
+      params.add("sort", "test_tl asc,id desc");
+      assertQ(req(params),
+              "*[count(//doc)=2]",
+              "//result/doc[1]/float[@name='id'][.='5.0']",
+              "//result/doc[2]/float[@name='id'][.='1.0']");
 
+      params = new ModifiableSolrParams();
+      params.add("q", "*:*");
+      params.add("fq", "{!collapse field="+group + min + hint+"}");
+      params.add("sort", "score desc,id asc");
+      params.add("defType", "edismax");
+      params.add("bf", "field(id)");
+      assertQ(req(params),
+              "*[count(//doc)=2]",
+              "//result/doc[1]/float[@name='id'][.='5.0']",
+              "//result/doc[2]/float[@name='id'][.='1.0']");
+    }
 
 
     //Test collapse by max int field
@@ -420,9 +629,6 @@ public class TestCollapseQParserPlugin e
                          "//result/doc[1]/float[@name='id'][.='2.0']",
                          "//result/doc[2]/float[@name='id'][.='6.0']");
 
-
-
-
     //Test collapse by min float field
     params = new ModifiableSolrParams();
     params.add("q", "*:*");
@@ -446,7 +652,42 @@ public class TestCollapseQParserPlugin e
     assertQ(req(params), "*[count(//doc)=2]",
         "//result/doc[1]/float[@name='id'][.='5.0']",
         "//result/doc[2]/float[@name='id'][.='1.0']");
+    
+    // attempting to use cscore() in sort local param should fail
+    assertQEx("expected error trying to sort on a function that includes cscore()",
+              req(params("q", "{!func}sub(sub(test_tl,1000),id)",
+                         "fq", "{!collapse field="+group+" sort='abs(cscore()) asc, id asc'}",
+                         "sort", "score asc")),
+              SolrException.ErrorCode.BAD_REQUEST);
+    
+    // multiple params for picking groupHead should all fail
+    for (String bad : new String[] {
+        "{!collapse field="+group+" min=test_tf max=test_tf}",
+        "{!collapse field="+group+" min=test_tf sort='test_tf asc'}",
+        "{!collapse field="+group+" max=test_tf sort='test_tf asc'}" }) {
+      assertQEx("Expected error: " + bad, req(params("q", "*:*", "fq", bad)),
+                SolrException.ErrorCode.BAD_REQUEST);
+    }
 
+    // multiple params for picking groupHead should work as long as only one is non-null
+    // sort used
+    for (SolrParams collapse : new SolrParams[] {
+        // these should all be equivilently valid
+        params("fq", "{!collapse field="+group+" nullPolicy=collapse sort='test_ti asc'"+hint+"}"),
+        params("fq", "{!collapse field="+group+" nullPolicy=collapse min='' sort='test_ti asc'"+hint+"}"),
+        params("fq", "{!collapse field="+group+" nullPolicy=collapse max='' sort='test_ti asc'"+hint+"}"),
+        params("fq", "{!collapse field="+group+" nullPolicy=collapse min=$x sort='test_ti asc'"+hint+"}"),
+        params("fq", "{!collapse field="+group+" nullPolicy=collapse min=$x sort='test_ti asc'"+hint+"}",
+               "x",""),
+      }) {
+      
+      assertQ(req(collapse, "q", "*:*", "sort", "test_ti desc"),
+              "*[count(//doc)=3]",
+              "//result/doc[1]/float[@name='id'][.='4.0']",
+              "//result/doc[2]/float[@name='id'][.='1.0']",
+              "//result/doc[3]/float[@name='id'][.='5.0']");
+    }
+    
 
     //Test nullPolicy expand
     params = new ModifiableSolrParams();
@@ -460,7 +701,6 @@ public class TestCollapseQParserPlugin e
         "//result/doc[4]/float[@name='id'][.='1.0']");
 
     //Test nullPolicy collapse
-
     params = new ModifiableSolrParams();
     params.add("q", "*:*");
     params.add("fq", "{!collapse field="+group+" max=test_tf nullPolicy=collapse"+hint+"}");
@@ -533,5 +773,44 @@ public class TestCollapseQParserPlugin e
     assertQ(req(params), "*[count(//doc)=0]");
   }
 
+  public void testGroupHeadSelector() {
+    GroupHeadSelector s;
+    
+    try {
+      s = GroupHeadSelector.build(params("sort", "foo_s asc", "min", "bar_s"));
+      fail("no exception with multi criteria");
+    } catch (SolrException e) {
+      // expected
+    }
+    
+    s = GroupHeadSelector.build(params("min", "foo_s"));
+    assertEquals(GroupHeadSelectorType.MIN, s.type);
+    assertEquals("foo_s", s.selectorText);
+
+    s = GroupHeadSelector.build(params("max", "foo_s"));
+    assertEquals(GroupHeadSelectorType.MAX, s.type);
+    assertEquals("foo_s", s.selectorText);
+    assertFalse(s.equals(GroupHeadSelector.build(params("min", "foo_s", "other", "stuff"))));
+
+    s = GroupHeadSelector.build(params());
+    assertEquals(GroupHeadSelectorType.SCORE, s.type);
+    assertNotNull(s.selectorText);
+    assertEquals(GroupHeadSelector.build(params()), s);
+    assertFalse(s.equals(GroupHeadSelector.build(params("min", "BAR_s"))));
+
+    s = GroupHeadSelector.build(params("sort", "foo_s asc"));
+    assertEquals(GroupHeadSelectorType.SORT, s.type);
+    assertEquals("foo_s asc", s.selectorText);
+    assertEquals(GroupHeadSelector.build(params("sort", "foo_s asc")),
+                 s);
+    assertFalse(s.equals(GroupHeadSelector.build(params("sort", "BAR_s asc"))));
+    assertFalse(s.equals(GroupHeadSelector.build(params("min", "BAR_s"))));
+    assertFalse(s.equals(GroupHeadSelector.build(params())));
+
+    assertEquals(GroupHeadSelector.build(params("sort", "foo_s asc")).hashCode(),
+                 GroupHeadSelector.build(params("sort", "foo_s asc",
+                                                "other", "stuff")).hashCode());
+    
+  }
 
 }