001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.solr.util;
017:
018: import org.apache.solr.core.SolrException;
019: import org.apache.solr.request.*;
020: import org.apache.solr.util.TestHarness;
021:
022: import org.xml.sax.SAXException;
023: import junit.framework.TestCase;
024: import javax.xml.xpath.XPathExpressionException;
025:
026: import java.util.*;
027: import java.io.*;
028:
029: /**
030: * An Abstract base class that makes writing Solr JUnit tests "easier"
031: *
032: * <p>
033: * Test classes that subclass this need only specify the path to the
034: * schema.xml file (:TODO: the solrconfig.xml as well) and write some
035: * testMethods. This class takes care of creating/destroying the index,
036: * and provides several assert methods to assist you.
037: * </p>
038: *
039: * @see #setUp
040: * @see #tearDown
041: */
042: public abstract class AbstractSolrTestCase extends TestCase {
043:
044: /**
045: * Harness initialized by initTestHarness.
046: *
047: * <p>
048: * For use in test methods as needed.
049: * </p>
050: */
051: protected TestHarness h;
052: /**
053: * LocalRequestFactory initialized by initTestHarness using sensible
054: * defaults.
055: *
056: * <p>
057: * For use in test methods as needed.
058: * </p>
059: */
060: protected TestHarness.LocalRequestFactory lrf;
061:
062: /**
063: * Subclasses must define this method to return the name of the
064: * schema.xml they wish to use.
065: */
066: public abstract String getSchemaFile();
067:
068: /**
069: * Subclasses must define this method to return the name of the
070: * solrconfig.xml they wish to use.
071: */
072: public abstract String getSolrConfigFile();
073:
074: /**
075: * The directory used to story the index managed by the TestHarness h
076: */
077: protected File dataDir;
078:
079: /**
080: * Initializes things your test might need
081: *
082: * <ul>
083: * <li>Creates a dataDir in the "java.io.tmpdir"</li>
084: * <li>initializes the TestHarness h using this data directory, and getSchemaPath()</li>
085: * <li>initializes the LocalRequestFactory lrf using sensible defaults.</li>
086: * </ul>
087: *
088: */
089: public void setUp() throws Exception {
090:
091: dataDir = new File(System.getProperty("java.io.tmpdir")
092: + System.getProperty("file.separator")
093: + getClass().getName() + "-" + getName() + "-"
094: + System.currentTimeMillis());
095: dataDir.mkdirs();
096: h = new TestHarness(dataDir.getAbsolutePath(),
097: getSolrConfigFile(), getSchemaFile());
098: lrf = h.getRequestFactory("standard", 0, 20, "version", "2.2");
099:
100: }
101:
102: /**
103: * Shuts down the test harness, and makes the best attempt possible
104: * to delete dataDir, unless the system property "solr.test.leavedatadir"
105: * is set.
106: */
107: public void tearDown() throws Exception {
108: if (h != null) {
109: h.close();
110: }
111: String skip = System.getProperty("solr.test.leavedatadir");
112: if (null != skip && 0 != skip.trim().length()) {
113: System.err
114: .println("NOTE: per solr.test.leavedatadir, dataDir will not be removed: "
115: + dataDir.getAbsolutePath());
116: } else {
117: if (!recurseDelete(dataDir)) {
118: System.err
119: .println("!!!! WARNING: best effort to remove "
120: + dataDir.getAbsolutePath()
121: + " FAILED !!!!!");
122: }
123: }
124: }
125:
126: /** Validates an update XML String is successful
127: */
128: public void assertU(String update) {
129: assertU(null, update);
130: }
131:
132: /** Validates an update XML String is successful
133: */
134: public void assertU(String message, String update) {
135: checkUpdateU(message, update, true);
136: }
137:
138: /** Validates an update XML String failed
139: */
140: public void assertFailedU(String update) {
141: assertFailedU(null, update);
142: }
143:
144: /** Validates an update XML String failed
145: */
146: public void assertFailedU(String message, String update) {
147: checkUpdateU(message, update, false);
148: }
149:
150: /** Checks the success or failure of an update message
151: */
152: private void checkUpdateU(String message, String update,
153: boolean shouldSucceed) {
154: try {
155: String m = (null == message) ? "" : message + " ";
156: if (shouldSucceed) {
157: String res = h.validateUpdate(update);
158: if (res != null)
159: fail(m + "update was not successful: " + res);
160: } else {
161: String res = h.validateErrorUpdate(update);
162: if (res != null)
163: fail(m
164: + "update succeeded, but should have failed: "
165: + res);
166: }
167: } catch (SAXException e) {
168: throw new RuntimeException("Invalid XML", e);
169: }
170: }
171:
172: /** Validates a query matches some XPath test expressions and closes the query */
173: public void assertQ(SolrQueryRequest req, String... tests) {
174: assertQ(null, req, tests);
175: }
176:
177: /** Validates a query matches some XPath test expressions and closes the query */
178: public void assertQ(String message, SolrQueryRequest req,
179: String... tests) {
180: try {
181: String m = (null == message) ? "" : message + " ";
182: String response = h.query(req);
183: String results = h.validateXPath(response, tests);
184: if (null != results) {
185: fail(m + "query failed XPath: " + results
186: + " xml response was: " + response);
187: }
188: } catch (XPathExpressionException e1) {
189: throw new RuntimeException("XPath is invalid", e1);
190: } catch (Exception e2) {
191: throw new RuntimeException("Exception during query", e2);
192: }
193: }
194:
195: /** Makes sure a query throws a SolrException with the listed response code */
196: public void assertQEx(String message, SolrQueryRequest req, int code) {
197: try {
198: h.query(req);
199: fail(message);
200: } catch (SolrException sex) {
201: assertEquals(code, sex.code());
202: } catch (Exception e2) {
203: throw new RuntimeException("Exception during query", e2);
204: }
205: }
206:
207: /**
208: * @see TestHarness#optimize
209: */
210: public String optimize(String... args) {
211: return h.optimize();
212: }
213:
214: /**
215: * @see TestHarness#commit
216: */
217: public String commit(String... args) {
218: return h.commit();
219: }
220:
221: /**
222: * Generates a simple <add><doc>... XML String with no options
223: *
224: * @param fieldsAndValues 0th and Even numbered args are fields names odds are field values.
225: * @see #add
226: * @see #doc
227: */
228: public String adoc(String... fieldsAndValues) {
229: Doc d = doc(fieldsAndValues);
230: return add(d);
231: }
232:
233: /**
234: * Generates an <add><doc>... XML String with options
235: * on the add.
236: *
237: * @param doc the Document to add
238: * @param args 0th and Even numbered args are param names, Odds are param values.
239: * @see #add
240: * @see #doc
241: */
242: public String add(Doc doc, String... args) {
243: try {
244: StringWriter r = new StringWriter();
245:
246: // this is anoying
247: if (null == args || 0 == args.length) {
248: r.write("<add>");
249: r.write(doc.xml);
250: r.write("</add>");
251: } else {
252: XML.writeUnescapedXML(r, "add", doc.xml,
253: (Object[]) args);
254: }
255:
256: return r.getBuffer().toString();
257: } catch (IOException e) {
258: throw new RuntimeException(
259: "this should never happen with a StringWriter", e);
260: }
261: }
262:
263: /**
264: * Generates a <delete>... XML string for an ID
265: *
266: * @see TestHarness#deleteById
267: */
268: public String delI(String id) {
269: return h.deleteById(id);
270: }
271:
272: /**
273: * Generates a <delete>... XML string for an query
274: *
275: * @see TestHarness#deleteByQuery
276: */
277: public String delQ(String q) {
278: return h.deleteByQuery(q);
279: }
280:
281: /**
282: * Generates a simple <doc>... XML String with no options
283: *
284: * @param fieldsAndValues 0th and Even numbered args are fields names, Odds are field values.
285: * @see TestHarness#makeSimpleDoc
286: */
287: public Doc doc(String... fieldsAndValues) {
288: Doc d = new Doc();
289: d.xml = h.makeSimpleDoc(fieldsAndValues).toString();
290: return d;
291: }
292:
293: /**
294: * Generates a SolrQueryRequest using the LocalRequestFactory
295: * @see #lrf
296: */
297: public SolrQueryRequest req(String... q) {
298: return lrf.makeRequest(q);
299: }
300:
301: /**
302: * Generates a SolrQueryRequest using the LocalRequestFactory
303: * @see #lrf
304: */
305: public SolrQueryRequest req(String[] params, String... moreParams) {
306: String[] allParams = moreParams;
307: if (params.length != 0) {
308: int len = params.length + moreParams.length;
309: allParams = new String[len];
310: System.arraycopy(params, 0, allParams, 0, params.length);
311: System.arraycopy(moreParams, 0, allParams, params.length,
312: moreParams.length);
313: }
314:
315: return lrf.makeRequest(allParams);
316: }
317:
318: /** Neccessary to make method signatures un-ambiguous */
319: public static class Doc {
320: public String xml;
321:
322: public String toString() {
323: return xml;
324: }
325: }
326:
327: public static boolean recurseDelete(File f) {
328: if (f.isDirectory()) {
329: for (File sub : f.listFiles()) {
330: if (!recurseDelete(sub)) {
331: return false;
332: }
333: }
334: }
335: return f.delete();
336: }
337: }
|