2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
19
package org.apache.solr.util;
22
import org.apache.lucene.util.LuceneTestCase;
23
import org.apache.solr.SolrTestCaseJ4;
24
import org.apache.solr.core.SolrConfig;
25
import org.apache.solr.common.SolrException;
26
import org.apache.solr.common.SolrInputDocument;
27
import org.apache.solr.common.SolrInputField;
28
import org.apache.solr.common.util.XML;
29
import org.apache.solr.request.*;
30
import org.junit.AfterClass;
31
import org.junit.BeforeClass;
33
import org.xml.sax.SAXException;
34
import org.slf4j.LoggerFactory;
35
import org.slf4j.Logger;
36
import javax.xml.xpath.XPathExpressionException;
39
import java.util.HashSet;
40
import java.util.List;
41
import java.util.ArrayList;
44
* An Abstract base class that makes writing Solr JUnit tests "easier"
47
* Test classes that subclass this need only specify the path to the
48
* schema.xml file (:TODO: the solrconfig.xml as well) and write some
49
* testMethods. This class takes care of creating/destroying the index,
50
* and provides several assert methods to assist you.
56
public abstract class AbstractSolrTestCase extends LuceneTestCase {
57
protected SolrConfig solrConfig;
59
* Harness initialized by initTestHarness.
62
* For use in test methods as needed.
65
protected TestHarness h;
67
* LocalRequestFactory initialized by initTestHarness using sensible
71
* For use in test methods as needed.
74
protected TestHarness.LocalRequestFactory lrf;
77
* Subclasses must define this method to return the name of the
78
* schema.xml they wish to use.
80
public abstract String getSchemaFile();
83
* Subclasses must define this method to return the name of the
84
* solrconfig.xml they wish to use.
86
public abstract String getSolrConfigFile();
89
* Subclasses can override this to change a test's solr home
90
* (default is in test-files)
92
public String getSolrHome() {
93
return SolrTestCaseJ4.TEST_HOME();
97
public static void beforeClassAbstractSolrTestCase() throws Exception {
98
SolrTestCaseJ4.startTrackingSearchers();
102
public static void afterClassAbstractSolrTestCase() throws Exception {
103
SolrTestCaseJ4.endTrackingSearchers();
107
* The directory used to story the index managed by the TestHarness h
109
protected File dataDir;
112
* Initializes things your test might need
115
* <li>Creates a dataDir in the "java.io.tmpdir"</li>
116
* <li>initializes the TestHarness h using this data directory, and getSchemaPath()</li>
117
* <li>initializes the LocalRequestFactory lrf using sensible defaults.</li>
122
public static Logger log = LoggerFactory.getLogger(AbstractSolrTestCase.class);
124
private String factoryProp;
126
public void setUp() throws Exception {
128
log.info("####SETUP_START " + getName());
129
ignoreException("ignore_exception");
130
factoryProp = System.getProperty("solr.directoryFactory");
131
if (factoryProp == null) {
132
System.setProperty("solr.directoryFactory","solr.RAMDirectoryFactory");
134
dataDir = new File(TEMP_DIR,
135
getClass().getName() + "-" + System.currentTimeMillis());
137
String configFile = getSolrConfigFile();
138
System.setProperty("solr.solr.home", getSolrHome());
139
if (configFile != null) {
141
solrConfig = h.createConfig(getSolrConfigFile());
142
h = new TestHarness( dataDir.getAbsolutePath(),
145
lrf = h.getRequestFactory
146
("standard",0,20,"version","2.2");
148
log.info("####SETUP_END " + getName());
151
/** Causes an exception matching the regex pattern to not be logged. */
152
public static void ignoreException(String pattern) {
153
if (SolrException.ignorePatterns == null)
154
SolrException.ignorePatterns = new HashSet<String>();
155
SolrException.ignorePatterns.add(pattern);
158
public static void resetExceptionIgnores() {
159
SolrException.ignorePatterns = null;
160
ignoreException("ignore_exception"); // always ignore "ignore_exception"
163
/** Subclasses that override setUp can optionally call this method
164
* to log the fact that their setUp process has ended.
166
public void postSetUp() {
167
log.info("####POSTSETUP " + getName());
171
/** Subclasses that override tearDown can optionally call this method
172
* to log the fact that the tearDown process has started. This is necessary
173
* since subclasses will want to call super.tearDown() at the *end* of their
176
public void preTearDown() {
177
log.info("####PRETEARDOWN " + getName());
181
* Shuts down the test harness, and makes the best attempt possible
182
* to delete dataDir, unless the system property "solr.test.leavedatadir"
186
public void tearDown() throws Exception {
187
log.info("####TEARDOWN_START " + getName());
188
if (factoryProp == null) {
189
System.clearProperty("solr.directoryFactory");
192
if (h != null) { h.close(); }
193
SolrTestCaseJ4.closeDirectories();
194
String skip = System.getProperty("solr.test.leavedatadir");
195
if (null != skip && 0 != skip.trim().length()) {
196
System.err.println("NOTE: per solr.test.leavedatadir, dataDir will not be removed: " + dataDir.getAbsolutePath());
198
if (!recurseDelete(dataDir)) {
199
System.err.println("!!!! WARNING: best effort to remove " + dataDir.getAbsolutePath() + " FAILED !!!!!");
203
resetExceptionIgnores();
207
/** Validates an update XML String is successful
209
public void assertU(String update) {
210
assertU(null, update);
213
/** Validates an update XML String is successful
215
public void assertU(String message, String update) {
216
checkUpdateU(message, update, true);
219
/** Validates an update XML String failed
221
public void assertFailedU(String update) {
222
assertFailedU(null, update);
225
/** Validates an update XML String failed
227
public void assertFailedU(String message, String update) {
228
checkUpdateU(message, update, false);
231
/** Checks the success or failure of an update message
233
private void checkUpdateU(String message, String update, boolean shouldSucceed) {
235
String m = (null == message) ? "" : message + " ";
237
String res = h.validateUpdate(update);
238
if (res != null) fail(m + "update was not successful: " + res);
240
String res = h.validateErrorUpdate(update);
241
if (res != null) fail(m + "update succeeded, but should have failed: " + res);
243
} catch (SAXException e) {
244
throw new RuntimeException("Invalid XML", e);
248
/** Validates a query matches some XPath test expressions and closes the query */
249
public void assertQ(SolrQueryRequest req, String... tests) {
250
assertQ(null, req, tests);
253
/** Validates a query matches some XPath test expressions and closes the query */
254
public void assertQ(String message, SolrQueryRequest req, String... tests) {
256
String m = (null == message) ? "" : message + " ";
257
String response = h.query(req);
258
String results = h.validateXPath(response, tests);
259
if (null != results) {
260
fail(m + "query failed XPath: " + results +
261
"\n xml response was: " + response +
262
"\n request was: " + req.getParamString());
264
} catch (XPathExpressionException e1) {
265
throw new RuntimeException("XPath is invalid", e1);
266
} catch (Exception e2) {
267
throw new RuntimeException("Exception during query", e2);
271
/** Makes sure a query throws a SolrException with the listed response code */
272
public void assertQEx(String message, SolrQueryRequest req, int code ) {
276
} catch (SolrException sex) {
277
assertEquals( code, sex.code() );
278
} catch (Exception e2) {
279
throw new RuntimeException("Exception during query", e2);
283
public void assertQEx(String message, SolrQueryRequest req, SolrException.ErrorCode code ) {
287
} catch (SolrException e) {
288
assertEquals( code.code, e.code() );
289
} catch (Exception e2) {
290
throw new RuntimeException("Exception during query", e2);
296
* @see TestHarness#optimize
298
public String optimize(String... args) {
299
return h.optimize(args);
302
* @see TestHarness#commit
304
public String commit(String... args) {
305
return h.commit(args);
309
* Generates a simple <add><doc>... XML String with no options
311
* @param fieldsAndValues 0th and Even numbered args are fields names odds are field values.
315
public String adoc(String... fieldsAndValues) {
316
Doc d = doc(fieldsAndValues);
321
* Generates a simple <add><doc>... XML String with no options
323
public String adoc(SolrInputDocument sdoc) {
324
List<String> fields = new ArrayList<String>();
325
for (SolrInputField sf : sdoc) {
326
for (Object o : sf.getValues()) {
327
fields.add(sf.getName());
328
fields.add(o.toString());
331
return adoc(fields.toArray(new String[fields.size()]));
336
* Generates an <add><doc>... XML String with options
339
* @param doc the Document to add
340
* @param args 0th and Even numbered args are param names, Odds are param values.
344
public String add(Doc doc, String... args) {
346
StringWriter r = new StringWriter();
349
if (null == args || 0 == args.length) {
354
XML.writeUnescapedXML(r, "add", doc.xml, (Object[])args);
357
return r.getBuffer().toString();
358
} catch (IOException e) {
359
throw new RuntimeException
360
("this should never happen with a StringWriter", e);
365
* Generates a <delete>... XML string for an ID
367
* @see TestHarness#deleteById
369
public String delI(String id) {
370
return h.deleteById(id);
373
* Generates a <delete>... XML string for an query
375
* @see TestHarness#deleteByQuery
377
public String delQ(String q) {
378
return h.deleteByQuery(q);
382
* Generates a simple <doc>... XML String with no options
384
* @param fieldsAndValues 0th and Even numbered args are fields names, Odds are field values.
385
* @see TestHarness#makeSimpleDoc
387
public Doc doc(String... fieldsAndValues) {
389
d.xml = h.makeSimpleDoc(fieldsAndValues).toString();
394
* Generates a SolrQueryRequest using the LocalRequestFactory
397
public SolrQueryRequest req(String... q) {
398
return lrf.makeRequest(q);
402
* Generates a SolrQueryRequest using the LocalRequestFactory
405
public SolrQueryRequest req(String[] params, String... moreParams) {
406
String[] allParams = moreParams;
407
if (params.length!=0) {
408
int len = params.length + moreParams.length;
409
allParams = new String[len];
410
System.arraycopy(params,0,allParams,0,params.length);
411
System.arraycopy(moreParams,0,allParams,params.length,moreParams.length);
414
return lrf.makeRequest(allParams);
417
/** Neccessary to make method signatures un-ambiguous */
418
public static class Doc {
421
public String toString() { return xml; }
424
public static boolean recurseDelete(File f) {
425
if (f.isDirectory()) {
426
for (File sub : f.listFiles()) {
427
if (!recurseDelete(sub)) {
428
System.err.println("!!!! WARNING: best effort to remove " + sub.getAbsolutePath() + " FAILED !!!!!");
436
/** @see SolrTestCaseJ4#getFile */
437
public static File getFile(String name) throws IOException {
438
return SolrTestCaseJ4.getFile(name);