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 {