mirror of https://github.com/apache/lucene.git
SOLR-2048: response testing with JSON
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@987550 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
85549f7e7c
commit
c4539873b4
|
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -23,6 +23,8 @@ import org.apache.lucene.util.LuceneTestCaseJ4;
|
|||
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 LuceneTestCaseJ4 {
|
|||
}
|
||||
}
|
||||
|
||||
/** 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 {
|
||||
|
|
Loading…
Reference in New Issue