You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by kr...@apache.org on 2016/11/03 14:40:50 UTC

[03/20] lucene-solr:jira/solr-8593: SOLR-8542: Adds Solr Learning to Rank (LTR) plugin for reranking results with machine learning models. (Michael Nilsson, Diego Ceccarelli, Joshua Pantony, Jon Dorando, Naveen Santhapuri, Alessandro Benedetti, David Gro

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java
new file mode 100644
index 0000000..437e10d
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ltr.feature;
+
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.model.LinearModel;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+public class TestRankingFeature extends TestRerankBase {
+
+  @BeforeClass
+  public static void before() throws Exception {
+    setuptest("solrconfig-ltr.xml", "schema.xml");
+
+    assertU(adoc("id", "1", "title", "w1", "description", "w1", "popularity",
+        "1"));
+    assertU(adoc("id", "2", "title", "w2 2asd asdd didid", "description",
+        "w2 2asd asdd didid", "popularity", "2"));
+    assertU(adoc("id", "3", "title", "w3", "description", "w3", "popularity",
+        "3"));
+    assertU(adoc("id", "4", "title", "w4", "description", "w4", "popularity",
+        "4"));
+    assertU(adoc("id", "5", "title", "w5", "description", "w5", "popularity",
+        "5"));
+    assertU(adoc("id", "6", "title", "w1 w2", "description", "w1 w2",
+        "popularity", "6"));
+    assertU(adoc("id", "7", "title", "w1 w2 w3 w4 w5", "description",
+        "w1 w2 w3 w4 w5 w8", "popularity", "7"));
+    assertU(adoc("id", "8", "title", "w1 w1 w1 w2 w2 w8", "description",
+        "w1 w1 w1 w2 w2", "popularity", "8"));
+    assertU(commit());
+  }
+
+  @AfterClass
+  public static void after() throws Exception {
+    aftertest();
+  }
+
+  @Test
+  public void testRankingSolrFeature() throws Exception {
+    // before();
+    loadFeature("powpularityS", SolrFeature.class.getCanonicalName(),
+        "{\"q\":\"{!func}pow(popularity,2)\"}");
+    loadFeature("unpopularityS", SolrFeature.class.getCanonicalName(),
+        "{\"q\":\"{!func}div(1,popularity)\"}");
+
+    loadModel("powpularityS-model", LinearModel.class.getCanonicalName(),
+        new String[] {"powpularityS"}, "{\"weights\":{\"powpularityS\":1.0}}");
+    loadModel("unpopularityS-model", LinearModel.class.getCanonicalName(),
+        new String[] {"unpopularityS"}, "{\"weights\":{\"unpopularityS\":1.0}}");
+
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score");
+    query.add("rows", "4");
+
+    assertJQ("/query" + query.toQueryString(), "/response/numFound/==4");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='1'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/id=='8'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/id=='6'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[3]/id=='7'");
+    // Normal term match
+
+    query.add("rq", "{!ltr model=powpularityS-model reRankDocs=4}");
+    query.set("debugQuery", "on");
+
+    assertJQ("/query" + query.toQueryString(), "/response/numFound/==4");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='8'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==64.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/id=='7'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==49.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/id=='6'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==36.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[3]/id=='1'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[3]/score==1.0");
+
+    query.remove("rq");
+    query.add("rq", "{!ltr model=unpopularityS-model reRankDocs=4}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/numFound/==4");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='1'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==1.0");
+
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/id=='6'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/id=='7'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[3]/id=='8'");
+
+    //bad solr ranking feature
+    loadFeature("powdesS", SolrFeature.class.getCanonicalName(),
+        "{\"q\":\"{!func}pow(description,2)\"}");
+    loadModel("powdesS-model", LinearModel.class.getCanonicalName(),
+        new String[] {"powdesS"}, "{\"weights\":{\"powdesS\":1.0}}");
+
+    query.remove("rq");
+    query.add("rq", "{!ltr model=powdesS-model reRankDocs=4}");
+
+    assertJQ("/query" + query.toQueryString(),
+        "/error/msg/=='"+FeatureException.class.getCanonicalName()+": " +
+        "java.lang.UnsupportedOperationException: " +
+        "Unable to extract feature for powdesS'");
+    // aftertest();
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java
new file mode 100644
index 0000000..754409a
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.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.ltr.feature;
+
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.model.LinearModel;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestUserTermScoreWithQ extends TestRerankBase {
+
+  @BeforeClass
+  public static void before() throws Exception {
+    setuptest("solrconfig-ltr.xml", "schema.xml");
+
+    assertU(adoc("id", "1", "title", "w1", "description", "w1", "popularity",
+        "1"));
+    assertU(adoc("id", "2", "title", "w2 2asd asdd didid", "description",
+        "w2 2asd asdd didid", "popularity", "2"));
+    assertU(adoc("id", "3", "title", "w3", "description", "w3", "popularity",
+        "3"));
+    assertU(adoc("id", "4", "title", "w4", "description", "w4", "popularity",
+        "4"));
+    assertU(adoc("id", "5", "title", "w5", "description", "w5", "popularity",
+        "5"));
+    assertU(adoc("id", "6", "title", "w1 w2", "description", "w1 w2",
+        "popularity", "6"));
+    assertU(adoc("id", "7", "title", "w1 w2 w3 w4 w5", "description",
+        "w1 w2 w3 w4 w5 w8", "popularity", "7"));
+    assertU(adoc("id", "8", "title", "w1 w1 w1 w2 w2 w8", "description",
+        "w1 w1 w1 w2 w2", "popularity", "8"));
+    assertU(commit());
+  }
+
+  @AfterClass
+  public static void after() throws Exception {
+    aftertest();
+  }
+
+  @Test
+  public void testUserTermScoreWithQ() throws Exception {
+    // before();
+    loadFeature("SomeTermQ", SolrFeature.class.getCanonicalName(),
+        "{\"q\":\"{!terms f=popularity}88888\"}");
+    loadModel("Term-modelQ", LinearModel.class.getCanonicalName(),
+        new String[] {"SomeTermQ"}, "{\"weights\":{\"SomeTermQ\":1.0}}");
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score");
+    query.add("rows", "4");
+    query.add("rq", "{!ltr model=Term-modelQ reRankDocs=4}");
+    query.set("debugQuery", "on");
+
+    assertJQ("/query" + query.toQueryString(), "/response/numFound/==4");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==0.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==0.0");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java
new file mode 100644
index 0000000..c79207c
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.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.ltr.feature;
+
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.model.LinearModel;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestUserTermScorerQuery extends TestRerankBase  {
+
+  @BeforeClass
+  public static void before() throws Exception {
+    setuptest("solrconfig-ltr.xml", "schema.xml");
+
+    assertU(adoc("id", "1", "title", "w1", "description", "w1", "popularity",
+        "1"));
+    assertU(adoc("id", "2", "title", "w2 2asd asdd didid", "description",
+        "w2 2asd asdd didid", "popularity", "2"));
+    assertU(adoc("id", "3", "title", "w3", "description", "w3", "popularity",
+        "3"));
+    assertU(adoc("id", "4", "title", "w4", "description", "w4", "popularity",
+        "4"));
+    assertU(adoc("id", "5", "title", "w5", "description", "w5", "popularity",
+        "5"));
+    assertU(adoc("id", "6", "title", "w1 w2", "description", "w1 w2",
+        "popularity", "6"));
+    assertU(adoc("id", "7", "title", "w1 w2 w3 w4 w5", "description",
+        "w1 w2 w3 w4 w5 w8", "popularity", "7"));
+    assertU(adoc("id", "8", "title", "w1 w1 w1 w2 w2 w8", "description",
+        "w1 w1 w1 w2 w2", "popularity", "8"));
+    assertU(commit());
+  }
+
+  @AfterClass
+  public static void after() throws Exception {
+    aftertest();
+  }
+
+  @Test
+  public void testUserTermScorerQuery() throws Exception {
+    // before();
+    loadFeature("matchedTitleDFExt", SolrFeature.class.getCanonicalName(),
+        "{\"q\":\"${user_query}\",\"df\":\"title\"}");
+    loadModel("Term-matchedTitleDFExt", LinearModel.class.getCanonicalName(),
+        new String[] {"matchedTitleDFExt"},
+        "{\"weights\":{\"matchedTitleDFExt\":1.1}}");
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score");
+    query.add("rows", "4");
+    query.add("rq",
+        "{!ltr model=Term-matchedTitleDFExt reRankDocs=4 efi.user_query=w8}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/numFound/==4");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='8'");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java
new file mode 100644
index 0000000..f47a883
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java
@@ -0,0 +1,75 @@
+/*
+ * 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.ltr.feature;
+
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.model.LinearModel;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestUserTermScorereQDF extends TestRerankBase {
+
+  @BeforeClass
+  public static void before() throws Exception {
+    setuptest("solrconfig-ltr.xml", "schema.xml");
+
+    assertU(adoc("id", "1", "title", "w1", "description", "w1", "popularity",
+        "1"));
+    assertU(adoc("id", "2", "title", "w2 2asd asdd didid", "description",
+        "w2 2asd asdd didid", "popularity", "2"));
+    assertU(adoc("id", "3", "title", "w3", "description", "w3", "popularity",
+        "3"));
+    assertU(adoc("id", "4", "title", "w4", "description", "w4", "popularity",
+        "4"));
+    assertU(adoc("id", "5", "title", "w5", "description", "w5", "popularity",
+        "5"));
+    assertU(adoc("id", "6", "title", "w1 w2", "description", "w1 w2",
+        "popularity", "6"));
+    assertU(adoc("id", "7", "title", "w1 w2 w3 w4 w5", "description",
+        "w1 w2 w3 w4 w5 w8", "popularity", "7"));
+    assertU(adoc("id", "8", "title", "w1 w1 w1 w2 w2 w8", "description",
+        "w1 w1 w1 w2 w2", "popularity", "8"));
+    assertU(commit());
+  }
+
+  @AfterClass
+  public static void after() throws Exception {
+    aftertest();
+  }
+
+  @Test
+  public void testUserTermScorerQWithDF() throws Exception {
+    // before();
+    loadFeature("matchedTitleDF", SolrFeature.class.getCanonicalName(),
+        "{\"q\":\"w5\",\"df\":\"title\"}");
+    loadModel("Term-matchedTitleDF", LinearModel.class.getCanonicalName(),
+        new String[] {"matchedTitleDF"},
+        "{\"weights\":{\"matchedTitleDF\":1.0}}");
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score");
+    query.add("rows", "2");
+    query.add("rq", "{!ltr model=Term-matchedTitleDF reRankDocs=4}");
+    query.set("debugQuery", "on");
+
+    assertJQ("/query" + query.toQueryString(), "/response/numFound/==4");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='7'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==0.0");
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java
new file mode 100644
index 0000000..084da4a
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java
@@ -0,0 +1,165 @@
+/*
+ * 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.ltr.feature;
+
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.model.LinearModel;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestValueFeature extends TestRerankBase {
+
+  @BeforeClass
+  public static void before() throws Exception {
+    setuptest("solrconfig-ltr.xml", "schema.xml");
+
+    assertU(adoc("id", "1", "title", "w1"));
+    assertU(adoc("id", "2", "title", "w2"));
+    assertU(adoc("id", "3", "title", "w3"));
+    assertU(adoc("id", "4", "title", "w4"));
+    assertU(adoc("id", "5", "title", "w5"));
+    assertU(adoc("id", "6", "title", "w1 w2"));
+    assertU(adoc("id", "7", "title", "w1 w2 w3 w4 w5"));
+    assertU(adoc("id", "8", "title", "w1 w1 w1 w2 w2"));
+    assertU(commit());
+  }
+
+  @AfterClass
+  public static void after() throws Exception {
+    aftertest();
+  }
+
+  @Test
+  public void testValueFeatureWithEmptyValue() throws Exception {
+    final RuntimeException expectedException =
+        new RuntimeException("mismatch: '0'!='500' @ responseHeader/status");
+    try {
+        loadFeature("c2", ValueFeature.class.getCanonicalName(), "{\"value\":\"\"}");
+        fail("testValueFeatureWithEmptyValue failed to throw exception: "+expectedException);
+    } catch (RuntimeException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void testValueFeatureWithWhitespaceValue() throws Exception {
+    final RuntimeException expectedException =
+        new RuntimeException("mismatch: '0'!='500' @ responseHeader/status");
+    try {
+        loadFeature("c2", ValueFeature.class.getCanonicalName(),
+              "{\"value\":\" \"}");
+        fail("testValueFeatureWithWhitespaceValue failed to throw exception: "+expectedException);
+    } catch (RuntimeException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void testRerankingWithConstantValueFeatureReplacesDocScore() throws Exception {
+    loadFeature("c3", ValueFeature.class.getCanonicalName(), "c3",
+        "{\"value\":2}");
+    loadModel("m3", LinearModel.class.getCanonicalName(), new String[] {"c3"},
+        "c3", "{\"weights\":{\"c3\":1.0}}");
+
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score");
+    query.add("rows", "4");
+    query.add("wt", "json");
+    query.add("rq", "{!ltr model=m3 reRankDocs=4}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==2.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==2.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==2.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[3]/score==2.0");
+  }
+
+  @Test
+  public void testRerankingWithEfiValueFeatureReplacesDocScore() throws Exception {
+    loadFeature("c6", ValueFeature.class.getCanonicalName(), "c6",
+        "{\"value\":\"${val6}\"}");
+    loadModel("m6", LinearModel.class.getCanonicalName(), new String[] {"c6"},
+        "c6", "{\"weights\":{\"c6\":1.0}}");
+
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score");
+    query.add("rows", "4");
+    query.add("wt", "json");
+    query.add("rq", "{!ltr model=m6 reRankDocs=4 efi.val6='2'}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==2.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==2.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==2.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[3]/score==2.0");
+  }
+
+
+  @Test
+  public void testValueFeatureImplicitlyNotRequiredShouldReturnOkStatusCode() throws Exception {
+    loadFeature("c5", ValueFeature.class.getCanonicalName(), "c5",
+        "{\"value\":\"${val6}\"}");
+    loadModel("m5", LinearModel.class.getCanonicalName(), new String[] {"c5"},
+        "c5", "{\"weights\":{\"c5\":1.0}}");
+
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score,fvonly:[fvonly]");
+    query.add("rows", "4");
+    query.add("wt", "json");
+    query.add("rq", "{!ltr model=m5 reRankDocs=4}");
+
+    assertJQ("/query" + query.toQueryString(), "/responseHeader/status==0");
+  }
+
+  @Test
+  public void testValueFeatureExplictlyNotRequiredShouldReturnOkStatusCode() throws Exception {
+    loadFeature("c7", ValueFeature.class.getCanonicalName(), "c7",
+        "{\"value\":\"${val7}\",\"required\":false}");
+    loadModel("m7", LinearModel.class.getCanonicalName(), new String[] {"c7"},
+        "c7", "{\"weights\":{\"c7\":1.0}}");
+
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score,fvonly:[fvonly]");
+    query.add("rows", "4");
+    query.add("wt", "json");
+    query.add("rq", "{!ltr model=m7 reRankDocs=4}");
+
+    assertJQ("/query" + query.toQueryString(), "/responseHeader/status==0");
+  }
+
+  @Test
+  public void testValueFeatureRequiredShouldReturn400StatusCode() throws Exception {
+    loadFeature("c8", ValueFeature.class.getCanonicalName(), "c8",
+        "{\"value\":\"${val8}\",\"required\":true}");
+    loadModel("m8", LinearModel.class.getCanonicalName(), new String[] {"c8"},
+        "c8", "{\"weights\":{\"c8\":1.0}}");
+
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("title:w1");
+    query.add("fl", "*, score,fvonly:[fvonly]");
+    query.add("rows", "4");
+    query.add("wt", "json");
+    query.add("rq", "{!ltr model=m8 reRankDocs=4}");
+
+    assertJQ("/query" + query.toQueryString(), "/responseHeader/status==400");
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java
new file mode 100644
index 0000000..e8ee224
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java
@@ -0,0 +1,207 @@
+/*
+ * 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.ltr.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.norm.IdentityNormalizer;
+import org.apache.solr.ltr.norm.Normalizer;
+import org.apache.solr.ltr.store.FeatureStore;
+import org.apache.solr.ltr.store.rest.ManagedModelStore;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestLinearModel extends TestRerankBase {
+
+  public static LTRScoringModel createLinearModel(String name, List<Feature> features,
+      List<Normalizer> norms,
+      String featureStoreName, List<Feature> allFeatures,
+      Map<String,Object> params) throws ModelException {
+    final LTRScoringModel model = LTRScoringModel.getInstance(solrResourceLoader,
+        LinearModel.class.getCanonicalName(),
+        name,
+        features, norms, featureStoreName, allFeatures, params);
+    return model;
+  }
+
+  static ManagedModelStore store = null;
+  static FeatureStore fstore = null;
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    setuptest();
+    // loadFeatures("features-store-test-model.json");
+    store = getManagedModelStore();
+    fstore = getManagedFeatureStore().getFeatureStore("test");
+
+  }
+
+  @Test
+  public void getInstanceTest() {
+    final Map<String,Object> weights = new HashMap<>();
+    weights.put("constant1", 1d);
+    weights.put("constant5", 1d);
+
+    Map<String,Object> params = new HashMap<String,Object>();
+    final List<Feature> features = getFeatures(new String[] {
+        "constant1", "constant5"});
+    final List<Normalizer> norms =
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+    params.put("weights", weights);
+    final LTRScoringModel ltrScoringModel = createLinearModel("test1",
+        features, norms, "test", fstore.getFeatures(),
+        params);
+
+    store.addModel(ltrScoringModel);
+    final LTRScoringModel m = store.getModel("test1");
+    assertEquals(ltrScoringModel, m);
+  }
+
+  @Test
+  public void nullFeatureWeightsTest() {
+    final ModelException expectedException =
+        new ModelException("Model test2 doesn't contain any weights");
+    try {
+      final List<Feature> features = getFeatures(new String[]
+          {"constant1", "constant5"});
+      final List<Normalizer> norms =
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      createLinearModel("test2",
+          features, norms, "test", fstore.getFeatures(), null);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void existingNameTest() {
+    final SolrException expectedException =
+        new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+            ModelException.class.getCanonicalName()+": model 'test3' already exists. Please use a different name");
+    try {
+      final List<Feature> features = getFeatures(new String[]
+          {"constant1", "constant5"});
+      final List<Normalizer> norms =
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      final LTRScoringModel ltrScoringModel = createLinearModel("test3",
+          features, norms, "test", fstore.getFeatures(),
+              params);
+      store.addModel(ltrScoringModel);
+      final LTRScoringModel m = store.getModel("test3");
+      assertEquals(ltrScoringModel, m);
+      store.addModel(ltrScoringModel);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (SolrException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void duplicateFeatureTest() {
+    final ModelException expectedException =
+        new ModelException("duplicated feature constant1 in model test4");
+    try {
+      final List<Feature> features = getFeatures(new String[]
+          {"constant1", "constant1"});
+      final List<Normalizer> norms =
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      final LTRScoringModel ltrScoringModel = createLinearModel("test4",
+          features, norms, "test", fstore.getFeatures(),
+              params);
+      store.addModel(ltrScoringModel);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+
+  }
+
+  @Test
+  public void missingFeatureWeightTest() {
+    final ModelException expectedException =
+        new ModelException("Model test5 lacks weight(s) for [constant5]");
+    try {
+      final List<Feature> features = getFeatures(new String[]
+          {"constant1", "constant5"});
+      final List<Normalizer> norms =
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5missing", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      createLinearModel("test5",
+          features, norms, "test", fstore.getFeatures(),
+              params);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void emptyFeaturesTest() {
+    final ModelException expectedException =
+        new ModelException("no features declared for model test6");
+    try {
+      final List<Feature> features = getFeatures(new String[] {});
+      final List<Normalizer> norms =
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5missing", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      final LTRScoringModel ltrScoringModel = createLinearModel("test6",
+          features, norms, "test", fstore.getFeatures(),
+          params);
+      store.addModel(ltrScoringModel);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java
new file mode 100644
index 0000000..3748331
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java
@@ -0,0 +1,246 @@
+/*
+ * 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.ltr.model;
+
+//import static org.junit.internal.matchers.StringContains.containsString;
+
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.ltr.TestRerankBase;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class TestMultipleAdditiveTreesModel extends TestRerankBase {
+
+
+  @BeforeClass
+  public static void before() throws Exception {
+    setuptest("solrconfig-ltr.xml", "schema.xml");
+
+    assertU(adoc("id", "1", "title", "w1", "description", "w1", "popularity","1"));
+    assertU(adoc("id", "2", "title", "w2", "description", "w2", "popularity","2"));
+    assertU(adoc("id", "3", "title", "w3", "description", "w3", "popularity","3"));
+    assertU(adoc("id", "4", "title", "w4", "description", "w4", "popularity","4"));
+    assertU(adoc("id", "5", "title", "w5", "description", "w5", "popularity","5"));
+    assertU(commit());
+
+    loadFeatures("multipleadditivetreesmodel_features.json"); // currently needed to force
+    // scoring on all docs
+    loadModels("multipleadditivetreesmodel.json");
+  }
+
+  @AfterClass
+  public static void after() throws Exception {
+    aftertest();
+  }
+
+
+  @Test
+  public void testMultipleAdditiveTreesScoringWithAndWithoutEfiFeatureMatches() throws Exception {
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("*:*");
+    query.add("rows", "3");
+    query.add("fl", "*,score");
+
+    // Regular scores
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==1.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==1.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==1.0");
+
+    // No match scores since user_query not passed in to external feature info
+    // and feature depended on it.
+    query.add("rq", "{!ltr reRankDocs=3 model=multipleadditivetreesmodel efi.user_query=dsjkafljjk}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==-120.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==-120.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==-120.0");
+
+    // Matched user query since it was passed in
+    query.remove("rq");
+    query.add("rq", "{!ltr reRankDocs=3 model=multipleadditivetreesmodel efi.user_query=w3}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==-20.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==-120.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==-120.0");
+  }
+
+  @Ignore
+  @Test
+  public void multipleAdditiveTreesTestExplain() throws Exception {
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("*:*");
+    query.add("fl", "*,score,[fv]");
+    query.add("rows", "3");
+
+    query.add("rq",
+        "{!ltr reRankDocs=3 model=multipleadditivetreesmodel efi.user_query=w3}");
+
+    // test out the explain feature, make sure it returns something
+    query.setParam("debugQuery", "on");
+    String qryResult = JQ("/query" + query.toQueryString());
+
+    qryResult = qryResult.replaceAll("\n", " ");
+    // FIXME containsString doesn't exist.
+    // assertThat(qryResult, containsString("\"debug\":{"));
+    // qryResult = qryResult.substring(qryResult.indexOf("debug"));
+    //
+    // assertThat(qryResult, containsString("\"explain\":{"));
+    // qryResult = qryResult.substring(qryResult.indexOf("explain"));
+    //
+    // assertThat(qryResult, containsString("multipleadditivetreesmodel"));
+    // assertThat(qryResult,
+    // containsString(MultipleAdditiveTreesModel.class.getCanonicalName()));
+    //
+    // assertThat(qryResult, containsString("-100.0 = tree 0"));
+    // assertThat(qryResult, containsString("50.0 = tree 0"));
+    // assertThat(qryResult, containsString("-20.0 = tree 1"));
+    // assertThat(qryResult, containsString("'matchedTitle':1.0 > 0.5"));
+    // assertThat(qryResult, containsString("'matchedTitle':0.0 <= 0.5"));
+    //
+    // assertThat(qryResult, containsString(" Go Right "));
+    // assertThat(qryResult, containsString(" Go Left "));
+    // assertThat(qryResult,
+    // containsString("'this_feature_doesnt_exist' does not exist in FV"));
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoParams() throws Exception {
+    final ModelException expectedException =
+        new ModelException("no trees declared for model multipleadditivetreesmodel_no_params");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_params.json",
+              "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoParams failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestEmptyParams() throws Exception {
+    final ModelException expectedException =
+        new ModelException("no trees declared for model multipleadditivetreesmodel_no_trees");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_trees.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestEmptyParams failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoWeight() throws Exception {
+    final ModelException expectedException =
+        new ModelException("MultipleAdditiveTreesModel tree doesn't contain a weight");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_weight.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoWeight failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestTreesParamDoesNotContatinTree() throws Exception {
+    final ModelException expectedException =
+        new ModelException("MultipleAdditiveTreesModel tree doesn't contain a tree");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_tree.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestTreesParamDoesNotContatinTree failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoFeaturesSpecified() throws Exception {
+    final ModelException expectedException =
+        new ModelException("no features declared for model multipleadditivetreesmodel_no_features");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_features.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoFeaturesSpecified failed to throw exception: "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoRight() throws Exception {
+    final ModelException expectedException =
+        new ModelException("MultipleAdditiveTreesModel tree node is missing right");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_right.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoRight failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoLeft() throws Exception {
+    final ModelException expectedException =
+        new ModelException("MultipleAdditiveTreesModel tree node is missing left");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_left.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoLeft failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoThreshold() throws Exception {
+    final ModelException expectedException =
+        new ModelException("MultipleAdditiveTreesModel tree node is missing threshold");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_threshold.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoThreshold failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestMissingTreeFeature() throws Exception {
+    final ModelException expectedException =
+        new ModelException("MultipleAdditiveTreesModel tree node is leaf with left=-100.0 and right=75.0");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_feature.json",
+              "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestMissingTreeFeature failed to throw exception: "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestMinMaxNormalizer.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestMinMaxNormalizer.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestMinMaxNormalizer.java
new file mode 100644
index 0000000..055b3bc
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestMinMaxNormalizer.java
@@ -0,0 +1,120 @@
+/*
+ * 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.ltr.norm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.solr.core.SolrResourceLoader;
+import org.junit.Test;
+
+public class TestMinMaxNormalizer {
+
+  private final SolrResourceLoader solrResourceLoader = new SolrResourceLoader();
+
+  private Normalizer implTestMinMax(Map<String,Object> params,
+      float expectedMin, float expectedMax) {
+    final Normalizer n = Normalizer.getInstance(
+        solrResourceLoader,
+        MinMaxNormalizer.class.getCanonicalName(),
+        params);
+    assertTrue(n instanceof MinMaxNormalizer);
+    final MinMaxNormalizer mmn = (MinMaxNormalizer)n;
+    assertEquals(mmn.getMin(), expectedMin, 0.0);
+    assertEquals(mmn.getMax(), expectedMax, 0.0);
+    return n;
+  }
+
+  @Test
+  public void testInvalidMinMaxNoParams() {
+    implTestMinMax(new HashMap<String,Object>(),
+        Float.NEGATIVE_INFINITY,
+        Float.POSITIVE_INFINITY);
+  }
+
+  @Test
+  public void testInvalidMinMaxMissingMax() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("min", "0.0f");
+    implTestMinMax(params,
+        0.0f,
+        Float.POSITIVE_INFINITY);
+  }
+
+  @Test
+  public void testInvalidMinMaxMissingMin() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("max", "0.0f");
+    implTestMinMax(params,
+        Float.NEGATIVE_INFINITY,
+        0.0f);
+  }
+
+  @Test
+  public void testMinMaxNormalizerMinLargerThanMax() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("min", "10.0f");
+    params.put("max", "0.0f");
+    implTestMinMax(params,
+        10.0f,
+        0.0f);
+  }
+
+  @Test
+  public void testMinMaxNormalizerMinEqualToMax() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("min", "10.0f");
+    params.put("max", "10.0f");
+    final NormalizerException expectedException =
+        new NormalizerException("MinMax Normalizer delta must not be zero "
+            + "| min = 10.0,max = 10.0,delta = 0.0");
+    try {
+        implTestMinMax(params,
+              10.0f,
+              10.0f);
+        fail("testMinMaxNormalizerMinEqualToMax failed to throw exception: "+expectedException);
+    } catch(NormalizerException actualException) {
+        assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void testNormalizer() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("min", "5.0f");
+    params.put("max", "10.0f");
+    final Normalizer n =
+        implTestMinMax(params,
+            5.0f,
+            10.0f);
+
+    float value = 8;
+    assertEquals((value - 5f) / (10f - 5f), n.normalize(value), 0.0001);
+    value = 100;
+    assertEquals((value - 5f) / (10f - 5f), n.normalize(value), 0.0001);
+    value = 150;
+    assertEquals((value - 5f) / (10f - 5f), n.normalize(value), 0.0001);
+    value = -1;
+    assertEquals((value - 5f) / (10f - 5f), n.normalize(value), 0.0001);
+    value = 5;
+    assertEquals((value - 5f) / (10f - 5f), n.normalize(value), 0.0001);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestStandardNormalizer.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestStandardNormalizer.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestStandardNormalizer.java
new file mode 100644
index 0000000..10fa972
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/norm/TestStandardNormalizer.java
@@ -0,0 +1,132 @@
+/*
+ * 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.ltr.norm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.solr.core.SolrResourceLoader;
+import org.junit.Test;
+
+public class TestStandardNormalizer {
+
+  private final SolrResourceLoader solrResourceLoader = new SolrResourceLoader();
+
+  private Normalizer implTestStandard(Map<String,Object> params,
+      float expectedAvg, float expectedStd) {
+    final Normalizer n = Normalizer.getInstance(
+        solrResourceLoader,
+        StandardNormalizer.class.getCanonicalName(),
+        params);
+    assertTrue(n instanceof StandardNormalizer);
+    final StandardNormalizer sn = (StandardNormalizer)n;
+    assertEquals(sn.getAvg(), expectedAvg, 0.0);
+    assertEquals(sn.getStd(), expectedStd, 0.0);
+    return n;
+  }
+
+  @Test
+  public void testNormalizerNoParams() {
+    implTestStandard(new HashMap<String,Object>(),
+        0.0f,
+        1.0f);
+  }
+
+  @Test
+  public void testInvalidSTD() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("std", "0f");
+    final NormalizerException expectedException =
+        new NormalizerException("Standard Normalizer standard deviation must be positive "
+            + "| avg = 0.0,std = 0.0");
+    try {
+        implTestStandard(params,
+              0.0f,
+              0.0f);
+        fail("testInvalidSTD failed to throw exception: "+expectedException);
+    } catch(NormalizerException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void testInvalidSTD2() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("std", "-1f");
+    final NormalizerException expectedException =
+        new NormalizerException("Standard Normalizer standard deviation must be positive "
+            + "| avg = 0.0,std = -1.0");
+    try {
+        implTestStandard(params,
+              0.0f,
+              -1f);
+        fail("testInvalidSTD2 failed to throw exception: "+expectedException);
+    } catch(NormalizerException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void testInvalidSTD3() {
+    final Map<String,Object> params = new HashMap<String,Object>();
+    params.put("avg", "1f");
+    params.put("std", "0f");
+    final NormalizerException expectedException =
+        new NormalizerException("Standard Normalizer standard deviation must be positive "
+            + "| avg = 1.0,std = 0.0");
+    try {
+        implTestStandard(params,
+              1f,
+              0f);
+        fail("testInvalidSTD3 failed to throw exception: "+expectedException);
+    } catch(NormalizerException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void testNormalizer() {
+    Map<String,Object> params = new HashMap<String,Object>();
+    params.put("avg", "0f");
+    params.put("std", "1f");
+    final Normalizer identity =
+        implTestStandard(params,
+            0f,
+            1f);
+
+    float value = 8;
+    assertEquals(value, identity.normalize(value), 0.0001);
+    value = 150;
+    assertEquals(value, identity.normalize(value), 0.0001);
+    params = new HashMap<String,Object>();
+    params.put("avg", "10f");
+    params.put("std", "1.5f");
+    final Normalizer norm = Normalizer.getInstance(
+        solrResourceLoader,
+        StandardNormalizer.class.getCanonicalName(),
+        params);
+
+    for (final float v : new float[] {10f, 20f, 25f, 30f, 31f, 40f, 42f, 100f,
+        10000000f}) {
+      assertEquals((v - 10f) / (1.5f), norm.normalize(v), 0.0001);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java
new file mode 100644
index 0000000..14373fb
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestManagedFeatureStore.java
@@ -0,0 +1,36 @@
+/*
+ * 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.ltr.store.rest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestManagedFeatureStore extends LuceneTestCase {
+
+  public static Map<String,Object> createMap(String name, String className, Map<String,Object> params) {
+    final Map<String,Object> map = new HashMap<String,Object>();
+    map.put(ManagedFeatureStore.NAME_KEY, name);
+    map.put(ManagedFeatureStore.CLASS_KEY, className);
+    if (params != null) {
+      map.put(ManagedFeatureStore.PARAMS_KEY, params);
+    }
+    return map;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java
new file mode 100644
index 0000000..855f053
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java
@@ -0,0 +1,163 @@
+/*
+ * 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.ltr.store.rest;
+
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.feature.FieldValueFeature;
+import org.apache.solr.ltr.feature.ValueFeature;
+import org.apache.solr.ltr.model.LinearModel;
+import org.apache.solr.rest.ManagedResource;
+import org.apache.solr.rest.ManagedResourceStorage;
+import org.apache.solr.rest.RestManager;
+import org.apache.solr.search.LTRQParserPlugin;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestModelManager extends TestRerankBase {
+
+  @BeforeClass
+  public static void init() throws Exception {
+    setuptest();
+  }
+
+  @Before
+  public void restart() throws Exception {
+    restTestHarness.delete(ManagedFeatureStore.REST_END_POINT + "/*");
+    restTestHarness.delete(ManagedModelStore.REST_END_POINT + "/*");
+
+  }
+
+  @Test
+  public void test() throws Exception {
+    final SolrResourceLoader loader = new SolrResourceLoader(
+        tmpSolrHome.toPath());
+
+    final RestManager.Registry registry = loader.getManagedResourceRegistry();
+    assertNotNull(
+        "Expected a non-null RestManager.Registry from the SolrResourceLoader!",
+        registry);
+
+    final String resourceId = "/schema/fstore1";
+    registry.registerManagedResource(resourceId, ManagedFeatureStore.class,
+        new LTRQParserPlugin());
+
+    final String resourceId2 = "/schema/mstore1";
+    registry.registerManagedResource(resourceId2, ManagedModelStore.class,
+        new LTRQParserPlugin());
+
+    final NamedList<String> initArgs = new NamedList<>();
+
+    final RestManager restManager = new RestManager();
+    restManager.init(loader, initArgs,
+        new ManagedResourceStorage.InMemoryStorageIO());
+
+    final ManagedResource res = restManager.getManagedResource(resourceId);
+    assertTrue(res instanceof ManagedFeatureStore);
+    assertEquals(res.getResourceId(), resourceId);
+
+  }
+
+  @Test
+  public void testRestManagerEndpoints() throws Exception {
+    // relies on these ManagedResources being activated in the
+    // schema-rest.xml used by this test
+    assertJQ("/schema/managed", "/responseHeader/status==0");
+
+    final String valueFeatureClassName = ValueFeature.class.getCanonicalName();
+
+    // Add features
+    String feature = "{\"name\": \"test1\", \"class\": \""+valueFeatureClassName+"\", \"params\": {\"value\": 1} }";
+    assertJPut(ManagedFeatureStore.REST_END_POINT, feature,
+        "/responseHeader/status==0");
+
+    feature = "{\"name\": \"test2\", \"class\": \""+valueFeatureClassName+"\", \"params\": {\"value\": 1} }";
+    assertJPut(ManagedFeatureStore.REST_END_POINT, feature,
+        "/responseHeader/status==0");
+
+    feature = "{\"name\": \"test3\", \"class\": \""+valueFeatureClassName+"\", \"params\": {\"value\": 1} }";
+    assertJPut(ManagedFeatureStore.REST_END_POINT, feature,
+        "/responseHeader/status==0");
+
+    feature = "{\"name\": \"test33\", \"store\": \"TEST\", \"class\": \""+valueFeatureClassName+"\", \"params\": {\"value\": 1} }";
+    assertJPut(ManagedFeatureStore.REST_END_POINT, feature,
+        "/responseHeader/status==0");
+
+    final String multipleFeatures = "[{\"name\": \"test4\", \"class\": \""+valueFeatureClassName+"\", \"params\": {\"value\": 1} }"
+        + ",{\"name\": \"test5\", \"class\": \""+valueFeatureClassName+"\", \"params\": {\"value\": 1} } ]";
+    assertJPut(ManagedFeatureStore.REST_END_POINT, multipleFeatures,
+        "/responseHeader/status==0");
+
+    final String fieldValueFeatureClassName = FieldValueFeature.class.getCanonicalName();
+
+    // Add bad feature (wrong params)_
+    final String badfeature = "{\"name\": \"fvalue\", \"class\": \""+fieldValueFeatureClassName+"\", \"params\": {\"value\": 1} }";
+    assertJPut(ManagedFeatureStore.REST_END_POINT, badfeature,
+        "/error/msg/=='No setter corrresponding to \\'value\\' in "+fieldValueFeatureClassName+"'");
+
+    final String linearModelClassName = LinearModel.class.getCanonicalName();
+
+    // Add models
+    String model = "{ \"name\":\"testmodel1\", \"class\":\""+linearModelClassName+"\", \"features\":[] }";
+    // fails since it does not have features
+    assertJPut(ManagedModelStore.REST_END_POINT, model,
+        "/responseHeader/status==400");
+    // fails since it does not have weights
+    model = "{ \"name\":\"testmodel2\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}] }";
+    assertJPut(ManagedModelStore.REST_END_POINT, model,
+        "/responseHeader/status==400");
+    // success
+    model = "{ \"name\":\"testmodel3\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}}}";
+    assertJPut(ManagedModelStore.REST_END_POINT, model,
+        "/responseHeader/status==0");
+    // success
+    final String multipleModels = "[{ \"name\":\"testmodel4\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}} }\n"
+        + ",{ \"name\":\"testmodel5\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}} } ]";
+    assertJPut(ManagedModelStore.REST_END_POINT, multipleModels,
+        "/responseHeader/status==0");
+    final String qryResult = JQ(ManagedModelStore.REST_END_POINT);
+
+    assert (qryResult.contains("\"name\":\"testmodel3\"")
+        && qryResult.contains("\"name\":\"testmodel4\"") && qryResult
+          .contains("\"name\":\"testmodel5\""));
+    /*
+     * assertJQ(LTRParams.MSTORE_END_POINT, "/models/[0]/name=='testmodel3'");
+     * assertJQ(LTRParams.MSTORE_END_POINT, "/models/[1]/name=='testmodel4'");
+     * assertJQ(LTRParams.MSTORE_END_POINT, "/models/[2]/name=='testmodel5'");
+     */
+    assertJQ(ManagedFeatureStore.REST_END_POINT,
+        "/featureStores==['TEST','_DEFAULT_']");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/_DEFAULT_",
+        "/features/[0]/name=='test1'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/TEST",
+        "/features/[0]/name=='test33'");
+  }
+
+  @Test
+  public void testEndpointsFromFile() throws Exception {
+    loadFeatures("features-linear.json");
+    loadModels("linear-model.json");
+
+    assertJQ(ManagedModelStore.REST_END_POINT,
+        "/models/[0]/name=='6029760550880411648'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/_DEFAULT_",
+        "/features/[1]/name=='description'");
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/5a66b3bc/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
new file mode 100644
index 0000000..66c26fd
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ltr.store.rest;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.feature.ValueFeature;
+import org.apache.solr.ltr.model.LinearModel;
+import org.junit.Before;
+import org.junit.Test;
+import org.noggit.ObjectBuilder;
+
+public class TestModelManagerPersistence extends TestRerankBase {
+
+  @Before
+  public void init() throws Exception {
+    setupPersistenttest();
+  }
+
+  // executed first
+  @Test
+  public void testFeaturePersistence() throws Exception {
+
+    loadFeature("feature", ValueFeature.class.getCanonicalName(), "test",
+        "{\"value\":2}");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test",
+        "/features/[0]/name=='feature'");
+    restTestHarness.reload();
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test",
+        "/features/[0]/name=='feature'");
+    loadFeature("feature1", ValueFeature.class.getCanonicalName(), "test1",
+        "{\"value\":2}");
+    loadFeature("feature2", ValueFeature.class.getCanonicalName(), "test",
+        "{\"value\":2}");
+    loadFeature("feature3", ValueFeature.class.getCanonicalName(), "test2",
+        "{\"value\":2}");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test",
+        "/features/[0]/name=='feature'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test",
+        "/features/[1]/name=='feature2'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test1",
+        "/features/[0]/name=='feature1'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test2",
+        "/features/[0]/name=='feature3'");
+    restTestHarness.reload();
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test",
+        "/features/[0]/name=='feature'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test",
+        "/features/[1]/name=='feature2'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test1",
+        "/features/[0]/name=='feature1'");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test2",
+        "/features/[0]/name=='feature3'");
+    loadModel("test-model", LinearModel.class.getCanonicalName(),
+        new String[] {"feature"}, "test", "{\"weights\":{\"feature\":1.0}}");
+    loadModel("test-model2", LinearModel.class.getCanonicalName(),
+        new String[] {"feature1"}, "test1", "{\"weights\":{\"feature1\":1.0}}");
+    final String fstorecontent = FileUtils
+        .readFileToString(fstorefile, "UTF-8");
+    final String mstorecontent = FileUtils
+        .readFileToString(mstorefile, "UTF-8");
+
+    //check feature/model stores on deletion
+    final ArrayList<Object> fStore = (ArrayList<Object>) ((Map<String,Object>)
+        ObjectBuilder.fromJSON(fstorecontent)).get("managedList");
+    for (int idx = 0;idx < fStore.size(); ++ idx) {
+      String store = (String) ((Map<String,Object>)fStore.get(idx)).get("store");
+      assertTrue(store.equals("test") || store.equals("test2") || store.equals("test1"));
+    }
+
+    final ArrayList<Object> mStore = (ArrayList<Object>) ((Map<String,Object>)
+        ObjectBuilder.fromJSON(mstorecontent)).get("managedList");
+    for (int idx = 0;idx < mStore.size(); ++ idx) {
+      String store = (String) ((Map<String,Object>)mStore.get(idx)).get("store");
+      assertTrue(store.equals("test") || store.equals("test1"));
+    }
+
+    assertJDelete(ManagedFeatureStore.REST_END_POINT + "/test2",
+        "/responseHeader/status==0");
+    assertJDelete(ManagedModelStore.REST_END_POINT + "/test-model2",
+        "/responseHeader/status==0");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test2",
+        "/features/==[]");
+    assertJQ(ManagedModelStore.REST_END_POINT + "/test-model2",
+        "/models/[0]/name=='test-model'");
+    restTestHarness.reload();
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test2",
+        "/features/==[]");
+    assertJQ(ManagedModelStore.REST_END_POINT + "/test-model2",
+        "/models/[0]/name=='test-model'");
+
+    assertJDelete(ManagedModelStore.REST_END_POINT + "/*",
+        "/responseHeader/status==0");
+    assertJDelete(ManagedFeatureStore.REST_END_POINT + "/*",
+        "/responseHeader/status==0");
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test1",
+        "/features/==[]");
+    restTestHarness.reload();
+    assertJQ(ManagedFeatureStore.REST_END_POINT + "/test1",
+        "/features/==[]");
+
+  }
+
+}