All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.util.TestHarness Maven / Gradle / Ivy

The newest version!
/**
 * 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.util;

import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.XML;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.handler.XmlUpdateRequestHandler;
import org.apache.solr.request.LocalSolrQueryRequest;
import org.apache.solr.request.QueryResponseWriter;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryResponse;
import org.apache.solr.schema.IndexSchema;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import org.apache.solr.common.util.NamedList.NamedListEntry;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * This class provides a simple harness that may be useful when
 * writing testcases.
 *
 * 

* This class lives in the main source tree (and not in the test source * tree), so that it will be included with even the most minimal solr * distribution, in order to encourage plugin writers to create unit * tests for their plugins. * * @version $Id:$ */ public class TestHarness { protected CoreContainer container; private SolrCore core; private XPath xpath = XPathFactory.newInstance().newXPath(); private DocumentBuilder builder; public XmlUpdateRequestHandler updater; public static SolrConfig createConfig(String confFile) { // set some system properties for use by tests System.setProperty("solr.test.sys.prop1", "propone"); System.setProperty("solr.test.sys.prop2", "proptwo"); try { return new SolrConfig(confFile); } catch(Exception xany) { throw new RuntimeException(xany); } } /** * Assumes "solrconfig.xml" is the config file to use, and * "schema.xml" is the schema path to use. * * @param dataDirectory path for index data, will not be cleaned up */ public TestHarness( String dataDirectory) { this( dataDirectory, "schema.xml"); } /** * Assumes "solrconfig.xml" is the config file to use. * * @param dataDirectory path for index data, will not be cleaned up * @param schemaFile path of schema file */ public TestHarness( String dataDirectory, String schemaFile) { this( dataDirectory, "solrconfig.xml", schemaFile); } /** * @param dataDirectory path for index data, will not be cleaned up * @param configFile solrconfig filename * @param schemaFile schema filename */ public TestHarness( String dataDirectory, String configFile, String schemaFile) { this( dataDirectory, createConfig(configFile), schemaFile); } /** * @param dataDirectory path for index data, will not be cleaned up * @param solrConfig solronfig instance * @param schemaFile schema filename */ public TestHarness( String dataDirectory, SolrConfig solrConfig, String schemaFile) { this( dataDirectory, solrConfig, new IndexSchema(solrConfig, schemaFile, null)); } /** * @param dataDirectory path for index data, will not be cleaned up * @param solrConfig solrconfig instance * @param indexSchema schema instance */ public TestHarness( String dataDirectory, SolrConfig solrConfig, IndexSchema indexSchema) { this("", new Initializer("", dataDirectory, solrConfig, indexSchema)); } public TestHarness(String coreName, CoreContainer.Initializer init) { try { container = init.initialize(); if (coreName == null) coreName = ""; // get the core & decrease its refcount: // the container holds the core for the harness lifetime core = container.getCore(coreName); if (core != null) core.close(); builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); updater = new XmlUpdateRequestHandler(); updater.init( null ); } catch (Exception e) { throw new RuntimeException(e); } } // Creates a container based on infos needed to create one core static class Initializer extends CoreContainer.Initializer { String coreName; String dataDirectory; SolrConfig solrConfig; IndexSchema indexSchema; public Initializer(String coreName, String dataDirectory, SolrConfig solrConfig, IndexSchema indexSchema) { if (coreName == null) coreName = ""; this.coreName = coreName; this.dataDirectory = dataDirectory; this.solrConfig = solrConfig; this.indexSchema = indexSchema; } public String getCoreName() { return coreName; } @Override public CoreContainer initialize() { CoreContainer container = new CoreContainer(new SolrResourceLoader(SolrResourceLoader.locateSolrHome())); CoreDescriptor dcore = new CoreDescriptor(container, coreName, solrConfig.getResourceLoader().getInstanceDir()); dcore.setConfigName(solrConfig.getResourceName()); dcore.setSchemaName(indexSchema.getResourceName()); SolrCore core = new SolrCore( null, dataDirectory, solrConfig, indexSchema, dcore); container.register(coreName, core, false); return container; } } public CoreContainer getCoreContainer() { return container; } public SolrCore getCore() { return core; } /** * Processes an "update" (add, commit or optimize) and * returns the response as a String. * * @deprecated The better approach is to instantiate an Updatehandler directly * * @param xml The XML of the update * @return The XML response to the update */ @Deprecated public String update(String xml) { StringReader req = new StringReader(xml); StringWriter writer = new StringWriter(32000); updater.doLegacyUpdate(req, writer); return writer.toString(); } /** * Validates that an "update" (add, commit or optimize) results in success. * * :TODO: currently only deals with one add/doc at a time, this will need changed if/when SOLR-2 is resolved * * @param xml The XML of the update * @return null if successful, otherwise the XML response to the update */ public String validateUpdate(String xml) throws SAXException { return checkUpdateStatus(xml, "0"); } /** * Validates that an "update" (add, commit or optimize) results in success. * * :TODO: currently only deals with one add/doc at a time, this will need changed if/when SOLR-2 is resolved * * @param xml The XML of the update * @return null if successful, otherwise the XML response to the update */ public String validateErrorUpdate(String xml) throws SAXException { return checkUpdateStatus(xml, "1"); } /** * Validates that an "update" (add, commit or optimize) results in success. * * :TODO: currently only deals with one add/doc at a time, this will need changed if/when SOLR-2 is resolved * * @param xml The XML of the update * @return null if successful, otherwise the XML response to the update */ public String checkUpdateStatus(String xml, String code) throws SAXException { try { String res = update(xml); String valid = validateXPath(res, "//result[@status="+code+"]" ); return (null == valid) ? null : res; } catch (XPathExpressionException e) { throw new RuntimeException ("?!? static xpath has bug?", e); } } /** * Validates that an add of a single document results in success. * * @param fieldsAndValues Odds are field names, Evens are values * @return null if successful, otherwise the XML response to the update * @see #appendSimpleDoc */ public String validateAddDoc(String... fieldsAndValues) throws XPathExpressionException, SAXException, IOException { StringBuilder buf = new StringBuilder(); buf.append(""); appendSimpleDoc(buf, fieldsAndValues); buf.append(""); String res = update(buf.toString()); String valid = validateXPath(res, "//result[@status=0]" ); return (null == valid) ? null : res; } /** * Validates a "query" response against an array of XPath test strings * * @param req the Query to process * @return null if all good, otherwise the first test that fails. * @exception Exception any exception in the response. * @exception IOException if there is a problem writing the XML * @see LocalSolrQueryRequest */ public String validateQuery(SolrQueryRequest req, String... tests) throws IOException, Exception { String res = query(req); return validateXPath(res, tests); } /** * Processes a "query" using a user constructed SolrQueryRequest * * @param req the Query to process, will be closed. * @return The XML response to the query * @exception Exception any exception in the response. * @exception IOException if there is a problem writing the XML * @see LocalSolrQueryRequest */ public String query(SolrQueryRequest req) throws IOException, Exception { return query(req.getParams().get(CommonParams.QT), req); } /** * Processes a "query" using a user constructed SolrQueryRequest * * @param handler the name of the request handler to process the request * @param req the Query to process, will be closed. * @return The XML response to the query * @exception Exception any exception in the response. * @exception IOException if there is a problem writing the XML * @see LocalSolrQueryRequest */ public String query(String handler, SolrQueryRequest req) throws IOException, Exception { SolrQueryResponse rsp = queryAndResponse(handler, req); StringWriter sw = new StringWriter(32000); QueryResponseWriter responseWriter = core.getQueryResponseWriter(req); responseWriter.write(sw,req,rsp); req.close(); return sw.toString(); } public SolrQueryResponse queryAndResponse(String handler, SolrQueryRequest req) throws Exception { SolrQueryResponse rsp = new SolrQueryResponse(); core.execute(core.getRequestHandler(handler),req,rsp); if (rsp.getException() != null) { throw rsp.getException(); } return rsp; } /** * A helper method which valides a String against an array of XPath test * strings. * * @param xml The xml String to validate * @param tests Array of XPath strings to test (in boolean mode) on the xml * @return null if all good, otherwise the first test that fails. */ public String validateXPath(String xml, String... tests) throws XPathExpressionException, SAXException { if (tests==null || tests.length == 0) return null; Document document=null; try { document = builder.parse(new ByteArrayInputStream (xml.getBytes("UTF-8"))); } catch (UnsupportedEncodingException e1) { throw new RuntimeException("Totally weird UTF-8 exception", e1); } catch (IOException e2) { throw new RuntimeException("Totally weird io exception", e2); } for (String xp : tests) { xp=xp.trim(); Boolean bool = (Boolean) xpath.evaluate(xp, document, XPathConstants.BOOLEAN); if (!bool) { return xp; } } return null; } /** * Shuts down and frees any resources */ public void close() { if (container != null) { for (SolrCore c : container.getCores()) { if (c.getOpenCount() > 1) throw new RuntimeException("SolrCore.getOpenCount()=="+core.getOpenCount()); } } if (container != null) { container.shutdown(); container = null; } } /** * A helper that adds an xml <doc> containing all of the * fields and values specified (odds are fields, evens are values) * to a StringBuilder */ public void appendSimpleDoc(StringBuilder buf, String... fieldsAndValues) throws IOException { buf.append(makeSimpleDoc(fieldsAndValues)); } /** * A helper that adds an xml <doc> containing all of the * fields and values specified (odds are fields, evens are values) * to a StringBuffer. * @deprecated see {@link #appendSimpleDoc(StringBuilder, String...)} */ public void appendSimpleDoc(StringBuffer buf, String... fieldsAndValues) throws IOException { buf.append(makeSimpleDoc(fieldsAndValues)); } /** * A helper that creates an xml <doc> containing all of the * fields and values specified * * @param fieldsAndValues 0 and Even numbered args are fields names odds are field values. */ public static StringBuffer makeSimpleDoc(String... fieldsAndValues) { try { StringWriter w = new StringWriter(); w.append(""); for (int i = 0; i < fieldsAndValues.length; i+=2) { XML.writeXML(w, "field", fieldsAndValues[i+1], "name", fieldsAndValues[i]); } w.append(""); return w.getBuffer(); } catch (IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Generates a delete by query xml string * @param q Query that has not already been xml escaped */ public static String deleteByQuery(String q) { return delete("query", q); } /** * Generates a delete by id xml string * @param id ID that has not already been xml escaped */ public static String deleteById(String id) { return delete("id", id); } /** * Generates a delete xml string * @param val text that has not already been xml escaped */ private static String delete(String deltype, String val) { try { StringWriter r = new StringWriter(); r.write(""); XML.writeXML(r, deltype, val); r.write(""); return r.getBuffer().toString(); } catch (IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Helper that returns an <optimize> String with * optional key/val pairs. * * @param args 0 and Even numbered args are params, Odd numbered args are values. */ public static String optimize(String... args) { return simpleTag("optimize", args); } private static String simpleTag(String tag, String... args) { try { StringWriter r = new StringWriter(); // this is annoying if (null == args || 0 == args.length) { XML.writeXML(r, tag, null); } else { XML.writeXML(r, tag, null, (Object[])args); } return r.getBuffer().toString(); } catch (IOException e) { throw new RuntimeException ("this should never happen with a StringWriter", e); } } /** * Helper that returns an <commit> String with * optional key/val pairs. * * @param args 0 and Even numbered args are params, Odd numbered args are values. */ public static String commit(String... args) { return simpleTag("commit", args); } public LocalRequestFactory getRequestFactory(String qtype, int start, int limit) { LocalRequestFactory f = new LocalRequestFactory(); f.qtype = qtype; f.start = start; f.limit = limit; return f; } /** * 0 and Even numbered args are keys, Odd numbered args are values. */ public LocalRequestFactory getRequestFactory(String qtype, int start, int limit, String... args) { LocalRequestFactory f = getRequestFactory(qtype, start, limit); for (int i = 0; i < args.length; i+=2) { f.args.put(args[i], args[i+1]); } return f; } public LocalRequestFactory getRequestFactory(String qtype, int start, int limit, Map args) { LocalRequestFactory f = getRequestFactory(qtype, start, limit); f.args.putAll(args); return f; } /** * A Factory that generates LocalSolrQueryRequest objects using a * specified set of default options. */ public class LocalRequestFactory { public String qtype = "standard"; public int start = 0; public int limit = 1000; public Map args = new HashMap(); public LocalRequestFactory() { } public LocalSolrQueryRequest makeRequest(String ... q) { if (q.length==1) { return new LocalSolrQueryRequest(TestHarness.this.getCore(), q[0], qtype, start, limit, args); } if (q.length%2 != 0) { throw new RuntimeException("The length of the string array (query arguments) needs to be even"); } Map.Entry [] entries = new NamedListEntry[q.length / 2]; for (int i = 0; i < q.length; i += 2) { entries[i/2] = new NamedListEntry(q[i], q[i+1]); } return new LocalSolrQueryRequest(TestHarness.this.getCore(), new NamedList(entries)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy