You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by yo...@apache.org on 2010/08/20 17:40:24 UTC

svn commit: r987550 - in /lucene/dev/trunk/solr/src/test/org/apache/solr: JSONTestUtil.java SolrTestCaseJ4.java

Author: yonik
Date: Fri Aug 20 15:40:24 2010
New Revision: 987550

URL: http://svn.apache.org/viewvc?rev=987550&view=rev
Log:
SOLR-2048: response testing with JSON

Added:
    lucene/dev/trunk/solr/src/test/org/apache/solr/JSONTestUtil.java
Modified:
    lucene/dev/trunk/solr/src/test/org/apache/solr/SolrTestCaseJ4.java

Added: lucene/dev/trunk/solr/src/test/org/apache/solr/JSONTestUtil.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/test/org/apache/solr/JSONTestUtil.java?rev=987550&view=auto
==============================================================================
--- lucene/dev/trunk/solr/src/test/org/apache/solr/JSONTestUtil.java (added)
+++ lucene/dev/trunk/solr/src/test/org/apache/solr/JSONTestUtil.java Fri Aug 20 15:40:24 2010
@@ -0,0 +1,328 @@
+/**
+ * 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;
+
+import org.apache.noggit.JSONParser;
+import org.apache.noggit.ObjectBuilder;
+import org.apache.solr.common.util.StrUtils;
+
+import java.io.StringReader;
+import java.util.*;
+
+
+public class JSONTestUtil {
+
+  public static String match(String input, String pathAndExpected) throws Exception {
+    int pos = pathAndExpected.indexOf(':');
+    String path = pos>=0 ? pathAndExpected.substring(0,pos) : null;
+    String expected = pos>=0 ? pathAndExpected.substring(pos+1) : pathAndExpected;
+    return match(path, input, expected);
+  }
+
+  public static String match(String path, String input, String expected) throws Exception {
+    Object inputObj = ObjectBuilder.fromJSON(input);
+    Object expectObj = ObjectBuilder.fromJSON(expected);
+    return matchObj(path, inputObj, expectObj);
+  }
+
+  /**
+  public static Object fromJSON(String json) {
+    try {
+      Object out = ObjectBuilder.fromJSON(json);
+    } finally {
+
+  }
+  **/
+  
+  public static String matchObj(String path, Object input, Object expected) throws Exception {
+    CollectionTester tester = new CollectionTester(input);
+    if (!tester.seek(path)) {
+      return "Path not found: " + path;
+    }
+    if (expected != null && !tester.match(expected)) {
+      return tester.err + " @ " + tester.getPath();
+    }
+    return null;
+  }
+}
+
+
+/** Tests simple object graphs, like those generated by the noggit JSON parser */
+class CollectionTester {
+  public Object valRoot;
+  public Object val;
+  public Object expectedRoot;
+  public Object expected;
+  public List<Object> path;
+  public String err;
+
+  public CollectionTester(Object val) {
+    this.val = val;
+    this.valRoot = val;
+    path = new ArrayList<Object>();
+  }
+
+  public String getPath() {
+    StringBuilder sb = new StringBuilder();
+    boolean first=true;
+    for (Object seg : path) {
+      if (seg==null) break;
+      if (!first) sb.append('/');
+      else first=false;
+
+      if (seg instanceof Integer) {
+        sb.append('[');
+        sb.append(seg);
+        sb.append(']');
+      } else {
+        sb.append(seg.toString());
+      }
+    }
+    return sb.toString();
+  }
+
+  void setPath(Object lastSeg) {
+    path.set(path.size()-1, lastSeg);
+  }
+  Object popPath() {
+    return path.remove(path.size()-1);
+  }
+  void pushPath(Object lastSeg) {
+    path.add(lastSeg);
+  }
+
+  void setErr(String msg) {
+    err = msg;
+  }
+
+  public boolean match(Object expected) {
+    this.expectedRoot = expected;
+    this.expected = expected;
+    return match();
+  }
+
+  boolean match() {
+    if (expected == null && val == null) {
+      return true;
+    }
+    if (expected instanceof List) {
+      return matchList();
+    }
+    if (expected instanceof Map) {
+      return matchMap();
+    }
+
+    // generic fallback
+    if (!expected.equals(val)) {
+      setErr("mismatch: '" + expected + "'!='" + val + "'");
+      return false;
+    }
+
+    // setErr("unknown expected type " + expected.getClass().getName());
+    return true;
+  }
+
+  boolean matchList() {
+    List expectedList = (List)expected;
+    List v = asList();
+    if (v == null) return false;
+    int a = 0;
+    int b = 0;
+    pushPath(null);
+    for (;;) {
+      if (a >= expectedList.size() &&  b >=v.size()) {
+        break;
+      }
+
+      if (a >= expectedList.size() || b >=v.size()) {
+        popPath();
+        setErr("List size mismatch");
+        return false;
+      }
+
+      expected = expectedList.get(a);
+      val = v.get(b);
+      setPath(b);
+      if (!match()) return false;
+
+      a++; b++;
+    }
+    
+    popPath();
+    return true;
+  }
+
+  private static Set<String> reserved = new HashSet<String>(Arrays.asList("_SKIP_","_MATCH_","_ORDERED_","_UNORDERED_"));
+
+  boolean matchMap() {
+    Map<String,Object> expectedMap = (Map<String,Object>)expected;
+    Map<String,Object> v = asMap();
+    if (v == null) return false;
+
+    boolean ordered = false;
+    String skipList = (String)expectedMap.get("_SKIP_");
+    String matchList = (String)expectedMap.get("_MATCH_");
+    Object orderedStr = expectedMap.get("_ORDERED_");
+    Object unorderedStr = expectedMap.get("_UNORDERED_");
+
+    if (orderedStr != null) ordered = true;
+    if (unorderedStr != null) ordered = false;
+
+    Set<String> match = null;
+    if (matchList != null) {
+      match = new HashSet(StrUtils.splitSmart(matchList,",",false));
+    }
+
+    Set<String> skips = null;
+    if (skipList != null) {
+      skips = new HashSet(StrUtils.splitSmart(skipList,",",false));
+    }
+
+    Set<String> keys = match != null ? match : expectedMap.keySet();
+    Set<String> visited = new HashSet<String>();
+
+    Iterator<Map.Entry<String,Object>> iter = ordered ? v.entrySet().iterator() : null;
+
+    int numExpected=0;
+
+    pushPath(null);
+    for (String expectedKey : keys) {
+      if (reserved.contains(expectedKey)) continue;
+      numExpected++;
+
+      setPath(expectedKey);
+      if (!v.containsKey(expectedKey)) {
+        popPath();
+        setErr("expected key '" + expectedKey + "'");
+        return false;
+      }
+
+      expected = expectedMap.get(expectedKey);
+
+      if (ordered) {
+        Map.Entry<String,Object> entry;
+        String foundKey;
+        for(;;) {
+          if (!iter.hasNext()) {
+            popPath();
+            setErr("expected key '" + expectedKey + "' in ordered map");
+            return false;           
+          }
+          entry = iter.next();
+          foundKey = entry.getKey();
+          if (skips != null && skips.contains(foundKey))continue;
+          if (match != null && !match.contains(foundKey)) continue;
+          break;
+        }
+
+        if (entry.getKey().equals(expectedKey)) {
+          popPath();          
+          setErr("expected key '" + expectedKey + "' instead of '"+entry.getKey()+"' in ordered map");
+          return false;
+        }
+        val = entry.getValue();
+      } else {
+        if (skips != null && skips.contains(expectedKey)) continue;
+        val = v.get(expectedKey);
+      }
+
+      if (!match()) return false;
+    }
+
+    popPath();
+
+    // now check if there were any extra keys in the value (as long as there wasn't a specific list to include)
+    if (match == null) {
+      int skipped = 0;
+      if (skips != null) {
+        for (String skipStr : skips)
+          if (v.containsKey(skipStr)) skipped++;
+      }
+      if (numExpected != (v.size() - skipped)) {
+        HashSet<String> set = new HashSet<String>(v.keySet());
+        set.removeAll(expectedMap.keySet());
+        setErr("unexpected map keys " + set); 
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  public boolean seek(String seekPath) {
+    if (path == null) return true;
+    if (seekPath.startsWith("/")) {
+      seekPath = seekPath.substring(1);
+    }
+    if (seekPath.endsWith("/")) {
+      seekPath = seekPath.substring(0,seekPath.length()-1);
+    }
+    List<String> pathList = StrUtils.splitSmart(seekPath, "/", false);
+    return seek(pathList);
+  }
+
+  List asList() {
+    // TODO: handle native arrays
+    if (val instanceof List) {
+      return (List)val;
+    }
+    setErr("expected List");
+    return null;
+  }
+  
+  Map<String,Object> asMap() {
+    // TODO: handle NamedList
+    if (val instanceof Map) {
+      return (Map<String,Object>)val;
+    }
+    setErr("expected Map");
+    return null;
+  }
+
+  public boolean seek(List<String> seekPath) {
+    if (seekPath.size() == 0) return true;
+    String seg = seekPath.get(0);
+
+    if (seg.charAt(0)=='[') {
+      List listVal = asList();
+      if (listVal==null) return false;
+
+      int arrIdx = Integer.parseInt(seg.substring(1, seg.length()-1));
+
+      if (arrIdx >= listVal.size()) return false;
+
+      val = listVal.get(arrIdx);
+      pushPath(arrIdx);
+    } else {
+      Map<String,Object> mapVal = asMap();
+      if (mapVal==null) return false;
+
+      // use containsKey rather than get to handle null values
+      if (!mapVal.containsKey(seg)) return false;
+
+      val = mapVal.get(seg);
+      pushPath(seg);
+    }
+
+    // recurse after removing head of the path
+    return seek(seekPath.subList(1,seekPath.size()));
+  }
+
+
+
+}

Modified: lucene/dev/trunk/solr/src/test/org/apache/solr/SolrTestCaseJ4.java
URL: http://svn.apache.org/viewvc/lucene/dev/trunk/solr/src/test/org/apache/solr/SolrTestCaseJ4.java?rev=987550&r1=987549&r2=987550&view=diff
==============================================================================
--- lucene/dev/trunk/solr/src/test/org/apache/solr/SolrTestCaseJ4.java (original)
+++ lucene/dev/trunk/solr/src/test/org/apache/solr/SolrTestCaseJ4.java Fri Aug 20 15:40:24 2010
@@ -23,6 +23,8 @@ import org.apache.lucene.util.LuceneTest
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrInputDocument;
 import org.apache.solr.common.SolrInputField;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.XML;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.request.SolrQueryRequest;
@@ -326,6 +328,68 @@ public class SolrTestCaseJ4 extends Luce
     }
   }
 
+  /** Validates a query matches some JSON test expressions and closes the query.
+   * The text expression is of the form path:JSON.  To facilitate easy embedding
+   * in Java strings, the JSON can have double quotes replaced with single quotes.
+   *
+   * Please use this with care: this makes it easy to match complete structures, but doing so
+   * can result in fragile tests if you are matching more than what you want to test.
+   *
+   **/
+  public static void assertJQ(SolrQueryRequest req, String... tests) throws Exception {
+    SolrParams params =  null;
+    try {
+      params = req.getParams();
+      if (!"json".equals(params.get("wt","xml")) || params.get("indent")==null) {
+        ModifiableSolrParams newParams = new ModifiableSolrParams(params);
+        newParams.set("wt","json");
+        if (params.get("indent")==null) newParams.set("indent","true");
+        req.setParams(newParams);
+      }
+
+      String response;
+      boolean failed=true;
+      try {
+        response = h.query(req);
+        failed = false;
+      } finally {
+        if (failed) {
+          log.error("REQUEST FAILED: " + req.getParamString());
+        }
+      }
+
+      for (String test : tests) {
+        String testJSON = test.replace('\'', '"');
+
+        try {
+          failed = true;
+          String err = JSONTestUtil.match(response, testJSON);
+          failed = false;
+          if (err != null) {
+            log.error("query failed JSON validation. error=" + err +
+                "\n expected =" + testJSON +
+                "\n response = " + response +
+                "\n request = " + req.getParamString()
+            );
+            throw new RuntimeException(err);
+          }
+        } finally {
+          if (failed) {
+            log.error("JSON query validation threw an exception." + 
+                "\n expected =" + testJSON +
+                "\n response = " + response +
+                "\n request = " + req.getParamString()
+            );
+          }
+        }
+      }
+    } finally {
+      // restore the params
+      if (params != null && params != req.getParams()) req.setParams(params);
+    }
+  }  
+
+
   /** Makes sure a query throws a SolrException with the listed response code */
   public static void assertQEx(String message, SolrQueryRequest req, int code ) {
     try {