diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000000..6072fee3e1af
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "rest-spec"]
+ path = rest-spec
+ url = git@github.com:elasticsearch/elasticsearch-rest-api-spec.git
diff --git a/TESTING.asciidoc b/TESTING.asciidoc
index 4079b99c43c0..d07a8c7506f7 100644
--- a/TESTING.asciidoc
+++ b/TESTING.asciidoc
@@ -166,3 +166,45 @@ even if tests are passing.
------------------------------
mvn test -Dtests.output=always
------------------------------
+
+== Testing the REST layer
+
+The available integration tests make use of the java API to communicate with
+the elasticsearch nodes, using the internal binary transport (port 9300 by
+default).
+The REST layer is tested through specific tests that are shared between all
+the elasticsearch official clients and can be found on the
+https://github.com/elasticsearch/elasticsearch-rest-api-spec[elasticsearch-rest-api-spec project].
+They consist of
+https://github.com/elasticsearch/elasticsearch-rest-api-spec/tree/master/test[YAML files]
+that describe the operations to be executed and the obtained results that
+need to be tested.
+
+`ElasticsearchRestTests` is the executable test class that runs all the
+yaml suites available through a git submodule within the `rest-spec` folder.
+The submodule gets automatically initialized through maven right before
+running tests (generate-test-resources phase).
+The REST tests cannot be run without the files pulled from the submodule,
+thus if the `rest-spec` folder is empty on your working copy, it means
+that it needs to be initialized with the following command:
+
+------------------------------
+git submodule update --init
+------------------------------
+
+The following are the options supported by the REST tests runner:
+
+* `tests.rest[true|false|host:port]`: determines whether the REST tests need
+to be run and if so whether to rely on an external cluster (providing host
+and port) or fire a test cluster (default)
+* `tests.rest.suite`: comma separated paths of the test suites to be run
+(by default loaded from /rest-spec/test). It is possible to run only a subset
+of the tests providing a sub-folder or even a single yaml file (the default
+/rest-spec/test prefix is optional when files are loaded from classpath)
+e.g. -Dtests.rest.suite=index,get,create/10_with_id
+* `tests.rest.spec`: REST spec path (default /rest-spec/api)
+* `tests.iters`: runs multiple iterations
+* `tests.seed`: seed to base the random behaviours on
+* `tests.appendseed[true|false]`: enables adding the seed to each test
+section's description (default false)
+* `tests.cluster_seed`: seed used to create the test cluster (if enabled)
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index e70a6c89b3c0..82063e9111c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -35,6 +35,8 @@
true
onerror
+ false
+ false
INFO
@@ -65,6 +67,12 @@
${lucene.version}
test
+
+ org.apache.httpcomponents
+ httpclient
+ 4.3.1
+ test
+
org.apache.lucene
@@ -313,6 +321,14 @@
**/*.*
+
+ ${basedir}/rest-spec
+ rest-spec
+
+ api/*.json
+ test/**/*.yaml
+
+
@@ -396,6 +412,7 @@
${tests.jvm.argline}
+ ${tests.appendseed}
${tests.iters}
${tests.maxfailures}
${tests.failfast}
@@ -412,6 +429,9 @@
${tests.integration}
${tests.cluster_seed}
${tests.client.ratio}
+ ${tests.rest}
+ ${tests.rest.suite}
+ ${tests.rest.spec}
${env.ES_TEST_LOCAL}
${es.node.mode}
${es.logger.level}
@@ -1002,14 +1022,50 @@
exec
+
+ java
+
+ -version
+
+
+
+
+ Init Rest Spec
+ generate-test-resources
+
+ exec
+
+
+ ${rest.init.skip}
+ git
+
+ submodule
+ update
+ --init
+
+
+
+
+ Pull Rest Spec
+ generate-test-resources
+
+ exec
+
+
+ ${rest.pull.skip}
+ git
+
+ submodule
+ foreach
+ git
+ pull
+ origin
+ master
+
+
-
- java
-
- -version
-
-
+
org.apache.maven.plugins
@@ -1026,7 +1082,14 @@
org/elasticsearch/test/**/*
org/apache/lucene/util/AbstractRandomizedTest.class
org/apache/lucene/util/AbstractRandomizedTest$*.class
+ com/carrotsearch/randomizedtesting/StandaloneRandomizedContext.class
+
+
+ org/elasticsearch/test/rest/ElasticsearchRestTests.class
+
+ org/elasticsearch/test/rest/test/**/*
+
diff --git a/rest-spec b/rest-spec
new file mode 160000
index 000000000000..2f5f78f24d8f
--- /dev/null
+++ b/rest-spec
@@ -0,0 +1 @@
+Subproject commit 2f5f78f24d8fbacf69c83ab7545654c83965e846
diff --git a/src/test/java/com/carrotsearch/randomizedtesting/StandaloneRandomizedContext.java b/src/test/java/com/carrotsearch/randomizedtesting/StandaloneRandomizedContext.java
new file mode 100644
index 000000000000..8816f6e553eb
--- /dev/null
+++ b/src/test/java/com/carrotsearch/randomizedtesting/StandaloneRandomizedContext.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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 com.carrotsearch.randomizedtesting;
+
+/**
+ * Exposes methods that allow to use a {@link RandomizedContext} without using a {@link RandomizedRunner}
+ * This was specifically needed by the REST tests since they run with a custom junit runner ({@link org.elasticsearch.test.rest.junit.RestTestSuiteRunner})
+ */
+public final class StandaloneRandomizedContext {
+
+ private StandaloneRandomizedContext() {
+
+ }
+
+ /**
+ * Creates a new {@link RandomizedContext} associated to the current thread
+ */
+ public static void createRandomizedContext(Class> testClass, Randomness runnerRandomness) {
+ //the randomized runner is passed in as null, which is fine as long as we don't try to access it afterwards
+ RandomizedContext randomizedContext = RandomizedContext.create(Thread.currentThread().getThreadGroup(), testClass, null);
+ randomizedContext.push(runnerRandomness.clone(Thread.currentThread()));
+ }
+
+ /**
+ * Destroys the {@link RandomizedContext} associated to the current thread
+ */
+ public static void disposeRandomizedContext() {
+ RandomizedContext.current().dispose();
+ }
+
+ public static void pushRandomness(Randomness randomness) {
+ RandomizedContext.current().push(randomness);
+ }
+
+ public static void popAndDestroy() {
+ RandomizedContext.current().popAndDestroy();
+ }
+
+ /**
+ * Returns the string formatted seed associated to the current thread's randomized context
+ */
+ public static String getSeedAsString() {
+ return SeedUtils.formatSeed(RandomizedContext.current().getRandomness().getSeed());
+ }
+
+ /**
+ * Util method to extract the seed out of a {@link Randomness} instance
+ */
+ public static long getSeed(Randomness randomness) {
+ return randomness.getSeed();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java b/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java
index 40ea83f459a8..7e573404a076 100644
--- a/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java
+++ b/src/test/java/org/elasticsearch/test/ElasticsearchIntegrationTest.java
@@ -18,7 +18,6 @@
*/
package org.elasticsearch.test;
-import com.carrotsearch.randomizedtesting.SeedUtils;
import com.google.common.base.Joiner;
import org.apache.lucene.util.AbstractRandomizedTest;
import org.elasticsearch.ExceptionsHelper;
@@ -68,6 +67,8 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import static org.elasticsearch.test.TestCluster.SHARED_CLUSTER_SEED;
+import static org.elasticsearch.test.TestCluster.clusterName;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
import static org.hamcrest.Matchers.emptyIterable;
@@ -123,7 +124,7 @@ import static org.hamcrest.Matchers.equalTo;
* This class supports the following system properties (passed with -Dkey=value to the application)
*
* -D{@value #TESTS_CLIENT_RATIO} - a double value in the interval [0..1] which defines the ration between node and transport clients used
- * -D{@value #TESTS_CLUSTER_SEED} - a random seed used to initialize the clusters random context.
+ * -D{@value TestCluster#TESTS_CLUSTER_SEED} - a random seed used to initialize the clusters random context.
* -D{@value #INDEX_SEED_SETTING} - a random seed used to initialize the index random context.
*
*
@@ -132,24 +133,13 @@ import static org.hamcrest.Matchers.equalTo;
@AbstractRandomizedTest.IntegrationTests
public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase {
-
- /**
- * The random seed for the shared test cluster used in the current JVM.
- */
- public static final long SHARED_CLUSTER_SEED = clusterSeed();
-
- private static final TestCluster GLOBAL_CLUSTER = new TestCluster(SHARED_CLUSTER_SEED, TestCluster.clusterName("shared", ElasticsearchTestCase.CHILD_VM_ID, SHARED_CLUSTER_SEED));
+ private static final TestCluster GLOBAL_CLUSTER = new TestCluster(SHARED_CLUSTER_SEED, clusterName("shared", ElasticsearchTestCase.CHILD_VM_ID, SHARED_CLUSTER_SEED));
/**
* Key used to set the transport client ratio via the commandline -D{@value #TESTS_CLIENT_RATIO}
*/
public static final String TESTS_CLIENT_RATIO = "tests.client.ratio";
- /**
- * Key used to set the shared cluster random seed via the commandline -D{@value #TESTS_CLUSTER_SEED}
- */
- public static final String TESTS_CLUSTER_SEED = "tests.cluster_seed";
-
/**
* Key used to retrieve the index random seed from the index settings on a running node.
* The value of this seed can be used to initialize a random context for a specific index.
@@ -830,7 +820,7 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
};
}
- return new TestCluster(currentClusterSeed, numNodes, TestCluster.clusterName(scope.name(), ElasticsearchTestCase.CHILD_VM_ID, currentClusterSeed), nodeSettingsSource);
+ return new TestCluster(currentClusterSeed, numNodes, clusterName(scope.name(), ElasticsearchTestCase.CHILD_VM_ID, currentClusterSeed), nodeSettingsSource);
}
/**
@@ -859,14 +849,6 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
double transportClientRatio() default -1;
}
- private static long clusterSeed() {
- String property = System.getProperty(TESTS_CLUSTER_SEED);
- if (property == null || property.isEmpty()) {
- return System.nanoTime();
- }
- return SeedUtils.parseSeed(property);
- }
-
/**
* Returns the client ratio configured via
*/
diff --git a/src/test/java/org/elasticsearch/test/TestCluster.java b/src/test/java/org/elasticsearch/test/TestCluster.java
index 4803da083697..3fbefd816d5f 100644
--- a/src/test/java/org/elasticsearch/test/TestCluster.java
+++ b/src/test/java/org/elasticsearch/test/TestCluster.java
@@ -33,6 +33,7 @@ import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.routing.ShardRouting;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
@@ -78,6 +79,24 @@ public final class TestCluster implements Iterable {
private final ESLogger logger = Loggers.getLogger(getClass());
+ /**
+ * The random seed for the shared test cluster used in the current JVM.
+ */
+ public static final long SHARED_CLUSTER_SEED = clusterSeed();
+
+ /**
+ * Key used to set the shared cluster random seed via the commandline -D{@value #TESTS_CLUSTER_SEED}
+ */
+ public static final String TESTS_CLUSTER_SEED = "tests.cluster_seed";
+
+ private static long clusterSeed() {
+ String property = System.getProperty(TESTS_CLUSTER_SEED);
+ if (!Strings.hasLength(property)) {
+ return System.nanoTime();
+ }
+ return SeedUtils.parseSeed(property);
+ }
+
/* sorted map to make traverse order reproducible */
private final TreeMap nodes = newTreeMap();
@@ -109,6 +128,10 @@ public final class TestCluster implements Iterable {
this(clusterSeed, -1, clusterName, NodeSettingsSource.EMPTY);
}
+ public TestCluster(long clusterSeed, int numNodes, String clusterName) {
+ this(clusterSeed, numNodes, clusterName, NodeSettingsSource.EMPTY);
+ }
+
TestCluster(long clusterSeed, int numNodes, String clusterName, NodeSettingsSource nodeSettingsSource) {
this.clusterName = clusterName;
Random random = new Random(clusterSeed);
@@ -164,7 +187,7 @@ public final class TestCluster implements Iterable {
return builder.build();
}
- static String clusterName(String prefix, String childVMId, long clusterSeed) {
+ public static String clusterName(String prefix, String childVMId, long clusterSeed) {
StringBuilder builder = new StringBuilder(prefix);
builder.append('-').append(NetworkUtils.getLocalAddress().getHostName());
builder.append("-CHILD_VM=[").append(childVMId).append(']');
@@ -379,7 +402,7 @@ public final class TestCluster implements Iterable {
return null;
}
- void close() {
+ public void close() {
ensureOpen();
if (this.open.compareAndSet(true, false)) {
IOUtils.closeWhileHandlingException(nodes.values());
@@ -554,7 +577,7 @@ public final class TestCluster implements Iterable {
/**
* This method should be executed before each test to reset the cluster to it's initial state.
*/
- synchronized void beforeTest(Random random, double transportClientRatio) {
+ public synchronized void beforeTest(Random random, double transportClientRatio) {
reset(random, true, transportClientRatio);
}
@@ -619,7 +642,7 @@ public final class TestCluster implements Iterable {
/**
* This method should be executed during tearDown
*/
- synchronized void afterTest() {
+ public synchronized void afterTest() {
wipeDataDirectories();
resetClients(); /* reset all clients - each test gets its own client based on the Random instance created above. */
diff --git a/src/test/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java b/src/test/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java
index 9b90589fe17c..56f2c231fc2d 100644
--- a/src/test/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java
+++ b/src/test/java/org/elasticsearch/test/junit/listeners/ReproduceInfoPrinter.java
@@ -4,6 +4,7 @@ import com.carrotsearch.randomizedtesting.RandomizedContext;
import com.carrotsearch.randomizedtesting.ReproduceErrorMessageBuilder;
import com.carrotsearch.randomizedtesting.SeedUtils;
import com.carrotsearch.randomizedtesting.TraceFormatting;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
@@ -13,9 +14,9 @@ import org.junit.runner.Description;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
+import static com.carrotsearch.randomizedtesting.SysGlobals.SYSPROP_ITERATIONS;
+import static org.elasticsearch.test.TestCluster.SHARED_CLUSTER_SEED;
+import static org.elasticsearch.test.TestCluster.TESTS_CLUSTER_SEED;
/**
* A {@link RunListener} that emits to {@link System#err} a string with command
@@ -46,26 +47,44 @@ public class ReproduceInfoPrinter extends RunListener {
final StringBuilder b = new StringBuilder();
b.append("FAILURE : ").append(d.getDisplayName()).append("\n");
b.append("REPRODUCE WITH : mvn test");
- ReproduceErrorMessageBuilder builder = new MavenMessageBuilder(b).appendAllOpts(failure.getDescription());
- if (ElasticsearchIntegrationTest.class.isAssignableFrom(failure.getDescription().getTestClass())) {
- builder.appendOpt("tests.cluster_seed", SeedUtils.formatSeed(ElasticsearchIntegrationTest.SHARED_CLUSTER_SEED));
+ ReproduceErrorMessageBuilder builder = reproduceErrorMessageBuilder(b).appendAllOpts(failure.getDescription());
+
+ if (mustAppendClusterSeed(failure)) {
+ appendClusterSeed(builder);
}
b.append("\n");
b.append("Throwable:\n");
if (failure.getException() != null) {
- TraceFormatting traces = new TraceFormatting();
- try {
- traces = RandomizedContext.current().getRunner().getTraceFormatting();
- } catch (IllegalStateException e) {
- // Ignore if no context.
- }
- traces.formatThrowable(b, failure.getException());
+ traces().formatThrowable(b, failure.getException());
}
+
logger.error(b.toString());
}
- private static class MavenMessageBuilder extends ReproduceErrorMessageBuilder {
+ protected boolean mustAppendClusterSeed(Failure failure) {
+ return ElasticsearchIntegrationTest.class.isAssignableFrom(failure.getDescription().getTestClass());
+ }
+
+ protected void appendClusterSeed(ReproduceErrorMessageBuilder builder) {
+ builder.appendOpt(TESTS_CLUSTER_SEED, SeedUtils.formatSeed(SHARED_CLUSTER_SEED));
+ }
+
+ protected ReproduceErrorMessageBuilder reproduceErrorMessageBuilder(StringBuilder b) {
+ return new MavenMessageBuilder(b);
+ }
+
+ protected TraceFormatting traces() {
+ TraceFormatting traces = new TraceFormatting();
+ try {
+ traces = RandomizedContext.current().getRunner().getTraceFormatting();
+ } catch (IllegalStateException e) {
+ // Ignore if no context.
+ }
+ return traces;
+ }
+
+ protected static class MavenMessageBuilder extends ReproduceErrorMessageBuilder {
public MavenMessageBuilder(StringBuilder b) {
super(b);
@@ -80,28 +99,34 @@ public class ReproduceInfoPrinter extends RunListener {
/**
* Append a single VM option.
*/
+ @Override
public ReproduceErrorMessageBuilder appendOpt(String sysPropName, String value) {
- if (sysPropName.equals("tests.iters")) { // we don't want the iters to be in there!
+ if (sysPropName.equals(SYSPROP_ITERATIONS())) { // we don't want the iters to be in there!
return this;
}
- if (value != null && !value.isEmpty()) {
+ if (Strings.hasLength(value)) {
return super.appendOpt(sysPropName, value);
}
return this;
}
public ReproduceErrorMessageBuilder appendESProperties() {
- for (String sysPropName : Arrays.asList(
- "es.logger.level", "es.node.mode", "es.node.local")) {
- if (System.getProperty(sysPropName) != null && !System.getProperty(sysPropName).isEmpty()) {
- appendOpt(sysPropName, System.getProperty(sysPropName));
- }
- }
+ appendProperties("es.logger.level", "es.node.mode", "es.node.local");
+
if (System.getProperty("tests.jvm.argline") != null && !System.getProperty("tests.jvm.argline").isEmpty()) {
appendOpt("tests.jvm.argline", "\"" + System.getProperty("tests.jvm.argline") + "\"");
}
return this;
}
+ protected ReproduceErrorMessageBuilder appendProperties(String... properties) {
+ for (String sysPropName : properties) {
+ if (Strings.hasLength(System.getProperty(sysPropName))) {
+ appendOpt(sysPropName, System.getProperty(sysPropName));
+ }
+ }
+ return this;
+ }
+
}
}
diff --git a/src/test/java/org/elasticsearch/test/rest/ElasticsearchRestTests.java b/src/test/java/org/elasticsearch/test/rest/ElasticsearchRestTests.java
new file mode 100644
index 000000000000..08860210ca33
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/ElasticsearchRestTests.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest;
+
+import org.elasticsearch.test.rest.junit.RestTestSuiteRunner;
+import org.junit.runner.RunWith;
+
+import static org.apache.lucene.util.LuceneTestCase.Slow;
+
+/**
+ * Runs the clients test suite against an elasticsearch node, which can be an external node or an automatically created cluster.
+ * Communicates with elasticsearch exclusively via REST layer.
+ *
+ * @see RestTestSuiteRunner for extensive documentation and all the supported options
+ */
+@Slow
+@RunWith(RestTestSuiteRunner.class)
+public class ElasticsearchRestTests {
+
+
+}
\ No newline at end of file
diff --git a/src/test/java/org/elasticsearch/test/rest/RestTestExecutionContext.java b/src/test/java/org/elasticsearch/test/rest/RestTestExecutionContext.java
new file mode 100644
index 000000000000..aad2b73fc689
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/RestTestExecutionContext.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest;
+
+import com.google.common.collect.Maps;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.test.rest.client.RestClient;
+import org.elasticsearch.test.rest.client.RestException;
+import org.elasticsearch.test.rest.client.RestResponse;
+import org.elasticsearch.test.rest.spec.RestSpec;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Execution context passed across the REST tests.
+ * Holds the REST client used to communicate with elasticsearch.
+ * Caches the last obtained test response and allows to stash part of it within variables
+ * that can be used as input values in following requests.
+ */
+public class RestTestExecutionContext implements Closeable {
+
+ private static final ESLogger logger = Loggers.getLogger(RestTestExecutionContext.class);
+
+ private final RestClient restClient;
+
+ private final String esVersion;
+
+ private final Map stash = Maps.newHashMap();
+
+ private RestResponse response;
+
+ public RestTestExecutionContext(String host, int port, RestSpec restSpec) throws RestException, IOException {
+
+ this.restClient = new RestClient(host, port, restSpec);
+ this.esVersion = restClient.getEsVersion();
+ }
+
+ /**
+ * Calls an elasticsearch api with the parameters and request body provided as arguments.
+ * Saves the obtained response in the execution context.
+ * @throws RestException if the returned status code is non ok
+ */
+ public RestResponse callApi(String apiName, Map params, String body) throws IOException, RestException {
+ //makes a copy of the parameters before modifying them for this specific request
+ HashMap requestParams = Maps.newHashMap(params);
+ for (Map.Entry entry : requestParams.entrySet()) {
+ if (isStashed(entry.getValue())) {
+ entry.setValue(unstash(entry.getValue()).toString());
+ }
+ }
+ try {
+ return response = callApiInternal(apiName, requestParams, body);
+ } catch(RestException e) {
+ response = e.restResponse();
+ throw e;
+ }
+ }
+
+ /**
+ * Calls an elasticsearch api internally without saving the obtained response in the context.
+ * Useful for internal calls (e.g. delete index during teardown)
+ * @throws RestException if the returned status code is non ok
+ */
+ public RestResponse callApiInternal(String apiName, String... params) throws IOException, RestException {
+ return restClient.callApi(apiName, params);
+ }
+
+ private RestResponse callApiInternal(String apiName, Map params, String body) throws IOException, RestException {
+ return restClient.callApi(apiName, params, body);
+ }
+
+ /**
+ * Extracts a specific value from the last saved response
+ */
+ public Object response(String path) throws IOException {
+ return response.evaluate(path);
+ }
+
+ /**
+ * Clears the last obtained response and the stashed fields
+ */
+ public void clear() {
+ logger.debug("resetting response and stash");
+ response = null;
+ stash.clear();
+ }
+
+ /**
+ * Tells whether a particular value needs to be looked up in the stash
+ * The stash contains fields eventually extracted from previous responses that can be reused
+ * as arguments for following requests (e.g. scroll_id)
+ */
+ public boolean isStashed(Object key) {
+ if (key == null) {
+ return false;
+ }
+ String stashKey = key.toString();
+ return Strings.hasLength(stashKey) && stashKey.startsWith("$");
+ }
+
+ /**
+ * Extracts a value from the current stash
+ * The stash contains fields eventually extracted from previous responses that can be reused
+ * as arguments for following requests (e.g. scroll_id)
+ */
+ public Object unstash(String value) {
+ Object stashedValue = stash.get(value.substring(1));
+ if (stashedValue == null) {
+ throw new IllegalArgumentException("stashed value not found for key [" + value + "]");
+ }
+ return stashedValue;
+ }
+
+ /**
+ * Allows to saved a specific field in the stash as key-value pair
+ */
+ public void stash(String key, Object value) {
+ logger.debug("stashing [{}]=[{}]", key, value);
+ Object old = stash.put(key, value);
+ if (old != null && old != value) {
+ logger.trace("replaced stashed value [{}] with same key [{}]", old, key);
+ }
+ }
+
+ /**
+ * Returns the current es version as a string
+ */
+ public String esVersion() {
+ return esVersion;
+ }
+
+ /**
+ * Closes the execution context and releases the underlying resources
+ */
+ public void close() {
+ this.restClient.close();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/client/RestClient.java b/src/test/java/org/elasticsearch/test/rest/client/RestClient.java
new file mode 100644
index 000000000000..a8ef9ecb7520
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/client/RestClient.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.client;
+
+import com.carrotsearch.randomizedtesting.RandomizedTest;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
+import org.elasticsearch.test.rest.client.http.HttpResponse;
+import org.elasticsearch.test.rest.spec.RestApi;
+import org.elasticsearch.test.rest.spec.RestSpec;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * REST client used to test the elasticsearch REST layer
+ * Holds the {@link RestSpec} used to translate api calls into REST calls
+ */
+public class RestClient implements Closeable {
+
+ private static final ESLogger logger = Loggers.getLogger(RestClient.class);
+
+ private final RestSpec restSpec;
+ private final CloseableHttpClient httpClient;
+
+ private final String host;
+ private final int port;
+
+ private final String esVersion;
+
+ public RestClient(String host, int port, RestSpec restSpec) throws IOException, RestException {
+ this.restSpec = restSpec;
+ this.httpClient = createHttpClient();
+ this.host = host;
+ this.port = port;
+ this.esVersion = readVersion();
+ logger.info("REST client initialized [{}:{}], elasticsearch version: [{}]", host, port, esVersion);
+ }
+
+ private String readVersion() throws IOException, RestException {
+ //we make a manual call here without using callApi method, mainly because we are initializing
+ //and the randomized context doesn't exist for the current thread (would be used to choose the method otherwise)
+ RestApi restApi = restApi("info");
+ assert restApi.getPaths().size() == 1;
+ assert restApi.getMethods().size() == 1;
+ RestResponse restResponse = new RestResponse(httpRequestBuilder()
+ .path(restApi.getPaths().get(0))
+ .method(restApi.getMethods().get(0)).execute());
+ checkStatusCode(restResponse);
+ Object version = restResponse.evaluate("version.number");
+ if (version == null) {
+ throw new RuntimeException("elasticsearch version not found in the response");
+ }
+ return version.toString();
+ }
+
+ public String getEsVersion() {
+ return esVersion;
+ }
+
+ /**
+ * Calls an api with the provided parameters
+ * @throws RestException if the obtained status code is non ok, unless the specific error code needs to be ignored
+ * according to the ignore parameter received as input (which won't get sent to elasticsearch)
+ */
+ public RestResponse callApi(String apiName, String... params) throws IOException, RestException {
+ if (params.length % 2 != 0) {
+ throw new IllegalArgumentException("The number of params passed must be even but was [" + params.length + "]");
+ }
+
+ Map paramsMap = Maps.newHashMap();
+ for (int i = 0; i < params.length; i++) {
+ paramsMap.put(params[i++], params[i]);
+ }
+
+ return callApi(apiName, paramsMap, null);
+ }
+
+ /**
+ * Calls an api with the provided parameters and body
+ * @throws RestException if the obtained status code is non ok, unless the specific error code needs to be ignored
+ * according to the ignore parameter received as input (which won't get sent to elasticsearch)
+ */
+ public RestResponse callApi(String apiName, Map params, String body) throws IOException, RestException {
+
+ List ignores = Lists.newArrayList();
+ Map requestParams = null;
+ if (params != null) {
+ //makes a copy of the parameters before modifying them for this specific request
+ requestParams = Maps.newHashMap(params);
+ //ignore is a special parameter supported by the clients, shouldn't be sent to es
+ String ignoreString = requestParams.remove("ignore");
+ if (Strings.hasLength(ignoreString)) {
+ try {
+ ignores.add(Integer.valueOf(ignoreString));
+ } catch(NumberFormatException e) {
+ throw new IllegalArgumentException("ignore value should be a number, found [" + ignoreString + "] instead");
+ }
+ }
+ }
+
+ HttpRequestBuilder httpRequestBuilder = callApiBuilder(apiName, requestParams, body);
+ logger.debug("calling api [{}]", apiName);
+ HttpResponse httpResponse = httpRequestBuilder.execute();
+
+ //http HEAD doesn't support response body
+ // For the few api (exists class of api) that use it we need to accept 404 too
+ if (!httpResponse.supportsBody()) {
+ ignores.add(404);
+ }
+
+ RestResponse restResponse = new RestResponse(httpResponse);
+ checkStatusCode(restResponse, ignores);
+ return restResponse;
+ }
+
+ private void checkStatusCode(RestResponse restResponse, List ignores) throws RestException {
+ //ignore is a catch within the client, to prevent the client from throwing error if it gets non ok codes back
+ if (ignores.contains(restResponse.getStatusCode())) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("ignored non ok status codes {} as requested", ignores);
+ }
+ return;
+ }
+ checkStatusCode(restResponse);
+ }
+
+ private void checkStatusCode(RestResponse restResponse) throws RestException {
+ if (restResponse.isError()) {
+ throw new RestException("non ok status code [" + restResponse.getStatusCode() + "] returned", restResponse);
+ }
+ }
+
+ private HttpRequestBuilder callApiBuilder(String apiName, Map params, String body) {
+ RestApi restApi = restApi(apiName);
+
+ HttpRequestBuilder httpRequestBuilder = httpRequestBuilder().body(body);
+
+ //divide params between ones that go within query string and ones that go within path
+ Map pathParts = Maps.newHashMap();
+ if (params != null) {
+ for (Map.Entry entry : params.entrySet()) {
+ if (restApi.getPathParts().contains(entry.getKey())) {
+ pathParts.put(entry.getKey(), entry.getValue());
+ } else {
+ httpRequestBuilder.addParam(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ //the http method is randomized (out of the available ones with the chosen api)
+ return httpRequestBuilder.method(RandomizedTest.randomFrom(restApi.getSupportedMethods(pathParts.keySet())))
+ .path(restApi.getFinalPath(pathParts));
+ }
+
+ private RestApi restApi(String apiName) {
+ RestApi restApi = restSpec.getApi(apiName);
+ if (restApi == null) {
+ throw new IllegalArgumentException("rest api [" + apiName + "] doesn't exist in the rest spec");
+ }
+ return restApi;
+ }
+
+ protected HttpRequestBuilder httpRequestBuilder() {
+ return new HttpRequestBuilder(httpClient).host(host).port(port);
+ }
+
+ protected CloseableHttpClient createHttpClient() {
+ return HttpClients.createDefault();
+ }
+
+ /**
+ * Closes the REST client and the underlying http client
+ */
+ public void close() {
+ try {
+ httpClient.close();
+ } catch(IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/client/RestException.java b/src/test/java/org/elasticsearch/test/rest/client/RestException.java
new file mode 100644
index 000000000000..5c03f4a2a21e
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/client/RestException.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.client;
+
+/**
+ * Thrown when a status code that holds an error is received (unless needs to be ignored)
+ * Holds the original {@link RestResponse}
+ */
+public class RestException extends Exception {
+
+ private final RestResponse restResponse;
+
+ public RestException(String message, RestResponse restResponse) {
+ super(message);
+ this.restResponse = restResponse;
+ }
+
+ public RestResponse restResponse() {
+ return restResponse;
+ }
+
+ public int statusCode() {
+ return restResponse.getStatusCode();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/client/RestResponse.java b/src/test/java/org/elasticsearch/test/rest/client/RestResponse.java
new file mode 100644
index 000000000000..5e77044f181d
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/client/RestResponse.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.client;
+
+import org.elasticsearch.test.rest.client.http.HttpResponse;
+import org.elasticsearch.test.rest.json.JsonPath;
+
+import java.io.IOException;
+
+/**
+ * Response obtained from a REST call
+ * Supports parsing the response body as json when needed and returning specific values extracted from it
+ */
+public class RestResponse {
+
+ private final HttpResponse response;
+ private JsonPath parsedResponse;
+
+ RestResponse(HttpResponse response) {
+ this.response = response;
+ }
+
+ public int getStatusCode() {
+ return response.getStatusCode();
+ }
+
+ public String getReasonPhrase() {
+ return response.getReasonPhrase();
+ }
+
+ public String getBody() {
+ return response.getBody();
+ }
+
+ public boolean isError() {
+ return response.isError();
+ }
+
+ /**
+ * Parses the response body as json and extracts a specific value from it (identified by the provided path)
+ */
+ public Object evaluate(String path) throws IOException {
+
+ if (response == null) {
+ return null;
+ }
+
+ JsonPath jsonPath = parsedResponse();
+
+ if (jsonPath == null) {
+ //special case: api that don't support body (e.g. exists) return true if 200, false if 404, even if no body
+ //is_true: '' means the response had no body but the client returned true (caused by 200)
+ //is_false: '' means the response had no body but the client returned false (caused by 404)
+ if ("".equals(path) && !response.supportsBody()) {
+ return !response.isError();
+ }
+ return null;
+ }
+
+ return jsonPath.evaluate(path);
+ }
+
+ private JsonPath parsedResponse() throws IOException {
+ if (parsedResponse != null) {
+ return parsedResponse;
+ }
+ if (response == null || !response.hasBody()) {
+ return null;
+ }
+ return parsedResponse = new JsonPath(response.getBody());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/client/http/HttpDeleteWithEntity.java b/src/test/java/org/elasticsearch/test/rest/client/http/HttpDeleteWithEntity.java
new file mode 100644
index 000000000000..e3ec79c3ebd0
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/client/http/HttpDeleteWithEntity.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.client.http;
+
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+
+import java.net.URI;
+
+/**
+ * Allows to send DELETE requests providing a body (not supported out of the box)
+ */
+public class HttpDeleteWithEntity extends HttpEntityEnclosingRequestBase {
+
+ public final static String METHOD_NAME = "DELETE";
+
+ public HttpDeleteWithEntity(final URI uri) {
+ setURI(uri);
+ }
+
+ @Override
+ public String getMethod() {
+ return METHOD_NAME;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/client/http/HttpGetWithEntity.java b/src/test/java/org/elasticsearch/test/rest/client/http/HttpGetWithEntity.java
new file mode 100644
index 000000000000..bf4dbfd0ef2e
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/client/http/HttpGetWithEntity.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.client.http;
+
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+
+import java.net.URI;
+
+/**
+ * Allows to send GET requests providing a body (not supported out of the box)
+ */
+public class HttpGetWithEntity extends HttpEntityEnclosingRequestBase {
+
+ public final static String METHOD_NAME = "GET";
+
+ public HttpGetWithEntity(final URI uri) {
+ setURI(uri);
+ }
+
+ @Override
+ public String getMethod() {
+ return METHOD_NAME;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/elasticsearch/test/rest/client/http/HttpRequestBuilder.java b/src/test/java/org/elasticsearch/test/rest/client/http/HttpRequestBuilder.java
new file mode 100644
index 000000000000..5c0b84da721d
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/client/http/HttpRequestBuilder.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.client.http;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Maps;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.lucene.util.IOUtils;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+/**
+ * Executable builder for an http request
+ * Holds an {@link org.apache.http.client.HttpClient} that is used to send the built http request
+ */
+public class HttpRequestBuilder {
+
+ private static final ESLogger logger = Loggers.getLogger(HttpRequestBuilder.class);
+
+ static final Charset DEFAULT_CHARSET = Charset.forName("utf-8");
+
+ private final CloseableHttpClient httpClient;
+
+ private String host;
+
+ private int port;
+
+ private String path = "";
+
+ private final Map params = Maps.newHashMap();
+
+ private String method = HttpGetWithEntity.METHOD_NAME;
+
+ private String body;
+
+ public HttpRequestBuilder(CloseableHttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public HttpRequestBuilder host(String host) {
+ this.host = host;
+ return this;
+ }
+
+ public HttpRequestBuilder port(int port) {
+ this.port = port;
+ return this;
+ }
+
+ public HttpRequestBuilder path(String path) {
+ this.path = path;
+ return this;
+ }
+
+ public HttpRequestBuilder addParam(String name, String value) {
+ this.params.put(name, value);
+ return this;
+ }
+
+ public HttpRequestBuilder method(String method) {
+ this.method = method;
+ return this;
+ }
+
+ public HttpRequestBuilder body(String body) {
+ if (Strings.hasLength(body)) {
+ this.body = body;
+ }
+ return this;
+ }
+
+ public HttpResponse execute() throws IOException {
+ CloseableHttpResponse closeableHttpResponse = null;
+ try {
+ HttpUriRequest httpUriRequest = buildRequest();
+ if (logger.isTraceEnabled()) {
+ StringBuilder stringBuilder = new StringBuilder(httpUriRequest.getMethod()).append(" ").append(httpUriRequest.getURI());
+ if (Strings.hasLength(body)) {
+ stringBuilder.append("\n").append(body);
+ }
+ logger.trace("sending request \n{}", stringBuilder.toString());
+ }
+ closeableHttpResponse = httpClient.execute(httpUriRequest);
+ HttpResponse httpResponse = new HttpResponse(httpUriRequest, closeableHttpResponse);
+ logger.trace("got response \n{}\n{}", closeableHttpResponse, httpResponse.hasBody() ? httpResponse.getBody() : "");
+ return httpResponse;
+ } finally {
+ try {
+ IOUtils.close(closeableHttpResponse);
+ } catch (IOException e) {
+ logger.error("error closing http response", e);
+ }
+ }
+ }
+
+ private HttpUriRequest buildRequest() {
+
+ if (HttpGetWithEntity.METHOD_NAME.equalsIgnoreCase(method)) {
+ return addOptionalBody(new HttpGetWithEntity(buildUri()));
+ }
+
+ if (HttpHead.METHOD_NAME.equalsIgnoreCase(method)) {
+ checkBodyNotSupported();
+ return new HttpHead(buildUri());
+ }
+
+ if (HttpDeleteWithEntity.METHOD_NAME.equalsIgnoreCase(method)) {
+ return addOptionalBody(new HttpDeleteWithEntity(buildUri()));
+ }
+
+ if (HttpPut.METHOD_NAME.equalsIgnoreCase(method)) {
+ return addOptionalBody(new HttpPut(buildUri()));
+ }
+
+ if (HttpPost.METHOD_NAME.equalsIgnoreCase(method)) {
+ return addOptionalBody(new HttpPost(buildUri()));
+ }
+
+ throw new UnsupportedOperationException("method [" + method + "] not supported");
+ }
+
+ private URI buildUri() {
+ String query;
+ if (params.size() == 0) {
+ query = null;
+ } else {
+ query = Joiner.on('&').withKeyValueSeparator("=").join(params);
+ }
+ try {
+ return new URI("http", null, host, port, path, query, null);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ private HttpEntityEnclosingRequestBase addOptionalBody(HttpEntityEnclosingRequestBase requestBase) {
+ if (Strings.hasText(body)) {
+ requestBase.setEntity(new StringEntity(body, DEFAULT_CHARSET));
+ }
+ return requestBase;
+ }
+
+ private void checkBodyNotSupported() {
+ if (Strings.hasText(body)) {
+ throw new IllegalArgumentException("request body not supported with head request");
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder(method).append(" '")
+ .append(host).append(":").append(port).append(path).append("'");
+ if (!params.isEmpty()) {
+ stringBuilder.append(", params=").append(params);
+ }
+ if (Strings.hasLength(body)) {
+ stringBuilder.append(", body=\n").append(body);
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/client/http/HttpResponse.java b/src/test/java/org/elasticsearch/test/rest/client/http/HttpResponse.java
new file mode 100644
index 000000000000..538aeeb3bc85
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/client/http/HttpResponse.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.client.http;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.util.EntityUtils;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import java.io.IOException;
+
+/**
+ * Response obtained from an http request
+ * Always consumes the whole response body loading it entirely into a string
+ */
+public class HttpResponse {
+
+ private static final ESLogger logger = Loggers.getLogger(HttpResponse.class);
+
+ private final HttpUriRequest httpRequest;
+ private final int statusCode;
+ private final String reasonPhrase;
+ private final String body;
+
+ HttpResponse(HttpUriRequest httpRequest, CloseableHttpResponse httpResponse) {
+ this.httpRequest = httpRequest;
+ this.statusCode = httpResponse.getStatusLine().getStatusCode();
+ this.reasonPhrase = httpResponse.getStatusLine().getReasonPhrase();
+ if (httpResponse.getEntity() != null) {
+ try {
+ this.body = EntityUtils.toString(httpResponse.getEntity(), HttpRequestBuilder.DEFAULT_CHARSET);
+ } catch (IOException e) {
+ EntityUtils.consumeQuietly(httpResponse.getEntity());
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ httpResponse.close();
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+ } else {
+ this.body = null;
+ }
+ }
+
+ public boolean isError() {
+ return statusCode >= 400;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+
+ public String getReasonPhrase() {
+ return reasonPhrase;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public boolean hasBody() {
+ return body != null;
+ }
+
+ public boolean supportsBody() {
+ return !HttpHead.METHOD_NAME.equals(httpRequest.getMethod());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder stringBuilder = new StringBuilder(statusCode).append(" ").append(reasonPhrase);
+ if (hasBody()) {
+ stringBuilder.append("\n").append(body);
+ }
+ return stringBuilder.toString();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/json/JsonPath.java b/src/test/java/org/elasticsearch/test/rest/json/JsonPath.java
new file mode 100644
index 000000000000..1d5e86007d69
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/json/JsonPath.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.json;
+
+import com.google.common.collect.Lists;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Holds a json object and allows to extract specific values from it
+ */
+public class JsonPath {
+
+ final String json;
+ final Map jsonMap;
+
+ public JsonPath(String json) throws IOException {
+ this.json = json;
+ this.jsonMap = convertToMap(json);
+ }
+
+ private static Map convertToMap(String json) throws IOException {
+ return JsonXContent.jsonXContent.createParser(json).mapOrderedAndClose();
+ }
+
+ /**
+ * Returns the object corresponding to the provided path if present, null otherwise
+ */
+ public Object evaluate(String path) {
+ String[] parts = parsePath(path);
+ Object object = jsonMap;
+ for (String part : parts) {
+ object = evaluate(part, object);
+ if (object == null) {
+ return null;
+ }
+ }
+ return object;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object evaluate(String key, Object object) {
+ if (object instanceof Map) {
+ return ((Map) object).get(key);
+ }
+ if (object instanceof List) {
+ List list = (List) object;
+ try {
+ return list.get(Integer.valueOf(key));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("element was a list, but [" + key + "] was not numeric", e);
+ } catch (IndexOutOfBoundsException e) {
+ throw new IllegalArgumentException("element was a list with " + list.size() + " elements, but [" + key + "] was out of bounds", e);
+ }
+ }
+
+ throw new IllegalArgumentException("no object found for [" + key + "] within object of class [" + object.getClass() + "]");
+ }
+
+ private String[] parsePath(String path) {
+ List list = Lists.newArrayList();
+ StringBuilder current = new StringBuilder();
+ boolean escape = false;
+ for (int i = 0; i < path.length(); i++) {
+ char c = path.charAt(i);
+ if (c == '\\') {
+ escape = true;
+ continue;
+ }
+
+ if (c == '.') {
+ if (escape) {
+ escape = false;
+ } else {
+ if (current.length() > 0) {
+ list.add(current.toString());
+ current.setLength(0);
+ }
+ continue;
+ }
+ }
+
+ current.append(c);
+ }
+
+ if (current.length() > 0) {
+ list.add(current.toString());
+ }
+
+ return list.toArray(new String[list.size()]);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/junit/DescriptionHelper.java b/src/test/java/org/elasticsearch/test/rest/junit/DescriptionHelper.java
new file mode 100644
index 000000000000..0a2179e5ff40
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/junit/DescriptionHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.junit;
+
+import com.google.common.base.Joiner;
+import org.elasticsearch.test.rest.section.RestTestSuite;
+import org.elasticsearch.test.rest.section.TestSection;
+import org.junit.runner.Description;
+
+import java.util.Map;
+
+/**
+ * Helper that knows how to assign proper junit {@link Description}s to each of the node in the tests tree
+ */
+public final class DescriptionHelper {
+
+ private DescriptionHelper() {
+
+ }
+
+ /*
+ The following generated ids need to be unique throughout a tests run.
+ Ids are also shown by IDEs (with junit 4.11 unique ids can be different from what gets shown, not yet in 4.10).
+ Some tricks are applied to control what gets shown in IDEs in order to keep the ids unique and nice to see at the same time.
+ */
+
+ static Description createRootDescription(String name) {
+ return Description.createSuiteDescription(name);
+ }
+
+ static Description createApiDescription(String api) {
+ return Description.createSuiteDescription(api);
+ }
+
+ static Description createTestSuiteDescription(RestTestSuite restTestSuite) {
+ //e.g. "indices_open (10_basic)", which leads to 10_basic being returned by Description#getDisplayName
+ String name = restTestSuite.getApi() + " (" + restTestSuite.getName() + ")";
+ return Description.createSuiteDescription(name);
+ }
+
+ static Description createTestSectionWithRepetitionsDescription(RestTestSuite restTestSuite, TestSection testSection) {
+ //e.g. "indices_open/10_basic (Basic test for index open/close)", which leads to
+ //"Basic test for index open/close" being returned by Description#getDisplayName
+ String name = restTestSuite.getDescription() + " (" + testSection.getName() + ")";
+ return Description.createSuiteDescription(name);
+ }
+
+ static Description createTestSectionIterationDescription(RestTestSuite restTestSuite, TestSection testSection, Map args) {
+ //e.g. "Basic test for index open/close {#0} (indices_open/10_basic)" some IDEs might strip out the part between parentheses
+ String name = testSection.getName() + formatMethodArgs(args) + " (" + restTestSuite.getDescription() + ")";
+ return Description.createSuiteDescription(name);
+ }
+
+ private static String formatMethodArgs(Map args) {
+ if (args == null || args.isEmpty()) return "";
+
+ StringBuilder b = new StringBuilder(" {");
+ Joiner.on(" ").withKeyValueSeparator("").appendTo(b, args);
+ b.append("}");
+
+ return b.toString();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/junit/RestReproduceInfoPrinter.java b/src/test/java/org/elasticsearch/test/rest/junit/RestReproduceInfoPrinter.java
new file mode 100644
index 000000000000..3f8f8d7d4b96
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/junit/RestReproduceInfoPrinter.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.junit;
+
+import com.carrotsearch.randomizedtesting.ReproduceErrorMessageBuilder;
+import com.carrotsearch.randomizedtesting.StandaloneRandomizedContext;
+import com.carrotsearch.randomizedtesting.TraceFormatting;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.test.junit.listeners.ReproduceInfoPrinter;
+import org.elasticsearch.test.rest.ElasticsearchRestTests;
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+
+import java.util.Arrays;
+
+import static com.carrotsearch.randomizedtesting.SysGlobals.SYSPROP_RANDOM_SEED;
+import static com.carrotsearch.randomizedtesting.SysGlobals.SYSPROP_TESTCLASS;
+import static org.elasticsearch.test.rest.junit.RestTestSuiteRunner.*;
+
+/**
+ * A {@link org.junit.runner.notification.RunListener} that emits to {@link System#err} a string with command
+ * line parameters allowing quick REST test re-run under MVN command line.
+ */
+class RestReproduceInfoPrinter extends ReproduceInfoPrinter {
+
+ protected static final ESLogger logger = Loggers.getLogger(RestReproduceInfoPrinter.class);
+
+ @Override
+ protected boolean mustAppendClusterSeed(Failure failure) {
+ return isTestCluster();
+ }
+
+ private static boolean isTestCluster() {
+ return runMode() == RunMode.TEST_CLUSTER;
+ }
+
+ @Override
+ protected TraceFormatting traces() {
+ return new TraceFormatting(
+ Arrays.asList(
+ "org.junit.",
+ "junit.framework.",
+ "sun.",
+ "java.lang.reflect.",
+ "com.carrotsearch.randomizedtesting.",
+ "org.elasticsearch.test.rest.junit."
+ ));
+ }
+
+ @Override
+ protected ReproduceErrorMessageBuilder reproduceErrorMessageBuilder(StringBuilder b) {
+ return new MavenMessageBuilder(b);
+ }
+
+ private static class MavenMessageBuilder extends ReproduceInfoPrinter.MavenMessageBuilder {
+
+ public MavenMessageBuilder(StringBuilder b) {
+ super(b);
+ }
+
+ @Override
+ public ReproduceErrorMessageBuilder appendAllOpts(Description description) {
+
+ try {
+ appendOpt(SYSPROP_RANDOM_SEED(), StandaloneRandomizedContext.getSeedAsString());
+ } catch (IllegalStateException e) {
+ logger.warn("No context available when dumping reproduce options?");
+ }
+
+ //we know that ElasticsearchRestTests is the only one that runs with RestTestSuiteRunner
+ appendOpt(SYSPROP_TESTCLASS(), ElasticsearchRestTests.class.getName());
+
+ if (description.getClassName() != null) {
+ appendOpt(REST_TESTS_SUITE, description.getClassName());
+ }
+
+ appendRunnerProperties();
+ appendEnvironmentSettings();
+
+ appendProperties("es.logger.level");
+
+ if (isTestCluster()) {
+ appendProperties("es.node.mode", "es.node.local");
+ }
+
+ appendRestTestsProperties();
+
+ return this;
+ }
+
+ public ReproduceErrorMessageBuilder appendRestTestsProperties() {
+ return appendProperties(REST_TESTS_MODE, REST_TESTS_SPEC);
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/junit/RestTestCandidate.java b/src/test/java/org/elasticsearch/test/rest/junit/RestTestCandidate.java
new file mode 100644
index 000000000000..aaf9d10a765c
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/junit/RestTestCandidate.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.junit;
+
+import org.elasticsearch.test.rest.section.RestTestSuite;
+import org.elasticsearch.test.rest.section.SetupSection;
+import org.elasticsearch.test.rest.section.TestSection;
+import org.junit.runner.Description;
+
+/**
+ * Wraps {@link org.elasticsearch.test.rest.section.TestSection}s ready to be run,
+ * properly enriched with the needed execution information.
+ * The tests tree structure gets flattened to the leaves (test sections)
+ */
+public class RestTestCandidate {
+
+ private final RestTestSuite restTestSuite;
+ private final Description suiteDescription;
+ private final TestSection testSection;
+ private final Description testDescription;
+ private final long seed;
+
+ static RestTestCandidate empty(RestTestSuite restTestSuite, Description suiteDescription) {
+ return new RestTestCandidate(restTestSuite, suiteDescription, null, null, -1);
+ }
+
+ RestTestCandidate(RestTestSuite restTestSuite, Description suiteDescription,
+ TestSection testSection, Description testDescription, long seed) {
+ this.restTestSuite = restTestSuite;
+ this.suiteDescription = suiteDescription;
+ this.testSection = testSection;
+ this.testDescription = testDescription;
+ this.seed = seed;
+ }
+
+ public String getApi() {
+ return restTestSuite.getApi();
+ }
+
+ public String getName() {
+ return restTestSuite.getName();
+ }
+
+ public String getSuiteDescription() {
+ return restTestSuite.getDescription();
+ }
+
+ public Description describeSuite() {
+ return suiteDescription;
+ }
+
+ public Description describeTest() {
+ return testDescription;
+ }
+
+ public SetupSection getSetupSection() {
+ return restTestSuite.getSetupSection();
+ }
+
+ public TestSection getTestSection() {
+ return testSection;
+ }
+
+ public long getSeed() {
+ return seed;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/junit/RestTestSuiteRunner.java b/src/test/java/org/elasticsearch/test/rest/junit/RestTestSuiteRunner.java
new file mode 100644
index 000000000000..453f77dfbab2
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/junit/RestTestSuiteRunner.java
@@ -0,0 +1,546 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.junit;
+
+import com.carrotsearch.hppc.hash.MurmurHash3;
+import com.carrotsearch.randomizedtesting.RandomizedTest;
+import com.carrotsearch.randomizedtesting.Randomness;
+import com.carrotsearch.randomizedtesting.SeedUtils;
+import com.google.common.collect.Lists;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.common.transport.InetSocketTransportAddress;
+import org.elasticsearch.http.HttpServerTransport;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.elasticsearch.test.TestCluster;
+import org.elasticsearch.test.rest.RestTestExecutionContext;
+import org.elasticsearch.test.rest.client.RestException;
+import org.elasticsearch.test.rest.client.RestResponse;
+import org.elasticsearch.test.rest.parser.RestTestParseException;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParser;
+import org.elasticsearch.test.rest.section.DoSection;
+import org.elasticsearch.test.rest.section.ExecutableSection;
+import org.elasticsearch.test.rest.section.RestTestSuite;
+import org.elasticsearch.test.rest.section.TestSection;
+import org.elasticsearch.test.rest.spec.RestSpec;
+import org.elasticsearch.test.rest.support.FileUtils;
+import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.carrotsearch.randomizedtesting.SeedUtils.parseSeedChain;
+import static com.carrotsearch.randomizedtesting.StandaloneRandomizedContext.*;
+import static com.carrotsearch.randomizedtesting.SysGlobals.*;
+import static org.elasticsearch.test.TestCluster.SHARED_CLUSTER_SEED;
+import static org.elasticsearch.test.TestCluster.clusterName;
+import static org.elasticsearch.test.rest.junit.DescriptionHelper.*;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+/**
+ * JUnit runner for elasticsearch REST tests
+ *
+ * Supports the following options provided as system properties:
+ * - tests.rest[true|false|host:port]: determines whether the REST tests need to be run and if so
+ * whether to rely on an external cluster (providing host and port) or fire a test cluster (default)
+ * - tests.rest.suite: comma separated paths of the test suites to be run (by default loaded from /rest-spec/test)
+ * it is possible to run only a subset of the tests providing a directory or a single yaml file
+ * (the default /rest-spec/test prefix is optional when files are loaded from classpath)
+ * - tests.rest.spec: REST spec path (default /rest-spec/api)
+ * - tests.iters: runs multiple iterations
+ * - tests.seed: seed to base the random behaviours on
+ * - tests.appendseed[true|false]: enables adding the seed to each test section's description (default false)
+ * - tests.cluster_seed: seed used to create the test cluster (if enabled)
+ *
+ */
+public class RestTestSuiteRunner extends ParentRunner {
+
+ private static final ESLogger logger = Loggers.getLogger(RestTestSuiteRunner.class);
+
+ public static final String REST_TESTS_MODE = "tests.rest";
+ public static final String REST_TESTS_SUITE = "tests.rest.suite";
+ public static final String REST_TESTS_SPEC = "tests.rest.spec";
+
+ private static final String DEFAULT_TESTS_PATH = "/rest-spec/test";
+ private static final String DEFAULT_SPEC_PATH = "/rest-spec/api";
+ private static final int DEFAULT_ITERATIONS = 1;
+
+ private static final String PATHS_SEPARATOR = ",";
+
+ private final RestTestExecutionContext restTestExecutionContext;
+ private final List restTestCandidates;
+ private final Description rootDescription;
+
+ private final RunMode runMode;
+
+ private final TestCluster testCluster;
+
+ private static final AtomicInteger sequencer = new AtomicInteger();
+
+ /** The runner's seed (master). */
+ private final Randomness runnerRandomness;
+
+ /**
+ * If {@link com.carrotsearch.randomizedtesting.SysGlobals#SYSPROP_RANDOM_SEED} property is used with two arguments
+ * (master:test_section) then this field contains test section level override.
+ */
+ private final Randomness testSectionRandomnessOverride;
+
+ enum RunMode {
+ NO, TEST_CLUSTER, EXTERNAL_CLUSTER
+ }
+
+ static RunMode runMode() {
+ String mode = System.getProperty(REST_TESTS_MODE);
+ if (!Strings.hasLength(mode)) {
+ //default true: we run the tests starting our own test cluster
+ mode = Boolean.TRUE.toString();
+ }
+
+ if (Boolean.FALSE.toString().equalsIgnoreCase(mode)) {
+ return RunMode.NO;
+ }
+ if (Boolean.TRUE.toString().equalsIgnoreCase(mode)) {
+ return RunMode.TEST_CLUSTER;
+ }
+ return RunMode.EXTERNAL_CLUSTER;
+ }
+
+ public RestTestSuiteRunner(Class> testClass) throws InitializationError {
+ super(testClass);
+
+ this.runMode = runMode();
+
+ if (runMode == RunMode.NO) {
+ //the tests won't be run. the run method will be called anyway but we'll just mark the whole suite as ignored
+ //no need to go ahead and parse the test suites then
+ this.runnerRandomness = null;
+ this.testSectionRandomnessOverride = null;
+ this.restTestExecutionContext = null;
+ this.restTestCandidates = null;
+ this.rootDescription = createRootDescription(getRootSuiteTitle());
+ this.rootDescription.addChild(createApiDescription("empty suite"));
+ this.testCluster = null;
+ return;
+ }
+
+ //the REST test suite is supposed to be run only once per jvm against either an external es node or a self started one
+ if (sequencer.getAndIncrement() > 0) {
+ throw new InitializationError("only one instance of RestTestSuiteRunner can be created per jvm");
+ }
+
+ //either read the seed from system properties (first one in the chain) or generate a new one
+ final String globalSeed = System.getProperty(SYSPROP_RANDOM_SEED());
+ final long initialSeed;
+ Randomness randomnessOverride = null;
+ if (Strings.hasLength(globalSeed)) {
+ final long[] seedChain = parseSeedChain(globalSeed);
+ if (seedChain.length == 0 || seedChain.length > 2) {
+ throw new IllegalArgumentException("Invalid system property "
+ + SYSPROP_RANDOM_SEED() + " specification: " + globalSeed);
+ }
+ if (seedChain.length > 1) {
+ //read the test section level seed if present
+ randomnessOverride = new Randomness(seedChain[1]);
+ }
+ initialSeed = seedChain[0];
+ } else {
+ initialSeed = MurmurHash3.hash(System.nanoTime());
+ }
+ this.runnerRandomness = new Randomness(initialSeed);
+ this.testSectionRandomnessOverride = randomnessOverride;
+ logger.info("Master seed: {}", SeedUtils.formatSeed(initialSeed));
+
+ String host;
+ int port;
+ if (runMode == RunMode.TEST_CLUSTER) {
+ this.testCluster = new TestCluster(SHARED_CLUSTER_SEED, 1, clusterName("REST-tests", ElasticsearchTestCase.CHILD_VM_ID, SHARED_CLUSTER_SEED));
+ this.testCluster.beforeTest(runnerRandomness.getRandom(), 0.0f);
+ HttpServerTransport httpServerTransport = testCluster.getInstance(HttpServerTransport.class);
+ InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) httpServerTransport.boundAddress().publishAddress();
+ host = inetSocketTransportAddress.address().getHostName();
+ port = inetSocketTransportAddress.address().getPort();
+ } else {
+ this.testCluster = null;
+ String testsMode = System.getProperty(REST_TESTS_MODE);
+ String[] split = testsMode.split(":");
+ if (split.length < 2) {
+ throw new InitializationError("address [" + testsMode + "] not valid");
+ }
+ host = split[0];
+ try {
+ port = Integer.valueOf(split[1]);
+ } catch(NumberFormatException e) {
+ throw new InitializationError("port is not valid, expected number but was [" + split[1] + "]");
+ }
+ }
+
+ try {
+ String[] specPaths = resolvePathsProperty(REST_TESTS_SPEC, DEFAULT_SPEC_PATH);
+ RestSpec restSpec = RestSpec.parseFrom(DEFAULT_SPEC_PATH, specPaths);
+
+ this.restTestExecutionContext = new RestTestExecutionContext(host, port, restSpec);
+ this.rootDescription = createRootDescription(getRootSuiteTitle());
+ this.restTestCandidates = collectTestCandidates(rootDescription);
+ } catch (Throwable e) {
+ stopTestCluster();
+ throw new InitializationError(e);
+ }
+ }
+
+ /**
+ * Parse the test suites and creates the test candidates to be run, together with their junit descriptions.
+ * The descriptions will be part of a tree containing api/yaml file/test section/eventual multiple iterations.
+ * The test candidates will be instead flattened out to the leaves level (iterations), the part that needs to be run.
+ */
+ protected List collectTestCandidates(Description rootDescription)
+ throws RestTestParseException, IOException {
+
+ String[] paths = resolvePathsProperty(REST_TESTS_SUITE, DEFAULT_TESTS_PATH);
+ Map> yamlSuites = FileUtils.findYamlSuites(DEFAULT_TESTS_PATH, paths);
+
+ int iterations = determineTestSectionIterationCount();
+ boolean appendSeedParameter = RandomizedTest.systemPropertyAsBoolean(SYSPROP_APPEND_SEED(), false);
+
+ //we iterate over the files and we shuffle them (grouped by api, and by yaml file)
+ //meanwhile we create the junit descriptions and test candidates (one per iteration)
+
+ //yaml suites are grouped by directory (effectively by api)
+ List apis = Lists.newArrayList(yamlSuites.keySet());
+ Collections.shuffle(apis, runnerRandomness.getRandom());
+
+ final boolean fixedSeed = testSectionRandomnessOverride != null;
+ final boolean hasRepetitions = iterations > 1;
+
+ List testCandidates = Lists.newArrayList();
+ RestTestSuiteParser restTestSuiteParser = new RestTestSuiteParser();
+ for (String api : apis) {
+
+ Description apiDescription = createApiDescription(api);
+ rootDescription.addChild(apiDescription);
+
+ List yamlFiles = Lists.newArrayList(yamlSuites.get(api));
+ Collections.shuffle(yamlFiles, runnerRandomness.getRandom());
+
+ for (File yamlFile : yamlFiles) {
+ RestTestSuite restTestSuite = restTestSuiteParser.parse(restTestExecutionContext.esVersion(), api, yamlFile);
+ Description testSuiteDescription = createTestSuiteDescription(restTestSuite);
+ apiDescription.addChild(testSuiteDescription);
+
+ if (restTestSuite.getTestSections().size() == 0) {
+ assert restTestSuite.getSetupSection().getSkipSection().skipVersion(restTestExecutionContext.esVersion());
+ testCandidates.add(RestTestCandidate.empty(restTestSuite, testSuiteDescription));
+ continue;
+ }
+
+ Collections.shuffle(restTestSuite.getTestSections(), runnerRandomness.getRandom());
+
+ for (TestSection testSection : restTestSuite.getTestSections()) {
+
+ //no need to generate seed if we are going to skip the test section
+ if (testSection.getSkipSection().skipVersion(restTestExecutionContext.esVersion())) {
+ Description testSectionDescription = createTestSectionIterationDescription(restTestSuite, testSection, null);
+ testSuiteDescription.addChild(testSectionDescription);
+ testCandidates.add(new RestTestCandidate(restTestSuite, testSuiteDescription, testSection, testSectionDescription, -1));
+ continue;
+ }
+
+ Description parentDescription;
+ if (hasRepetitions) {
+ //additional level to group multiple iterations under the same test section's node
+ parentDescription = createTestSectionWithRepetitionsDescription(restTestSuite, testSection);
+ testSuiteDescription.addChild(parentDescription);
+ } else {
+ parentDescription = testSuiteDescription;
+ }
+
+ final long testSectionSeed = determineTestSectionSeed(restTestSuite.getDescription() + "/" + testSection.getName());
+ for (int i = 0; i < iterations; i++) {
+ //test section name argument needs to be unique here
+ long thisSeed = (fixedSeed ? testSectionSeed : testSectionSeed ^ MurmurHash3.hash((long) i));
+
+ final LinkedHashMap args = new LinkedHashMap();
+ if (hasRepetitions) {
+ args.put("#", i);
+ }
+ if (hasRepetitions || appendSeedParameter) {
+ args.put("seed=", SeedUtils.formatSeedChain(runnerRandomness, new Randomness(thisSeed)));
+ }
+
+ Description testSectionDescription = createTestSectionIterationDescription(restTestSuite, testSection, args);
+ parentDescription.addChild(testSectionDescription);
+ testCandidates.add(new RestTestCandidate(restTestSuite, testSuiteDescription, testSection, testSectionDescription, thisSeed));
+ }
+ }
+ }
+ }
+
+ return testCandidates;
+ }
+
+ protected String getRootSuiteTitle() {
+ if (runMode == RunMode.NO) {
+ return "elasticsearch REST Tests - not run";
+ }
+ if (runMode == RunMode.TEST_CLUSTER) {
+ return String.format(Locale.ROOT, "elasticsearch REST Tests - test cluster %s", SeedUtils.formatSeed(SHARED_CLUSTER_SEED));
+ }
+ if (runMode == RunMode.EXTERNAL_CLUSTER) {
+ return String.format(Locale.ROOT, "elasticsearch REST Tests - external cluster %s", System.getProperty(REST_TESTS_MODE));
+ }
+ throw new UnsupportedOperationException("runMode [" + runMode + "] not supported");
+ }
+
+ private int determineTestSectionIterationCount() {
+ int iterations = RandomizedTest.systemPropertyAsInt(SYSPROP_ITERATIONS(), DEFAULT_ITERATIONS);
+ if (iterations < 1) {
+ throw new IllegalArgumentException("System property " + SYSPROP_ITERATIONS() + " must be >= 1 but was [" + iterations + "]");
+ }
+ return iterations;
+ }
+
+ protected static String[] resolvePathsProperty(String propertyName, String defaultValue) {
+ String property = System.getProperty(propertyName);
+ if (!Strings.hasLength(property)) {
+ return new String[]{defaultValue};
+ } else {
+ return property.split(PATHS_SEPARATOR);
+ }
+ }
+
+ /**
+ * Determine a given test section's initial random seed
+ */
+ private long determineTestSectionSeed(String testSectionName) {
+ if (testSectionRandomnessOverride != null) {
+ return getSeed(testSectionRandomnessOverride);
+ }
+
+ // We assign each test section a different starting hash based on the global seed
+ // and a hash of their name (so that the order of sections does not matter, only their names)
+ return getSeed(runnerRandomness) ^ MurmurHash3.hash((long) testSectionName.hashCode());
+ }
+
+ @Override
+ protected List getChildren() {
+ return restTestCandidates;
+ }
+
+ @Override
+ public Description getDescription() {
+ return rootDescription;
+ }
+
+ @Override
+ protected Description describeChild(RestTestCandidate child) {
+ return child.describeTest();
+ }
+
+ @Override
+ protected Statement classBlock(RunNotifier notifier) {
+ //we remove support for @BeforeClass & @AfterClass and JUnit Rules (as we don't call super)
+ Statement statement = childrenInvoker(notifier);
+ statement = withExecutionContextClose(statement);
+ if (testCluster != null) {
+ return withTestClusterClose(statement);
+ }
+ return statement;
+ }
+
+ protected Statement withExecutionContextClose(Statement statement) {
+ return new RunAfter(statement, new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ restTestExecutionContext.close();
+ }
+ });
+ }
+
+ protected Statement withTestClusterClose(Statement statement) {
+ return new RunAfter(statement, new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ stopTestCluster();
+ }
+ });
+ }
+
+ @Override
+ public void run(final RunNotifier notifier) {
+
+ if (runMode == RunMode.NO) {
+ notifier.fireTestIgnored(rootDescription.getChildren().get(0));
+ return;
+ }
+
+ notifier.addListener(new RestReproduceInfoPrinter());
+
+ //the test suite gets run on a separate thread as the randomized context is per thread
+ //once the randomized context is disposed it's not possible to create it again on the same thread
+ final Thread thread = new Thread() {
+ @Override
+ public void run() {
+ try {
+ createRandomizedContext(getTestClass().getJavaClass(), runnerRandomness);
+ RestTestSuiteRunner.super.run(notifier);
+ } finally {
+ disposeRandomizedContext();
+ }
+ }
+ };
+
+ thread.start();
+ try {
+ thread.join();
+ } catch (InterruptedException e) {
+ notifier.fireTestFailure(new Failure(getDescription(),
+ new RuntimeException("Interrupted while waiting for the suite runner? Weird.", e)));
+ }
+ }
+
+ @Override
+ protected void runChild(RestTestCandidate testCandidate, RunNotifier notifier) {
+
+ //if the while suite needs to be skipped, no test sections were loaded, only an empty one that we need to mark as ignored
+ if (testCandidate.getSetupSection().getSkipSection().skipVersion(restTestExecutionContext.esVersion())) {
+ logger.info("skipped test suite [{}]\nreason: {}\nskip versions: {} (current version: {})",
+ testCandidate.getSuiteDescription(), testCandidate.getSetupSection().getSkipSection().getReason(),
+ testCandidate.getSetupSection().getSkipSection().getVersion(), restTestExecutionContext.esVersion());
+
+ notifier.fireTestIgnored(testCandidate.describeSuite());
+ return;
+ }
+
+ //from now on no more empty test candidates are expected
+ assert testCandidate.getTestSection() != null;
+
+ if (testCandidate.getTestSection().getSkipSection().skipVersion(restTestExecutionContext.esVersion())) {
+ logger.info("skipped test [{}/{}]\nreason: {}\nskip versions: {} (current version: {})",
+ testCandidate.getSuiteDescription(), testCandidate.getTestSection().getName(),
+ testCandidate.getTestSection().getSkipSection().getReason(),
+ testCandidate.getTestSection().getSkipSection().getVersion(), restTestExecutionContext.esVersion());
+
+ notifier.fireTestIgnored(testCandidate.describeTest());
+ return;
+ }
+
+ runLeaf(methodBlock(testCandidate), testCandidate.describeTest(), notifier);
+ }
+
+ protected Statement methodBlock(final RestTestCandidate testCandidate) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ final String testThreadName = "TEST-" + testCandidate.getSuiteDescription() +
+ "." + testCandidate.getTestSection().getName() + "-seed#" + SeedUtils.formatSeedChain(runnerRandomness);
+ // This has a side effect of setting up a nested context for the test thread.
+ final String restoreName = Thread.currentThread().getName();
+ try {
+ Thread.currentThread().setName(testThreadName);
+ pushRandomness(new Randomness(testCandidate.getSeed()));
+ runTestSection(testCandidate);
+ } finally {
+ Thread.currentThread().setName(restoreName);
+ popAndDestroy();
+ }
+ }
+ };
+ }
+
+ protected void runTestSection(RestTestCandidate testCandidate)
+ throws IOException, RestException {
+
+ //let's check that there is something to run, otherwise there might be a problem with the test section
+ if (testCandidate.getTestSection().getExecutableSections().size() == 0) {
+ throw new IllegalArgumentException("No executable sections loaded for ["
+ + testCandidate.getSuiteDescription() + "/" + testCandidate.getTestSection().getName() + "]");
+ }
+
+ logger.info("cleaning up before test [{}: {}]", testCandidate.getSuiteDescription(), testCandidate.getTestSection().getName());
+ tearDown();
+
+ logger.info("start test [{}: {}]", testCandidate.getSuiteDescription(), testCandidate.getTestSection().getName());
+
+ if (!testCandidate.getSetupSection().isEmpty()) {
+ logger.info("start setup test [{}: {}]", testCandidate.getSuiteDescription(), testCandidate.getTestSection().getName());
+ for (DoSection doSection : testCandidate.getSetupSection().getDoSections()) {
+ doSection.execute(restTestExecutionContext);
+ }
+ logger.info("end setup test [{}: {}]", testCandidate.getSuiteDescription(), testCandidate.getTestSection().getName());
+ }
+
+ restTestExecutionContext.clear();
+
+ for (ExecutableSection executableSection : testCandidate.getTestSection().getExecutableSections()) {
+ executableSection.execute(restTestExecutionContext);
+ }
+
+ logger.info("end test [{}: {}]", testCandidate.getSuiteDescription(), testCandidate.getTestSection().getName());
+
+ logger.info("cleaning up after test [{}: {}]", testCandidate.getSuiteDescription(), testCandidate.getTestSection().getName());
+ tearDown();
+ }
+
+ private void tearDown() throws IOException, RestException {
+ wipeIndices();
+ wipeTemplates();
+ restTestExecutionContext.clear();
+ }
+
+ private void wipeIndices() throws IOException, RestException {
+ logger.debug("deleting all indices");
+ RestResponse restResponse = restTestExecutionContext.callApiInternal("indices.delete", "index", "_all");
+ assertThat(restResponse.getStatusCode(), equalTo(200));
+ }
+
+ @SuppressWarnings("unchecked")
+ public void wipeTemplates() throws IOException, RestException {
+ logger.debug("deleting all templates");
+ //delete templates by wildcard was only added in 0.90.6
+ //httpResponse = restTestExecutionContext.callApi("indices.delete_template", "name", "*");
+ RestResponse restResponse = restTestExecutionContext.callApiInternal("cluster.state", "filter_nodes", "true",
+ "filter_routing_table", "true", "filter_blocks", "true");
+ assertThat(restResponse.getStatusCode(), equalTo(200));
+ Object object = restResponse.evaluate("metadata.templates");
+ assertThat(object, instanceOf(Map.class));
+ Set templates = ((Map) object).keySet();
+ for (String template : templates) {
+ restResponse = restTestExecutionContext.callApiInternal("indices.delete_template", "name", template);
+ assertThat(restResponse.getStatusCode(), equalTo(200));
+ }
+ }
+
+ private void stopTestCluster() {
+ if (runMode == RunMode.TEST_CLUSTER) {
+ assert testCluster != null;
+ testCluster.afterTest();
+ testCluster.close();
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/junit/RunAfter.java b/src/test/java/org/elasticsearch/test/rest/junit/RunAfter.java
new file mode 100644
index 000000000000..abd1b2b98c7c
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/junit/RunAfter.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.junit;
+
+import com.google.common.collect.Lists;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+
+import java.util.List;
+
+/**
+ * {@link Statement} that allows to run a specific statement after another one
+ */
+public class RunAfter extends Statement {
+
+ private final Statement next;
+ private final Statement after;
+
+ public RunAfter(Statement next, Statement after) {
+ this.next = next;
+ this.after = after;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ List errors = Lists.newArrayList();
+ try {
+ next.evaluate();
+ } catch (Throwable e) {
+ errors.add(e);
+ } finally {
+ try {
+ after.evaluate();
+ } catch (Throwable e) {
+ errors.add(e);
+ }
+ }
+ MultipleFailureException.assertEmpty(errors);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/DoSectionParser.java b/src/test/java/org/elasticsearch/test/rest/parser/DoSectionParser.java
new file mode 100644
index 000000000000..cdbfe0fe9f2e
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/DoSectionParser.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.rest.section.ApiCallSection;
+import org.elasticsearch.test.rest.section.DoSection;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Parser for do sections
+ */
+public class DoSectionParser implements RestTestFragmentParser {
+
+ @Override
+ public DoSection parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+
+ XContentParser parser = parseContext.parser();
+
+ String currentFieldName = null;
+ XContentParser.Token token;
+
+ DoSection doSection = new DoSection();
+
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if ("catch".equals(currentFieldName)) {
+ doSection.setCatch(parser.text());
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ if (currentFieldName != null) {
+ ApiCallSection apiCallSection = new ApiCallSection(currentFieldName);
+ String paramName = null;
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ paramName = parser.currentName();
+ } else if (token.isValue()) {
+ if ("body".equals(paramName)) {
+ apiCallSection.addBody(parser.text());
+ } else {
+ apiCallSection.addParam(paramName, parser.text());
+ }
+ } else if (token == XContentParser.Token.START_OBJECT) {
+ if ("body".equals(paramName)) {
+ Map map = parser.mapOrdered();
+ XContentBuilder contentBuilder = XContentFactory.jsonBuilder().map(map);
+ apiCallSection.addBody(contentBuilder.string());
+ }
+ }
+ }
+ doSection.setApiCallSection(apiCallSection);
+ }
+ }
+ }
+
+ parser.nextToken();
+
+ if (doSection.getApiCallSection() == null) {
+ throw new RestTestParseException("client call section is mandatory within a do section");
+ }
+
+ return doSection;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/GreaterThanParser.java b/src/test/java/org/elasticsearch/test/rest/parser/GreaterThanParser.java
new file mode 100644
index 000000000000..3ca5214346f9
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/GreaterThanParser.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.test.rest.section.GreaterThanAssertion;
+
+import java.io.IOException;
+
+/**
+ * Parser for gt assert sections
+ */
+public class GreaterThanParser implements RestTestFragmentParser {
+
+ @Override
+ public GreaterThanAssertion parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ Tuple stringObjectTuple = parseContext.parseTuple();
+ if (! (stringObjectTuple.v2() instanceof Comparable) ) {
+ throw new RestTestParseException("gt section can only be used with objects that support natural ordering, found " + stringObjectTuple.v2().getClass().getSimpleName());
+ }
+ return new GreaterThanAssertion(stringObjectTuple.v1(), stringObjectTuple.v2());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/IsFalseParser.java b/src/test/java/org/elasticsearch/test/rest/parser/IsFalseParser.java
new file mode 100644
index 000000000000..2c113a874a4a
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/IsFalseParser.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.test.rest.section.IsFalseAssertion;
+
+import java.io.IOException;
+
+/**
+ * Parser for is_false assert sections
+ */
+public class IsFalseParser implements RestTestFragmentParser {
+
+ @Override
+ public IsFalseAssertion parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ return new IsFalseAssertion(parseContext.parseField());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/IsTrueParser.java b/src/test/java/org/elasticsearch/test/rest/parser/IsTrueParser.java
new file mode 100644
index 000000000000..37e5cb2cfebd
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/IsTrueParser.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.test.rest.section.IsTrueAssertion;
+
+import java.io.IOException;
+
+/**
+ * Parser for is_true assert sections
+ */
+public class IsTrueParser implements RestTestFragmentParser {
+
+ @Override
+ public IsTrueAssertion parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ return new IsTrueAssertion(parseContext.parseField());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/LengthParser.java b/src/test/java/org/elasticsearch/test/rest/parser/LengthParser.java
new file mode 100644
index 000000000000..63ce031a47e6
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/LengthParser.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.test.rest.section.LengthAssertion;
+
+import java.io.IOException;
+
+/**
+ * Parser for length assert sections
+ */
+public class LengthParser implements RestTestFragmentParser {
+
+ @Override
+ public LengthAssertion parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ Tuple stringObjectTuple = parseContext.parseTuple();
+ assert stringObjectTuple.v2() != null;
+ int value;
+ if (stringObjectTuple.v2() instanceof Number) {
+ value = ((Number) stringObjectTuple.v2()).intValue();
+ } else {
+ try {
+ value = Integer.valueOf(stringObjectTuple.v2().toString());
+ } catch(NumberFormatException e) {
+ throw new RestTestParseException("length is not a valid number", e);
+ }
+
+ }
+ return new LengthAssertion(stringObjectTuple.v1(), value);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/LessThanParser.java b/src/test/java/org/elasticsearch/test/rest/parser/LessThanParser.java
new file mode 100644
index 000000000000..245989e3599b
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/LessThanParser.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.test.rest.section.LessThanAssertion;
+
+import java.io.IOException;
+
+/**
+ * Parser for lt assert sections
+ */
+public class LessThanParser implements RestTestFragmentParser {
+
+ @Override
+ public LessThanAssertion parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ Tuple stringObjectTuple = parseContext.parseTuple();
+ if (! (stringObjectTuple.v2() instanceof Comparable) ) {
+ throw new RestTestParseException("lt section can only be used with objects that support natural ordering, found " + stringObjectTuple.v2().getClass().getSimpleName());
+ }
+ return new LessThanAssertion(stringObjectTuple.v1(), stringObjectTuple.v2());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/MatchParser.java b/src/test/java/org/elasticsearch/test/rest/parser/MatchParser.java
new file mode 100644
index 000000000000..4c3498909175
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/MatchParser.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.test.rest.section.MatchAssertion;
+
+import java.io.IOException;
+
+/**
+ * Parser for match assert sections
+ */
+public class MatchParser implements RestTestFragmentParser {
+
+ @Override
+ public MatchAssertion parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ Tuple stringObjectTuple = parseContext.parseTuple();
+ return new MatchAssertion(stringObjectTuple.v1(), stringObjectTuple.v2());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/RestTestFragmentParser.java b/src/test/java/org/elasticsearch/test/rest/parser/RestTestFragmentParser.java
new file mode 100644
index 000000000000..0528b8e9fdf9
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/RestTestFragmentParser.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import java.io.IOException;
+
+/**
+ * Base parser for a REST test suite fragment
+ * @param the test fragment's type that gets parsed and returned
+ */
+public interface RestTestFragmentParser {
+
+ /**
+ * Parses a test fragment given the current {@link RestTestSuiteParseContext}
+ */
+ T parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException;
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/RestTestParseException.java b/src/test/java/org/elasticsearch/test/rest/parser/RestTestParseException.java
new file mode 100644
index 000000000000..4ad0b76840f6
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/RestTestParseException.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+/**
+ * Exception thrown whenever there is a problem parsing any of the REST test suite fragment
+ */
+public class RestTestParseException extends Exception {
+
+ RestTestParseException(String message) {
+ super(message);
+ }
+
+ RestTestParseException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/RestTestSectionParser.java b/src/test/java/org/elasticsearch/test/rest/parser/RestTestSectionParser.java
new file mode 100644
index 000000000000..6689ac6cefdc
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/RestTestSectionParser.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.rest.section.TestSection;
+
+import java.io.IOException;
+
+/**
+ * Parser for a complete test section
+ *
+ * Depending on the elasticsearch version the tests are going to run against, test sections might need to get skipped
+ * In that case the relevant test sections parsing is entirely skipped
+ */
+public class RestTestSectionParser implements RestTestFragmentParser {
+
+ @Override
+ public TestSection parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ XContentParser parser = parseContext.parser();
+ parseContext.advanceToFieldName();
+ TestSection testSection = new TestSection(parser.currentName());
+ parser.nextToken();
+ testSection.setSkipSection(parseContext.parseSkipSection());
+
+ boolean skip = testSection.getSkipSection().skipVersion(parseContext.getCurrentVersion());
+
+ while ( parser.currentToken() != XContentParser.Token.END_ARRAY) {
+ if (skip) {
+ //if there was a skip section, there was a setup section as well, which means that we are sure
+ // the current token is at the beginning of a new object
+ assert parser.currentToken() == XContentParser.Token.START_OBJECT;
+ //we need to be at the beginning of an object to be able to skip children
+ parser.skipChildren();
+ //after skipChildren we are at the end of the skipped object, need to move on
+ parser.nextToken();
+ } else {
+ parseContext.advanceToFieldName();
+ testSection.addExecutableSection(parseContext.parseExecutableSection());
+ }
+ }
+
+ parser.nextToken();
+ assert parser.currentToken() == XContentParser.Token.END_OBJECT;
+ parser.nextToken();
+
+ return testSection;
+ }
+
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/RestTestSuiteParseContext.java b/src/test/java/org/elasticsearch/test/rest/parser/RestTestSuiteParseContext.java
new file mode 100644
index 000000000000..1b4f868e77e1
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/RestTestSuiteParseContext.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import com.google.common.collect.Maps;
+import org.elasticsearch.common.collect.Tuple;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.rest.section.*;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Context shared across the whole tests parse phase.
+ * Provides shared parse methods and holds information needed to parse the test sections (e.g. es version)
+ */
+public class RestTestSuiteParseContext {
+
+ private static final SetupSectionParser SETUP_SECTION_PARSER = new SetupSectionParser();
+ private static final RestTestSectionParser TEST_SECTION_PARSER = new RestTestSectionParser();
+ private static final SkipSectionParser SKIP_SECTION_PARSER = new SkipSectionParser();
+ private static final DoSectionParser DO_SECTION_PARSER = new DoSectionParser();
+ private static final Map> EXECUTABLE_SECTIONS_PARSERS = Maps.newHashMap();
+ static {
+ EXECUTABLE_SECTIONS_PARSERS.put("do", DO_SECTION_PARSER);
+ EXECUTABLE_SECTIONS_PARSERS.put("set", new SetSectionParser());
+ EXECUTABLE_SECTIONS_PARSERS.put("match", new MatchParser());
+ EXECUTABLE_SECTIONS_PARSERS.put("is_true", new IsTrueParser());
+ EXECUTABLE_SECTIONS_PARSERS.put("is_false", new IsFalseParser());
+ EXECUTABLE_SECTIONS_PARSERS.put("gt", new GreaterThanParser());
+ EXECUTABLE_SECTIONS_PARSERS.put("lt", new LessThanParser());
+ EXECUTABLE_SECTIONS_PARSERS.put("length", new LengthParser());
+ }
+
+ private final String api;
+ private final String suiteName;
+ private final XContentParser parser;
+ private final String currentVersion;
+
+ public RestTestSuiteParseContext(String api, String suiteName, XContentParser parser, String currentVersion) {
+ this.api = api;
+ this.suiteName = suiteName;
+ this.parser = parser;
+ this.currentVersion = currentVersion;
+ }
+
+ public String getApi() {
+ return api;
+ }
+
+ public String getSuiteName() {
+ return suiteName;
+ }
+
+ public XContentParser parser() {
+ return parser;
+ }
+
+ public String getCurrentVersion() {
+ return currentVersion;
+ }
+
+ public SetupSection parseSetupSection() throws IOException, RestTestParseException {
+
+ advanceToFieldName();
+
+ if ("setup".equals(parser.currentName())) {
+ parser.nextToken();
+ SetupSection setupSection = SETUP_SECTION_PARSER.parse(this);
+ parser.nextToken();
+ return setupSection;
+ }
+
+ return SetupSection.EMPTY;
+ }
+
+ public TestSection parseTestSection() throws IOException, RestTestParseException {
+ return TEST_SECTION_PARSER.parse(this);
+ }
+
+ public SkipSection parseSkipSection() throws IOException, RestTestParseException {
+
+ advanceToFieldName();
+
+ if ("skip".equals(parser.currentName())) {
+ SkipSection skipSection = SKIP_SECTION_PARSER.parse(this);
+ parser.nextToken();
+ return skipSection;
+ }
+
+ return SkipSection.EMPTY;
+ }
+
+ public ExecutableSection parseExecutableSection() throws IOException, RestTestParseException {
+ advanceToFieldName();
+ String section = parser.currentName();
+ RestTestFragmentParser extends ExecutableSection> execSectionParser = EXECUTABLE_SECTIONS_PARSERS.get(section);
+ if (execSectionParser == null) {
+ throw new RestTestParseException("no parser found for executable section [" + section + "]");
+ }
+ ExecutableSection executableSection = execSectionParser.parse(this);
+ parser.nextToken();
+ return executableSection;
+ }
+
+ public DoSection parseDoSection() throws IOException, RestTestParseException {
+ return DO_SECTION_PARSER.parse(this);
+ }
+
+ public void advanceToFieldName() throws IOException, RestTestParseException {
+ XContentParser.Token token = parser.currentToken();
+ //we are in the beginning, haven't called nextToken yet
+ if (token == null) {
+ token = parser.nextToken();
+ }
+ if (token == XContentParser.Token.START_ARRAY) {
+ token = parser.nextToken();
+ }
+ if (token == XContentParser.Token.START_OBJECT) {
+ token = parser.nextToken();
+ }
+ if (token != XContentParser.Token.FIELD_NAME) {
+ throw new RestTestParseException("malformed test section: field suiteName expected but found " + token);
+ }
+ }
+
+ public String parseField() throws IOException, RestTestParseException {
+ parser.nextToken();
+ assert parser.currentToken().isValue();
+ String field = parser.text();
+ parser.nextToken();
+ return field;
+ }
+
+ public Tuple parseTuple() throws IOException, RestTestParseException {
+ parser.nextToken();
+ advanceToFieldName();
+ Map map = parser.map();
+ assert parser.currentToken() == XContentParser.Token.END_OBJECT;
+ parser.nextToken();
+
+ if (map.size() != 1) {
+ throw new RestTestParseException("expected key value pair but found " + map.size() + " ");
+ }
+
+ Map.Entry entry = map.entrySet().iterator().next();
+ return Tuple.tuple(entry.getKey(), entry.getValue());
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/RestTestSuiteParser.java b/src/test/java/org/elasticsearch/test/rest/parser/RestTestSuiteParser.java
new file mode 100644
index 000000000000..3d0203424be5
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/RestTestSuiteParser.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.rest.section.RestTestSuite;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * Parser for a complete test suite (yaml file)
+ *
+ * Depending on the elasticsearch version the tests are going to run against, a whole test suite might need to get skipped
+ * In that case the relevant test sections parsing is entirely skipped
+ */
+public class RestTestSuiteParser implements RestTestFragmentParser {
+
+ public RestTestSuite parse(String currentVersion, String api, File file) throws IOException, RestTestParseException {
+
+ if (!file.isFile()) {
+ throw new IllegalArgumentException(file.getAbsolutePath() + " is not a file");
+ }
+
+ XContentParser parser = YamlXContent.yamlXContent.createParser(new FileInputStream(file));
+ try {
+ String filename = file.getName();
+ //remove the file extension
+ int i = filename.lastIndexOf('.');
+ if (i > 0) {
+ filename = filename.substring(0, i);
+ }
+ RestTestSuiteParseContext testParseContext = new RestTestSuiteParseContext(api, filename, parser, currentVersion);
+ return parse(testParseContext);
+ } finally {
+ parser.close();
+ }
+ }
+
+ @Override
+ public RestTestSuite parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+ XContentParser parser = parseContext.parser();
+
+ parser.nextToken();
+ assert parser.currentToken() == XContentParser.Token.START_OBJECT;
+
+ RestTestSuite restTestSuite = new RestTestSuite(parseContext.getApi(), parseContext.getSuiteName());
+
+ restTestSuite.setSetupSection(parseContext.parseSetupSection());
+
+ boolean skip = restTestSuite.getSetupSection().getSkipSection().skipVersion(parseContext.getCurrentVersion());
+
+ while(true) {
+ //the "---" section separator is not understood by the yaml parser. null is returned, same as when the parser is closed
+ //we need to somehow distinguish between a null in the middle of a test ("---")
+ // and a null at the end of the file (at least two consecutive null tokens)
+ if(parser.currentToken() == null) {
+ if (parser.nextToken() == null) {
+ break;
+ }
+ }
+
+ if (skip) {
+ //if there was a skip section, there was a setup section as well, which means that we are sure
+ // the current token is at the beginning of a new object
+ assert parser.currentToken() == XContentParser.Token.START_OBJECT;
+ //we need to be at the beginning of an object to be able to skip children
+ parser.skipChildren();
+ //after skipChildren we are at the end of the skipped object, need to move on
+ parser.nextToken();
+ } else {
+ restTestSuite.addTestSection(parseContext.parseTestSection());
+ }
+ }
+
+ return restTestSuite;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/SetSectionParser.java b/src/test/java/org/elasticsearch/test/rest/parser/SetSectionParser.java
new file mode 100644
index 000000000000..1caa96d9972c
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/SetSectionParser.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.rest.section.SetSection;
+
+import java.io.IOException;
+
+/**
+ * Parser for set sections
+ */
+public class SetSectionParser implements RestTestFragmentParser {
+
+ @Override
+ public SetSection parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+
+ XContentParser parser = parseContext.parser();
+
+ String currentFieldName = null;
+ XContentParser.Token token;
+
+ SetSection setSection = new SetSection();
+
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ setSection.addSet(currentFieldName, parser.text());
+ }
+ }
+
+ parser.nextToken();
+
+ if (setSection.getStash().isEmpty()) {
+ throw new RestTestParseException("set section must set at least a value");
+ }
+
+ return setSection;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/SetupSectionParser.java b/src/test/java/org/elasticsearch/test/rest/parser/SetupSectionParser.java
new file mode 100644
index 000000000000..8e0f22930601
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/SetupSectionParser.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.rest.section.SetupSection;
+
+import java.io.IOException;
+
+/**
+ * Parser for setup sections
+ */
+public class SetupSectionParser implements RestTestFragmentParser {
+
+ @Override
+ public SetupSection parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+
+ XContentParser parser = parseContext.parser();
+
+ SetupSection setupSection = new SetupSection();
+ setupSection.setSkipSection(parseContext.parseSkipSection());
+
+ boolean skip = setupSection.getSkipSection().skipVersion(parseContext.getCurrentVersion());
+
+ while (parser.currentToken() != XContentParser.Token.END_ARRAY) {
+ if (skip) {
+ //if there was a skip section, there was a setup section as well, which means that we are sure
+ // the current token is at the beginning of a new object
+ assert parser.currentToken() == XContentParser.Token.START_OBJECT;
+ //we need to be at the beginning of an object to be able to skip children
+ parser.skipChildren();
+ //after skipChildren we are at the end of the skipped object, need to move on
+ parser.nextToken();
+ } else {
+ parseContext.advanceToFieldName();
+ if (!"do".equals(parser.currentName())) {
+ throw new RestTestParseException("section [" + parser.currentName() + "] not supported within setup section");
+ }
+
+ parser.nextToken();
+ setupSection.addDoSection(parseContext.parseDoSection());
+ parser.nextToken();
+ }
+ }
+
+ parser.nextToken();
+
+ return setupSection;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/parser/SkipSectionParser.java b/src/test/java/org/elasticsearch/test/rest/parser/SkipSectionParser.java
new file mode 100644
index 000000000000..7c8d58389e44
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/parser/SkipSectionParser.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.parser;
+
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.rest.section.SkipSection;
+
+import java.io.IOException;
+
+/**
+ * Parser for skip sections
+ */
+public class SkipSectionParser implements RestTestFragmentParser {
+
+ @Override
+ public SkipSection parse(RestTestSuiteParseContext parseContext) throws IOException, RestTestParseException {
+
+ XContentParser parser = parseContext.parser();
+
+ String currentFieldName = null;
+ XContentParser.Token token;
+ String version = null;
+ String reason = null;
+
+ while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+ if (token == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ } else if (token.isValue()) {
+ if ("version".equals(currentFieldName)) {
+ version = parser.text();
+ } else if ("reason".equals(currentFieldName)) {
+ reason = parser.text();
+ } else {
+ throw new RestTestParseException("field " + currentFieldName + " not supported within skip section");
+ }
+ }
+ }
+
+ parser.nextToken();
+
+ if (!Strings.hasLength(version)) {
+ throw new RestTestParseException("version is mandatory within skip section");
+ }
+ if (!Strings.hasLength(reason)) {
+ throw new RestTestParseException("reason is mandatory within skip section");
+ }
+
+ return new SkipSection(version, reason);
+
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/ApiCallSection.java b/src/test/java/org/elasticsearch/test/rest/section/ApiCallSection.java
new file mode 100644
index 000000000000..159d750eea4f
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/ApiCallSection.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a test fragment that contains the information needed to call an api
+ */
+public class ApiCallSection {
+
+ private final String api;
+ private final Map params = Maps.newHashMap();
+ private final List bodies = Lists.newArrayList();
+
+ private static final String EMPTY_BODY = "";
+
+ public ApiCallSection(String api) {
+ this.api = api;
+ }
+
+ public String getApi() {
+ return api;
+ }
+
+ public Map getParams() {
+ //make sure we never modify the parameters once returned
+ return ImmutableMap.copyOf(params);
+ }
+
+ public void addParam(String key, String value) {
+ String existingValue = params.get(key);
+ if (existingValue != null) {
+ value = Joiner.on(",").join(existingValue, value);
+ }
+ this.params.put(key, value);
+ }
+
+ public List getBodiesAsList() {
+ return ImmutableList.copyOf(bodies);
+ }
+
+ public String getBody() {
+ if (bodies.size() == 0) {
+ return EMPTY_BODY;
+ }
+
+ if (bodies.size() == 1) {
+ return bodies.get(0);
+ }
+
+ StringBuilder bodyBuilder = new StringBuilder();
+ for (String body : bodies) {
+ bodyBuilder.append(body).append("\n");
+ }
+ return bodyBuilder.toString();
+ }
+
+ public void addBody(String body) {
+ this.bodies.add(body);
+ }
+
+ public boolean hasBody() {
+ return bodies.size() > 0;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/Assertion.java b/src/test/java/org/elasticsearch/test/rest/section/Assertion.java
new file mode 100644
index 000000000000..d719623011ac
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/Assertion.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.test.rest.RestTestExecutionContext;
+
+import java.io.IOException;
+
+/**
+ * Base class for executable sections that hold assertions
+ */
+public abstract class Assertion implements ExecutableSection {
+
+ private final String field;
+ private final Object expectedValue;
+
+ protected Assertion(String field, Object expectedValue) {
+ this.field = field;
+ this.expectedValue = expectedValue;
+ }
+
+ public final String getField() {
+ return field;
+ }
+
+ public final Object getExpectedValue() {
+ return expectedValue;
+ }
+
+ protected final Object resolveExpectedValue(RestTestExecutionContext executionContext) {
+ if (executionContext.isStashed(expectedValue)) {
+ return executionContext.unstash(expectedValue.toString());
+ }
+ return expectedValue;
+ }
+
+ protected final Object getActualValue(RestTestExecutionContext executionContext) throws IOException {
+ return executionContext.response(field);
+ }
+
+ @Override
+ public final void execute(RestTestExecutionContext executionContext) throws IOException {
+ doAssert(getActualValue(executionContext), resolveExpectedValue(executionContext));
+ }
+
+ /**
+ * Executes the assertion comparing the actual value (parsed from the response) with the expected one
+ */
+ protected abstract void doAssert(Object actualValue, Object expectedValue);
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/DoSection.java b/src/test/java/org/elasticsearch/test/rest/section/DoSection.java
new file mode 100644
index 000000000000..c528a0b8bb29
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/DoSection.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.test.rest.RestTestExecutionContext;
+import org.elasticsearch.test.rest.client.RestException;
+import org.elasticsearch.test.rest.client.RestResponse;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+/**
+ * Represents a do section:
+ *
+ * - do:
+ * catch: missing
+ * update:
+ * index: test_1
+ * type: test
+ * id: 1
+ * body: { doc: { foo: bar } }
+ *
+ */
+public class DoSection implements ExecutableSection {
+
+ private static final ESLogger logger = Loggers.getLogger(DoSection.class);
+
+ private String catchParam;
+ private ApiCallSection apiCallSection;
+
+ public String getCatch() {
+ return catchParam;
+ }
+
+ public void setCatch(String catchParam) {
+ this.catchParam = catchParam;
+ }
+
+ public ApiCallSection getApiCallSection() {
+ return apiCallSection;
+ }
+
+ public void setApiCallSection(ApiCallSection apiCallSection) {
+ this.apiCallSection = apiCallSection;
+ }
+
+ @Override
+ public void execute(RestTestExecutionContext executionContext) throws IOException {
+
+ try {
+ executionContext.callApi(apiCallSection.getApi(), apiCallSection.getParams(), apiCallSection.getBody());
+
+ } catch(RestException e) {
+ if (!Strings.hasLength(catchParam)) {
+ fail(formatStatusCodeMessage(e.restResponse(), "2xx"));
+ }
+
+ if ("param".equals(catchParam)) {
+ //client should throw validation error before sending request
+ //lets just return without doing anything as we don't have any client to test here
+ logger.info("found [catch: param], no request sent");
+ } else if ("missing".equals(catchParam)) {
+ assertThat(formatStatusCodeMessage(e.restResponse(), "404"), e.statusCode(), equalTo(404));
+ } else if ("conflict".equals(catchParam)) {
+ assertThat(formatStatusCodeMessage(e.restResponse(), "409"), e.statusCode(), equalTo(409));
+ } else if ("forbidden".equals(catchParam)) {
+ assertThat(formatStatusCodeMessage(e.restResponse(), "403"), e.statusCode(), equalTo(403));
+ } else if ("request".equals(catchParam)) {
+ //generic error response from ES
+ assertThat(formatStatusCodeMessage(e.restResponse(), "4xx|5xx"), e.statusCode(), greaterThanOrEqualTo(400));
+ } else if (catchParam.startsWith("/") && catchParam.endsWith("/")) {
+ //the text of the error message matches regular expression
+ assertThat(formatStatusCodeMessage(e.restResponse(), "4xx|5xx"), e.statusCode(), greaterThanOrEqualTo(400));
+ Object error = executionContext.response("error");
+ assertThat("error was expected in the response", error, notNullValue());
+ //remove delimiters from regex
+ String regex = catchParam.substring(1, catchParam.length() - 1);
+ String errorMessage = error.toString();
+ Matcher matcher = Pattern.compile(regex).matcher(errorMessage);
+ assertThat("error message [" + errorMessage + "] was expected to match [" + catchParam + "] but didn't",
+ matcher.find(), equalTo(true));
+ } else {
+ throw new UnsupportedOperationException("catch value [" + catchParam + "] not supported");
+ }
+ }
+ }
+
+ private String formatStatusCodeMessage(RestResponse restResponse, String expected) {
+ return "expected [" + expected + "] status code but api [" + apiCallSection.getApi() + "] returned ["
+ + restResponse.getStatusCode() + " " + restResponse.getReasonPhrase() + "] [" + restResponse.getBody() + "]";
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/ExecutableSection.java b/src/test/java/org/elasticsearch/test/rest/section/ExecutableSection.java
new file mode 100644
index 000000000000..5044bcdba6bf
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/ExecutableSection.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.test.rest.RestTestExecutionContext;
+
+import java.io.IOException;
+
+/**
+ * Represents a test fragment that can be executed (e.g. api call, assertion)
+ */
+public interface ExecutableSection {
+
+ /**
+ * Executes the section passing in the execution context
+ */
+ void execute(RestTestExecutionContext executionContext) throws IOException;
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/GreaterThanAssertion.java b/src/test/java/org/elasticsearch/test/rest/section/GreaterThanAssertion.java
new file mode 100644
index 000000000000..95efa9013075
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/GreaterThanAssertion.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Represents a gt assert section:
+ *
+ * - gt: { fields._ttl: 0}
+ *
+ */
+public class GreaterThanAssertion extends Assertion {
+
+ private static final ESLogger logger = Loggers.getLogger(GreaterThanAssertion.class);
+
+ public GreaterThanAssertion(String field, Object expectedValue) {
+ super(field, expectedValue);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void doAssert(Object actualValue, Object expectedValue) {
+ logger.trace("assert that [{}] is greater than [{}]", actualValue, expectedValue);
+ assertThat(actualValue, instanceOf(Comparable.class));
+ assertThat(expectedValue, instanceOf(Comparable.class));
+ assertThat(errorMessage(), (Comparable)actualValue, greaterThan((Comparable) expectedValue));
+ }
+
+ private String errorMessage() {
+ return "field [" + getField() + "] is not greater than [" + getExpectedValue() + "]";
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/IsFalseAssertion.java b/src/test/java/org/elasticsearch/test/rest/section/IsFalseAssertion.java
new file mode 100644
index 000000000000..d563d38e4248
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/IsFalseAssertion.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Represents an is_false assert section:
+ *
+ * - is_false: get.fields.bar
+ *
+ */
+public class IsFalseAssertion extends Assertion {
+
+ private static final ESLogger logger = Loggers.getLogger(IsFalseAssertion.class);
+
+ public IsFalseAssertion(String field) {
+ super(field, false);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void doAssert(Object actualValue, Object expectedValue) {
+ logger.trace("assert that [{}] doesn't have a true value", actualValue);
+
+ if (actualValue == null) {
+ return;
+ }
+
+ String actualString = actualValue.toString();
+ assertThat(errorMessage(), actualString, anyOf(
+ equalTo(""),
+ equalToIgnoringCase(Boolean.FALSE.toString()),
+ equalTo("0")
+ ));
+ }
+
+ private String errorMessage() {
+ return "field [" + getField() + "] has a true value but it shouldn't";
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/IsTrueAssertion.java b/src/test/java/org/elasticsearch/test/rest/section/IsTrueAssertion.java
new file mode 100644
index 000000000000..176b5ef98ffd
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/IsTrueAssertion.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Represents an is_true assert section:
+ *
+ * - is_true: get.fields.bar
+ *
+ */
+public class IsTrueAssertion extends Assertion {
+
+ private static final ESLogger logger = Loggers.getLogger(IsTrueAssertion.class);
+
+ public IsTrueAssertion(String field) {
+ super(field, true);
+ }
+
+ @Override
+ protected void doAssert(Object actualValue, Object expectedValue) {
+ logger.trace("assert that [{}] has a true value", actualValue);
+ String errorMessage = errorMessage();
+ assertThat(errorMessage, actualValue, notNullValue());
+ String actualString = actualValue.toString();
+ assertThat(errorMessage, actualString, not(equalTo("")));
+ assertThat(errorMessage, actualString, not(equalToIgnoringCase(Boolean.FALSE.toString())));
+ assertThat(errorMessage, actualString, not(equalTo("0")));
+ }
+
+ private String errorMessage() {
+ return "field [" + getField() + "] doesn't have a true value";
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/LengthAssertion.java b/src/test/java/org/elasticsearch/test/rest/section/LengthAssertion.java
new file mode 100644
index 000000000000..8070634325d5
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/LengthAssertion.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Represents a length assert section:
+ *
+ * - length: { hits.hits: 1 }
+ *
+ */
+public class LengthAssertion extends Assertion {
+
+ private static final ESLogger logger = Loggers.getLogger(LengthAssertion.class);
+
+ public LengthAssertion(String field, Object expectedValue) {
+ super(field, expectedValue);
+ }
+
+ @Override
+ protected void doAssert(Object actualValue, Object expectedValue) {
+ logger.trace("assert that [{}] has length [{}]", actualValue, expectedValue);
+ assertThat(expectedValue, instanceOf(Number.class));
+ int length = ((Number) expectedValue).intValue();
+ if (actualValue instanceof String) {
+ assertThat(errorMessage(), ((String) actualValue).length(), equalTo(length));
+ } else if (actualValue instanceof List) {
+ assertThat(errorMessage(), ((List) actualValue).size(), equalTo(length));
+ } else if (actualValue instanceof Map) {
+ assertThat(errorMessage(), ((Map) actualValue).keySet().size(), equalTo(length));
+ } else {
+ throw new UnsupportedOperationException("value is of unsupported type [" + actualValue.getClass().getSimpleName() + "]");
+ }
+ }
+
+ private String errorMessage() {
+ return "field [" + getField() + "] doesn't have length [" + getExpectedValue() + "]";
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/LessThanAssertion.java b/src/test/java/org/elasticsearch/test/rest/section/LessThanAssertion.java
new file mode 100644
index 000000000000..b5e609031207
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/LessThanAssertion.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Represents a lt assert section:
+ *
+ * - lt: { fields._ttl: 20000}
+ *
+ */
+public class LessThanAssertion extends Assertion {
+
+ private static final ESLogger logger = Loggers.getLogger(LessThanAssertion.class);
+
+ public LessThanAssertion(String field, Object expectedValue) {
+ super(field, expectedValue);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void doAssert(Object actualValue, Object expectedValue) {
+ logger.trace("assert that [{}] is less than [{}]", actualValue, expectedValue);
+ assertThat(actualValue, instanceOf(Comparable.class));
+ assertThat(expectedValue, instanceOf(Comparable.class));
+ assertThat(errorMessage(), (Comparable)actualValue, lessThan((Comparable)expectedValue));
+ }
+
+ private String errorMessage() {
+ return "field [" + getField() + "] is not less than [" + getExpectedValue() + "]";
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/MatchAssertion.java b/src/test/java/org/elasticsearch/test/rest/section/MatchAssertion.java
new file mode 100644
index 000000000000..c4823466f3d8
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/MatchAssertion.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Represents a match assert section:
+ *
+ * - match: { get.fields._routing: "5" }
+ *
+ */
+public class MatchAssertion extends Assertion {
+
+ private static final ESLogger logger = Loggers.getLogger(MatchAssertion.class);
+
+ public MatchAssertion(String field, Object expectedValue) {
+ super(field, expectedValue);
+ }
+
+ @Override
+ protected void doAssert(Object actualValue, Object expectedValue) {
+ assertThat(errorMessage(), actualValue, notNullValue());
+ logger.trace("assert that [{}] matches [{}]", actualValue, expectedValue);
+ if (!actualValue.getClass().equals(expectedValue.getClass())) {
+ if (actualValue instanceof Number && expectedValue instanceof Number) {
+ //Double 1.0 is equals to Integer 1
+ assertThat(errorMessage(), ((Number) actualValue).doubleValue(), equalTo(((Number) expectedValue).doubleValue()));
+ }
+ } else {
+ assertThat(errorMessage(), actualValue, equalTo(expectedValue));
+ }
+ }
+
+ private String errorMessage() {
+ return "field [" + getField() + "] doesn't match the expected value";
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/RestTestSuite.java b/src/test/java/org/elasticsearch/test/rest/section/RestTestSuite.java
new file mode 100644
index 000000000000..ea19cb738955
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/RestTestSuite.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import com.google.common.collect.Lists;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Holds a REST test suite loaded from a specific yaml file.
+ * Supports a setup section and multiple test sections.
+ */
+public class RestTestSuite {
+
+ private final String api;
+ private final String name;
+
+ private SetupSection setupSection;
+
+ private List testSections = Lists.newArrayList();
+
+ public RestTestSuite(String api, String name) {
+ this.api = replaceDot(api);
+ this.name = replaceDot(name);
+ }
+
+ public String getApi() {
+ return api;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ //describes the rest test suite (e.g. index/10_with_id)
+ //useful also to reproduce failures (RestReproduceInfoPrinter)
+ public String getDescription() {
+ return api + File.separator + name;
+ }
+
+ private static String replaceDot(String value) {
+ // '.' is used as separator internally and not expected to be within suite or test names, better replace it
+ return value.replace('.', '_');
+ }
+
+ public SetupSection getSetupSection() {
+ return setupSection;
+ }
+
+ public void setSetupSection(SetupSection setupSection) {
+ this.setupSection = setupSection;
+ }
+
+ public void addTestSection(TestSection testSection) {
+ this.testSections.add(testSection);
+ }
+
+ public List getTestSections() {
+ return testSections;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/SetSection.java b/src/test/java/org/elasticsearch/test/rest/section/SetSection.java
new file mode 100644
index 000000000000..08db06006d7b
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/SetSection.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import com.google.common.collect.Maps;
+import org.elasticsearch.test.rest.RestTestExecutionContext;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * Represents a set section:
+ *
+ * - set: {_scroll_id: scroll_id}
+ *
+ */
+public class SetSection implements ExecutableSection {
+
+ private Map stash = Maps.newHashMap();
+
+ public void addSet(String responseField, String stashedField) {
+ stash.put(responseField, stashedField);
+ }
+
+ public Map getStash() {
+ return stash;
+ }
+
+ @Override
+ public void execute(RestTestExecutionContext executionContext) throws IOException {
+ for (Map.Entry entry : stash.entrySet()) {
+ Object actualValue = executionContext.response(entry.getKey());
+ executionContext.stash(entry.getValue(), actualValue);
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/SetupSection.java b/src/test/java/org/elasticsearch/test/rest/section/SetupSection.java
new file mode 100644
index 000000000000..a8edfcc51692
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/SetupSection.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Represents a setup section. Holds a skip section and multiple do sections.
+ */
+public class SetupSection {
+
+ public static final SetupSection EMPTY;
+
+ static {
+ EMPTY = new SetupSection();
+ EMPTY.setSkipSection(SkipSection.EMPTY);
+ }
+
+ private SkipSection skipSection;
+
+ private List doSections = Lists.newArrayList();
+
+ public SkipSection getSkipSection() {
+ return skipSection;
+ }
+
+ public void setSkipSection(SkipSection skipSection) {
+ this.skipSection = skipSection;
+ }
+
+ public List getDoSections() {
+ return doSections;
+ }
+
+ public void addDoSection(DoSection doSection) {
+ this.doSections.add(doSection);
+ }
+
+ public boolean isEmpty() {
+ return EMPTY.equals(this);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/SkipSection.java b/src/test/java/org/elasticsearch/test/rest/section/SkipSection.java
new file mode 100644
index 000000000000..bfd713f84ced
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/SkipSection.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import org.elasticsearch.test.rest.support.VersionUtils;
+
+/**
+ * Represents a skip section that tells whether a specific test section or suite needs to be skipped
+ * based on the elasticsearch version the tests are running against.
+ */
+public class SkipSection {
+
+ public static final SkipSection EMPTY = new SkipSection("", "");
+
+ private final String version;
+ private final String reason;
+
+ public SkipSection(String version, String reason) {
+ this.version = version;
+ this.reason = reason;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getReason() {
+ return reason;
+ }
+
+ public boolean skipVersion(String currentVersion) {
+ if (isEmpty()) {
+ return false;
+ }
+ return VersionUtils.skipCurrentVersion(version, currentVersion);
+ }
+
+ public boolean isEmpty() {
+ return EMPTY.equals(this);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/section/TestSection.java b/src/test/java/org/elasticsearch/test/rest/section/TestSection.java
new file mode 100644
index 000000000000..0b32ba570502
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/section/TestSection.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.section;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Represents a test section, which is composed of a skip section and multiple executable sections.
+ */
+public class TestSection {
+ private final String name;
+ private SkipSection skipSection;
+ private final List executableSections;
+
+ public TestSection(String name) {
+ this.name = name;
+ this.executableSections = Lists.newArrayList();
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public SkipSection getSkipSection() {
+ return skipSection;
+ }
+
+ public void setSkipSection(SkipSection skipSection) {
+ this.skipSection = skipSection;
+ }
+
+ public List getExecutableSections() {
+ return executableSections;
+ }
+
+ public void addExecutableSection(ExecutableSection executableSection) {
+ this.executableSections.add(executableSection);
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/spec/RestApi.java b/src/test/java/org/elasticsearch/test/rest/spec/RestApi.java
new file mode 100644
index 000000000000..478484bf6282
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/spec/RestApi.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.spec;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.lucene.util.PriorityQueue;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Represents an elasticsearch REST endpoint (api)
+ */
+public class RestApi {
+
+ private static final String ALL = "_all";
+
+ private final String name;
+ private List methods = Lists.newArrayList();
+ private List paths = Lists.newArrayList();
+ private List pathParts = Lists.newArrayList();
+
+ RestApi(String name) {
+ this.name = name;
+ }
+
+ RestApi(RestApi restApi, String name, String... methods) {
+ this.name = name;
+ this.methods = Arrays.asList(methods);
+ paths.addAll(restApi.getPaths());
+ pathParts.addAll(restApi.getPathParts());
+ }
+
+ RestApi(RestApi restApi, List paths) {
+ this.name = restApi.getName();
+ this.methods = restApi.getMethods();
+ this.paths.addAll(paths);
+ pathParts.addAll(restApi.getPathParts());
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public List getMethods() {
+ return methods;
+ }
+
+ /**
+ * Returns the supported http methods given the rest parameters provided
+ */
+ public List getSupportedMethods(Set restParams) {
+ //we try to avoid hardcoded mappings but the index api is the exception
+ if ("index".equals(name) || "create".equals(name)) {
+ List indexMethods = Lists.newArrayList();
+ for (String method : methods) {
+ if (restParams.contains("id")) {
+ //PUT when the id is provided
+ if (HttpPut.METHOD_NAME.equals(method)) {
+ indexMethods.add(method);
+ }
+ } else {
+ //POST without id
+ if (HttpPost.METHOD_NAME.equals(method)) {
+ indexMethods.add(method);
+ }
+ }
+ }
+ return indexMethods;
+ }
+
+ return methods;
+ }
+
+ void addMethod(String method) {
+ this.methods.add(method);
+ }
+
+ public List getPaths() {
+ return paths;
+ }
+
+ void addPath(String path) {
+ this.paths.add(path);
+ }
+
+ public List getPathParts() {
+ return pathParts;
+ }
+
+ void addPathPart(String pathPart) {
+ this.pathParts.add(pathPart);
+ }
+
+ /**
+ * Finds the best matching rest path given the current parameters and replaces
+ * placeholders with their corresponding values received as arguments
+ */
+ public String getFinalPath(Map pathParams) {
+ RestPath matchingRestPath = findMatchingRestPath(pathParams.keySet());
+ String path = matchingRestPath.path;
+ for (Map.Entry paramEntry : matchingRestPath.params.entrySet()) {
+ //replace path placeholders with actual values
+ String value = pathParams.get(paramEntry.getValue());
+ if (value == null) {
+ //there might be additional placeholder to replace, not available as input params
+ //it can only be {index} or {type} to be replaced with _all
+ if (paramEntry.getValue().equals("index") || paramEntry.getValue().equals("type")) {
+ value = ALL;
+ } else {
+ throw new IllegalArgumentException("path [" + path + "] contains placeholders that weren't replaced with proper values");
+ }
+ }
+ path = path.replace(paramEntry.getKey(), value);
+ }
+ return path;
+ }
+
+ /**
+ * Finds the best matching rest path out of the available ones with the current api (based on REST spec).
+ *
+ * The best path is the one that has exactly the same number of placeholders to replace
+ * (e.g. /{index}/{type}/{id} when the params are exactly index, type and id).
+ * Otherwise there might be additional placeholders, thus we use the path with the least additional placeholders.
+ * (e.g. get with only index and id as parameters, the closest (and only) path contains {type} too, which becomes _all)
+ */
+ private RestPath findMatchingRestPath(Set restParams) {
+
+ RestPath[] restPaths = buildRestPaths();
+
+ //We need to find the path that has exactly the placeholders corresponding to our params
+ //If there's no exact match we fallback to the closest one (with as less additional placeholders as possible)
+ //The fallback is needed for:
+ //1) get, get_source and exists with only index and id => /{index}/_all/{id} (
+ //2) search with only type => /_all/{type/_search
+ PriorityQueue restPathQueue = new PriorityQueue(1) {
+ @Override
+ protected boolean lessThan(RestPath a, RestPath b) {
+ return a.params.size() >= b.params.size();
+ }
+ };
+ for (RestPath restPath : restPaths) {
+ if (restPath.params.values().containsAll(restParams)) {
+ restPathQueue.insertWithOverflow(restPath);
+ }
+ }
+
+ if (restPathQueue.size() > 0) {
+ return restPathQueue.top();
+ }
+
+ throw new IllegalArgumentException("unable to find best path for api [" + name + "] and params " + restParams);
+ }
+
+ private RestPath[] buildRestPaths() {
+ RestPath[] restPaths = new RestPath[paths.size()];
+ for (int i = 0; i < restPaths.length; i++) {
+ restPaths[i] = new RestPath(paths.get(i));
+ }
+ return restPaths;
+ }
+
+ private static class RestPath {
+ private static final Pattern PLACEHOLDERS_PATTERN = Pattern.compile("(\\{(.*?)})");
+
+ final String path;
+ //contains param to replace (e.g. {index}) and param key to use for lookup in the current values map (e.g. index)
+ final Map params;
+
+ RestPath(String path) {
+ this.path = path;
+ this.params = extractParams(path);
+ }
+
+ private static Map extractParams(String input) {
+ Map params = Maps.newHashMap();
+ Matcher matcher = PLACEHOLDERS_PATTERN.matcher(input);
+ while (matcher.find()) {
+ //key is e.g. {index}
+ String key = input.substring(matcher.start(), matcher.end());
+ if (matcher.groupCount() != 2) {
+ throw new IllegalArgumentException("no lookup key found for param [" + key + "]");
+ }
+ //to be replaced with current value found with key e.g. index
+ String value = matcher.group(2);
+ params.put(key, value);
+ }
+ return params;
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/spec/RestApiParser.java b/src/test/java/org/elasticsearch/test/rest/spec/RestApiParser.java
new file mode 100644
index 000000000000..114615d7664b
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/spec/RestApiParser.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.spec;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+
+/**
+ * Parser for a REST api spec (single json file)
+ */
+public class RestApiParser {
+
+ public RestApi parse(XContentParser parser) throws IOException {
+
+ try {
+ while ( parser.nextToken() != XContentParser.Token.FIELD_NAME ) {
+ //move to first field name
+ }
+
+ RestApi restApi = new RestApi(parser.currentName());
+
+ int level = -1;
+ while (parser.nextToken() != XContentParser.Token.END_OBJECT || level >= 0) {
+
+ if (parser.currentToken() == XContentParser.Token.FIELD_NAME) {
+ if ("methods".equals(parser.currentName())) {
+ parser.nextToken();
+ while (parser.nextToken() == XContentParser.Token.VALUE_STRING) {
+ restApi.addMethod(parser.text());
+ }
+ }
+
+ if ("url".equals(parser.currentName())) {
+ String currentFieldName = "url";
+ int innerLevel = -1;
+ while(parser.nextToken() != XContentParser.Token.END_OBJECT || innerLevel >= 0) {
+ if (parser.currentToken() == XContentParser.Token.FIELD_NAME) {
+ currentFieldName = parser.currentName();
+ }
+
+ if (parser.currentToken() == XContentParser.Token.START_ARRAY && "paths".equals(currentFieldName)) {
+ while (parser.nextToken() == XContentParser.Token.VALUE_STRING) {
+ restApi.addPath(parser.text());
+ }
+ }
+
+ if (parser.currentToken() == XContentParser.Token.START_OBJECT && "parts".equals(currentFieldName)) {
+ while (parser.nextToken() == XContentParser.Token.FIELD_NAME) {
+ restApi.addPathPart(parser.currentName());
+ parser.nextToken();
+ assert parser.currentToken() == XContentParser.Token.START_OBJECT;
+ parser.skipChildren();
+ }
+ }
+
+ if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
+ innerLevel++;
+ }
+ if (parser.currentToken() == XContentParser.Token.END_OBJECT) {
+ innerLevel--;
+ }
+ }
+ }
+ }
+
+ if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
+ level++;
+ }
+ if (parser.currentToken() == XContentParser.Token.END_OBJECT) {
+ level--;
+ }
+
+ }
+
+ parser.nextToken();
+ assert parser.currentToken() == XContentParser.Token.END_OBJECT;
+ parser.nextToken();
+
+ return restApi;
+
+ } finally {
+ parser.close();
+ }
+ }
+
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/spec/RestSpec.java b/src/test/java/org/elasticsearch/test/rest/spec/RestSpec.java
new file mode 100644
index 000000000000..f697b13e0d1b
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/spec/RestSpec.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.spec;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.elasticsearch.test.rest.support.FileUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Holds the elasticsearch REST spec
+ */
+public class RestSpec {
+ Map restApiMap = Maps.newHashMap();
+
+ private RestSpec() {
+ }
+
+ void addApi(RestApi restApi) {
+ if ("info".equals(restApi.getName())) {
+ //info and ping should really be two different api in the rest spec
+ //info (GET|HEAD /) needs to be manually split into 1) info: GET / 2) ping: HEAD /
+ restApiMap.put("info", new RestApi(restApi, "info", "GET"));
+ restApiMap.put("ping", new RestApi(restApi, "ping", "HEAD"));
+ } else if ("get".equals(restApi.getName())) {
+ //get_source endpoint shouldn't be present in the rest spec for the get api
+ //as get_source is already a separate api
+ List paths = Lists.newArrayList();
+ for (String path : restApi.getPaths()) {
+ if (!path.endsWith("/_source")) {
+ paths.add(path);
+ }
+ }
+ restApiMap.put(restApi.getName(), new RestApi(restApi, paths));
+ } else {
+ restApiMap.put(restApi.getName(), restApi);
+ }
+ }
+
+ public RestApi getApi(String api) {
+ return restApiMap.get(api);
+ }
+
+ /**
+ * Parses the complete set of REST spec available under the provided directories
+ */
+ public static RestSpec parseFrom(String optionalPathPrefix, String... paths) throws IOException {
+ RestSpec restSpec = new RestSpec();
+ for (String path : paths) {
+ for (File jsonFile : FileUtils.findJsonSpec(optionalPathPrefix, path)) {
+ XContentParser parser = JsonXContent.jsonXContent.createParser(new FileInputStream(jsonFile));
+ RestApi restApi = new RestApiParser().parse(parser);
+ restSpec.addApi(restApi);
+ }
+ }
+ return restSpec;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/support/FileUtils.java b/src/test/java/org/elasticsearch/test/rest/support/FileUtils.java
new file mode 100644
index 000000000000..3b93f4affca7
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/support/FileUtils.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.support;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.elasticsearch.common.Strings;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileNotFoundException;
+import java.net.URL;
+import java.util.Map;
+import java.util.Set;
+
+public final class FileUtils {
+
+ private static final String YAML_SUFFIX = ".yaml";
+ private static final String JSON_SUFFIX = ".json";
+
+ private FileUtils() {
+
+ }
+
+ /**
+ * Returns the json files found within the directory provided as argument.
+ * Files are looked up in the classpath first, then outside of it if not found.
+ */
+ public static Set findJsonSpec(String optionalPathPrefix, String path) throws FileNotFoundException {
+ File dir = resolveFile(optionalPathPrefix, path, null);
+
+ if (!dir.isDirectory()) {
+ throw new FileNotFoundException("file [" + path + "] is not a directory");
+ }
+
+ File[] jsonFiles = dir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.getName().endsWith(JSON_SUFFIX);
+ }
+ });
+
+ if (jsonFiles == null || jsonFiles.length == 0) {
+ throw new FileNotFoundException("no json files found within [" + path + "]");
+ }
+
+ return Sets.newHashSet(jsonFiles);
+ }
+
+ /**
+ * Returns the yaml files found within the paths provided.
+ * Each input path can either be a single file (the .yaml suffix is optional) or a directory.
+ * Each path is looked up in the classpath first, then outside of it if not found yet.
+ */
+ public static Map> findYamlSuites(final String optionalPathPrefix, final String... paths) throws FileNotFoundException {
+ Map> yamlSuites = Maps.newHashMap();
+ for (String path : paths) {
+ collectFiles(resolveFile(optionalPathPrefix, path, YAML_SUFFIX), YAML_SUFFIX, yamlSuites);
+ }
+ return yamlSuites;
+ }
+
+ private static File resolveFile(String optionalPathPrefix, String path, String optionalFileSuffix) throws FileNotFoundException {
+ //try within classpath with and without file suffix (as it could be a single test suite)
+ URL resource = findResource(path, optionalFileSuffix);
+ if (resource == null) {
+ //try within classpath with optional prefix: /rest-spec/test (or /rest-test/api) is optional
+ String newPath = optionalPathPrefix + File.separator + path;
+ resource = findResource(newPath, optionalFileSuffix);
+ if (resource == null) {
+ //if it wasn't on classpath we look outside ouf the classpath
+ File file = findFile(path, optionalFileSuffix);
+ if (!file.exists()) {
+ throw new FileNotFoundException("file [" + path + "] doesn't exist");
+ }
+ return file;
+ }
+ }
+ return new File(resource.getFile());
+ }
+
+ private static URL findResource(String path, String optionalFileSuffix) {
+ URL resource = FileUtils.class.getResource(path);
+ if (resource == null) {
+ //if not found we append the file suffix to the path (as it is optional)
+ if (Strings.hasLength(optionalFileSuffix) && !path.endsWith(optionalFileSuffix)) {
+ resource = FileUtils.class.getResource(path + optionalFileSuffix);
+ }
+ }
+ return resource;
+ }
+
+ private static File findFile(String path, String optionalFileSuffix) {
+ File file = new File(path);
+ if (!file.exists()) {
+ file = new File(path + optionalFileSuffix);
+ }
+ return file;
+ }
+
+ private static void collectFiles(final File file, final String fileSuffix, final Map> files) {
+ if (file.isFile()) {
+ // '.' is uses as separator internally and not expected to be within suite or test names, better replace it
+ String groupName = file.getParentFile().getName().replace('.', '_');
+ Set filesSet = files.get(groupName);
+ if (filesSet == null) {
+ filesSet = Sets.newHashSet();
+ files.put(groupName, filesSet);
+ }
+ filesSet.add(file);
+ } else if (file.isDirectory()) {
+ walkDir(file, fileSuffix, files);
+ }
+ }
+
+ private static void walkDir(final File dir, final String fileSuffix, final Map> files) {
+ File[] children = dir.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isDirectory() || pathname.getName().endsWith(fileSuffix);
+ }
+ });
+
+ for (File file : children) {
+ collectFiles(file, fileSuffix, files);
+ }
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/support/VersionUtils.java b/src/test/java/org/elasticsearch/test/rest/support/VersionUtils.java
new file mode 100644
index 000000000000..f3290f8ab75a
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/support/VersionUtils.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.support;
+
+public final class VersionUtils {
+
+ private VersionUtils() {
+
+ }
+
+ /**
+ * Parses an elasticsearch version string into an int array with an element per part
+ * e.g. 0.90.7 => [0,90,7]
+ */
+ public static int[] parseVersionNumber(String version) {
+ String[] split = version.split("\\.");
+ //we only take the first 3 parts if there are more, but less is ok too (e.g. 999)
+ int length = Math.min(3, split.length);
+ int[] versionNumber = new int[length];
+ for (int i = 0; i < length; i++) {
+ try {
+ versionNumber[i] = Integer.valueOf(split[i]);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("version is not a number", e);
+ }
+
+ }
+ return versionNumber;
+ }
+
+ /**
+ * Compares the skip version read from a test fragment with the elasticsearch version
+ * the tests are running against and determines whether the test fragment needs to be skipped
+ */
+ public static boolean skipCurrentVersion(String skipVersion, String currentVersion) {
+ int[] currentVersionNumber = parseVersionNumber(currentVersion);
+
+ String[] skipVersions = skipVersion.split("-");
+ if (skipVersions.length > 2) {
+ throw new IllegalArgumentException("too many skip versions found");
+ }
+
+ String skipVersionLowerBound = skipVersions[0].trim();
+ String skipVersionUpperBound = skipVersions[1].trim();
+
+ int[] skipVersionLowerBoundNumber = parseVersionNumber(skipVersionLowerBound);
+ int[] skipVersionUpperBoundNumber = parseVersionNumber(skipVersionUpperBound);
+
+ int length = Math.min(skipVersionLowerBoundNumber.length, currentVersionNumber.length);
+ for (int i = 0; i < length; i++) {
+ if (currentVersionNumber[i] < skipVersionLowerBoundNumber[i]) {
+ return false;
+ }
+ if (currentVersionNumber[i] > skipVersionLowerBoundNumber[i]) {
+ break;
+ }
+ }
+
+ length = Math.min(skipVersionUpperBoundNumber.length, currentVersionNumber.length);
+ for (int i = 0; i < length; i++) {
+ if (currentVersionNumber[i] > skipVersionUpperBoundNumber[i]) {
+ return false;
+ }
+ if (currentVersionNumber[i] < skipVersionUpperBoundNumber[i]) {
+ break;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/AbstractParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/AbstractParserTests.java
new file mode 100644
index 000000000000..78e1fb9a9825
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/AbstractParserTests.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.junit.After;
+import org.junit.Ignore;
+
+import static org.hamcrest.Matchers.nullValue;
+
+@Ignore
+public class AbstractParserTests extends ElasticsearchTestCase {
+
+ protected XContentParser parser;
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ //this is the way to make sure that we consumed the whole yaml
+ assertThat(parser.currentToken(), nullValue());
+ parser.close();
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/AssertionParsersTests.java b/src/test/java/org/elasticsearch/test/rest/test/AssertionParsersTests.java
new file mode 100644
index 000000000000..df9d9ef15f4b
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/AssertionParsersTests.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.rest.parser.*;
+import org.elasticsearch.test.rest.section.*;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+
+public class AssertionParsersTests extends AbstractParserTests {
+
+ @Test
+ public void testParseIsTrue() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "get.fields._timestamp"
+ );
+
+ IsTrueParser isTrueParser = new IsTrueParser();
+ IsTrueAssertion trueAssertion = isTrueParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(trueAssertion, notNullValue());
+ assertThat(trueAssertion.getField(), equalTo("get.fields._timestamp"));
+ }
+
+ @Test
+ public void testParseIsFalse() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "docs.1._source"
+ );
+
+ IsFalseParser isFalseParser = new IsFalseParser();
+ IsFalseAssertion falseAssertion = isFalseParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(falseAssertion, notNullValue());
+ assertThat(falseAssertion.getField(), equalTo("docs.1._source"));
+ }
+
+ @Test
+ public void testParseGreaterThan() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ field: 3}"
+ );
+
+ GreaterThanParser greaterThanParser = new GreaterThanParser();
+ GreaterThanAssertion greaterThanAssertion = greaterThanParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ assertThat(greaterThanAssertion, notNullValue());
+ assertThat(greaterThanAssertion.getField(), equalTo("field"));
+ assertThat(greaterThanAssertion.getExpectedValue(), instanceOf(Integer.class));
+ assertThat((Integer) greaterThanAssertion.getExpectedValue(), equalTo(3));
+ }
+
+ @Test
+ public void testParseLessThan() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ field: 3}"
+ );
+
+ LessThanParser lessThanParser = new LessThanParser();
+ LessThanAssertion lessThanAssertion = lessThanParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ assertThat(lessThanAssertion, notNullValue());
+ assertThat(lessThanAssertion.getField(), equalTo("field"));
+ assertThat(lessThanAssertion.getExpectedValue(), instanceOf(Integer.class));
+ assertThat((Integer) lessThanAssertion.getExpectedValue(), equalTo(3));
+ }
+
+ @Test
+ public void testParseLength() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ _id: 22}"
+ );
+
+ LengthParser lengthParser = new LengthParser();
+ LengthAssertion lengthAssertion = lengthParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ assertThat(lengthAssertion, notNullValue());
+ assertThat(lengthAssertion.getField(), equalTo("_id"));
+ assertThat(lengthAssertion.getExpectedValue(), instanceOf(Integer.class));
+ assertThat((Integer) lengthAssertion.getExpectedValue(), equalTo(22));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testParseMatchSimpleIntegerValue() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ field: 10 }"
+ );
+
+ MatchParser matchParser = new MatchParser();
+ MatchAssertion matchAssertion = matchParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(matchAssertion, notNullValue());
+ assertThat(matchAssertion.getField(), equalTo("field"));
+ assertThat(matchAssertion.getExpectedValue(), instanceOf(Integer.class));
+ assertThat((Integer) matchAssertion.getExpectedValue(), equalTo(10));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testParseMatchSimpleStringValue() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ foo: bar }"
+ );
+
+ MatchParser matchParser = new MatchParser();
+ MatchAssertion matchAssertion = matchParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(matchAssertion, notNullValue());
+ assertThat(matchAssertion.getField(), equalTo("foo"));
+ assertThat(matchAssertion.getExpectedValue(), instanceOf(String.class));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("bar"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testParseMatchArray() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{'matches': ['test_percolator_1', 'test_percolator_2']}"
+ );
+
+ MatchParser matchParser = new MatchParser();
+ MatchAssertion matchAssertion = matchParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(matchAssertion, notNullValue());
+ assertThat(matchAssertion.getField(), equalTo("matches"));
+ assertThat(matchAssertion.getExpectedValue(), instanceOf(List.class));
+ List strings = (List) matchAssertion.getExpectedValue();
+ assertThat(strings.size(), equalTo(2));
+ assertThat(strings.get(0).toString(), equalTo("test_percolator_1"));
+ assertThat(strings.get(1).toString(), equalTo("test_percolator_2"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testParseMatchSourceValues() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ _source: { responses.0.hits.total: 3, foo: bar }}"
+ );
+
+ MatchParser matchParser = new MatchParser();
+ MatchAssertion matchAssertion = matchParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(matchAssertion, notNullValue());
+ assertThat(matchAssertion.getField(), equalTo("_source"));
+ assertThat(matchAssertion.getExpectedValue(), instanceOf(Map.class));
+ Map expectedValue = (Map) matchAssertion.getExpectedValue();
+ assertThat(expectedValue.size(), equalTo(2));
+ Object o = expectedValue.get("responses.0.hits.total");
+ assertThat(o, instanceOf(Integer.class));
+ assertThat((Integer)o, equalTo(3));
+ o = expectedValue.get("foo");
+ assertThat(o, instanceOf(String.class));
+ assertThat(o.toString(), equalTo("bar"));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/DoSectionParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/DoSectionParserTests.java
new file mode 100644
index 000000000000..7b6958bccb26
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/DoSectionParserTests.java
@@ -0,0 +1,420 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.rest.parser.DoSectionParser;
+import org.elasticsearch.test.rest.parser.RestTestParseException;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParseContext;
+import org.elasticsearch.test.rest.section.ApiCallSection;
+import org.elasticsearch.test.rest.section.DoSection;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+public class DoSectionParserTests extends AbstractParserTests {
+
+ @Test
+ public void testParseDoSectionNoBody() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "get:\n" +
+ " index: test_index\n" +
+ " type: test_type\n" +
+ " id: 1"
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("get"));
+ assertThat(apiCallSection.getParams().size(), equalTo(3));
+ assertThat(apiCallSection.getParams().get("index"), equalTo("test_index"));
+ assertThat(apiCallSection.getParams().get("type"), equalTo("test_type"));
+ assertThat(apiCallSection.getParams().get("id"), equalTo("1"));
+ assertThat(apiCallSection.hasBody(), equalTo(false));
+ }
+
+ @Test
+ public void testParseDoSectionNoParamsNoBody() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "cluster.node_info: {}"
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("cluster.node_info"));
+ assertThat(apiCallSection.getParams().size(), equalTo(0));
+ assertThat(apiCallSection.hasBody(), equalTo(false));
+ }
+
+ @Test
+ public void testParseDoSectionWithJsonBody() throws Exception {
+ String body = "{ \"include\": { \"field1\": \"v1\", \"field2\": \"v2\" }, \"count\": 1 }";
+ parser = YamlXContent.yamlXContent.createParser(
+ "index:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body: " + body
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("index"));
+ assertThat(apiCallSection.getParams().size(), equalTo(3));
+ assertThat(apiCallSection.getParams().get("index"), equalTo("test_1"));
+ assertThat(apiCallSection.getParams().get("type"), equalTo("test"));
+ assertThat(apiCallSection.getParams().get("id"), equalTo("1"));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+
+ assertJsonEquals(apiCallSection.getBodiesAsList().get(0), body);
+ assertJsonEquals(apiCallSection.getBody(), body);
+ }
+
+ @Test
+ public void testParseDoSectionWithJsonMultipleBodiesAsLongString() throws Exception {
+ String bodies[] = new String[]{
+ "{ \"index\": { \"_index\":\"test_index\", \"_type\":\"test_type\", \"_id\":\"test_id\" } }\n",
+ "{ \"f1\":\"v1\", \"f2\":42 }\n",
+ "{ \"index\": { \"_index\":\"test_index2\", \"_type\":\"test_type2\", \"_id\":\"test_id2\" } }\n",
+ "{ \"f1\":\"v2\", \"f2\":47 }\n"
+ };
+ parser = YamlXContent.yamlXContent.createParser(
+ "bulk:\n" +
+ " refresh: true\n" +
+ " body: |\n" +
+ " " + bodies[0] +
+ " " + bodies[1] +
+ " " + bodies[2] +
+ " " + bodies[3]
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("bulk"));
+ assertThat(apiCallSection.getParams().size(), equalTo(1));
+ assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
+ StringBuilder bodyBuilder = new StringBuilder();
+ for (String body : bodies) {
+ bodyBuilder.append(body);
+ }
+ assertThat(apiCallSection.getBody(), equalTo(bodyBuilder.toString()));
+ }
+
+ @Test
+ public void testParseDoSectionWithJsonMultipleBodiesRepeatedProperty() throws Exception {
+ String[] bodies = new String[] {
+ "{ \"index\": { \"_index\":\"test_index\", \"_type\":\"test_type\", \"_id\":\"test_id\" } }",
+ "{ \"f1\":\"v1\", \"f2\":42 }",
+ };
+ parser = YamlXContent.yamlXContent.createParser(
+ "bulk:\n" +
+ " refresh: true\n" +
+ " body: \n" +
+ " " + bodies[0] + "\n" +
+ " body: \n" +
+ " " + bodies[1]
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("bulk"));
+ assertThat(apiCallSection.getParams().size(), equalTo(1));
+ assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(bodies.length));
+ for (int i = 0; i < bodies.length; i++) {
+ assertJsonEquals(apiCallSection.getBodiesAsList().get(i), bodies[i]);
+ }
+
+ String[] returnedBodies = apiCallSection.getBody().split("\n");
+ assertThat(returnedBodies.length, equalTo(bodies.length));
+ for (int i = 0; i < bodies.length; i++) {
+ assertJsonEquals(returnedBodies[i], bodies[i]);
+ }
+ }
+
+ @Test
+ public void testParseDoSectionWithYamlBody() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "search:\n" +
+ " body:\n" +
+ " _source: [ include.field1, include.field2 ]\n" +
+ " query: { match_all: {} }"
+ );
+ String body = "{ \"_source\": [ \"include.field1\", \"include.field2\" ], \"query\": { \"match_all\": {} }}";
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("search"));
+ assertThat(apiCallSection.getParams().size(), equalTo(0));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
+ assertJsonEquals(apiCallSection.getBodiesAsList().get(0), body);
+ assertJsonEquals(apiCallSection.getBody(), body);
+ }
+
+ @Test
+ public void testParseDoSectionWithYamlMultipleBodies() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "bulk:\n" +
+ " refresh: true\n" +
+ " body:\n" +
+ " - index:\n" +
+ " _index: test_index\n" +
+ " _type: test_type\n" +
+ " _id: test_id\n" +
+ " - f1: v1\n" +
+ " f2: 42\n" +
+ " - index:\n" +
+ " _index: test_index2\n" +
+ " _type: test_type2\n" +
+ " _id: test_id2\n" +
+ " - f1: v2\n" +
+ " f2: 47"
+ );
+ String[] bodies = new String[4];
+ bodies[0] = "{\"index\": {\"_index\": \"test_index\", \"_type\": \"test_type\", \"_id\": \"test_id\"}}";
+ bodies[1] = "{ \"f1\":\"v1\", \"f2\": 42 }";
+ bodies[2] = "{\"index\": {\"_index\": \"test_index2\", \"_type\": \"test_type2\", \"_id\": \"test_id2\"}}";
+ bodies[3] = "{ \"f1\":\"v2\", \"f2\": 47 }";
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("bulk"));
+ assertThat(apiCallSection.getParams().size(), equalTo(1));
+ assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(bodies.length));
+
+ for (int i = 0; i < bodies.length; i++) {
+ assertJsonEquals(apiCallSection.getBodiesAsList().get(i), bodies[i]);
+ }
+
+ String[] returnedBodies = apiCallSection.getBody().split("\n");
+ assertThat(returnedBodies.length, equalTo(bodies.length));
+ for (int i = 0; i < bodies.length; i++) {
+ assertJsonEquals(returnedBodies[i], bodies[i]);
+ }
+ }
+
+ @Test
+ public void testParseDoSectionWithYamlMultipleBodiesRepeatedProperty() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "bulk:\n" +
+ " refresh: true\n" +
+ " body:\n" +
+ " index:\n" +
+ " _index: test_index\n" +
+ " _type: test_type\n" +
+ " _id: test_id\n" +
+ " body:\n" +
+ " f1: v1\n" +
+ " f2: 42\n"
+ );
+ String[] bodies = new String[2];
+ bodies[0] = "{\"index\": {\"_index\": \"test_index\", \"_type\": \"test_type\", \"_id\": \"test_id\"}}";
+ bodies[1] = "{ \"f1\":\"v1\", \"f2\": 42 }";
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("bulk"));
+ assertThat(apiCallSection.getParams().size(), equalTo(1));
+ assertThat(apiCallSection.getParams().get("refresh"), equalTo("true"));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(bodies.length));
+
+ for (int i = 0; i < bodies.length; i++) {
+ assertJsonEquals(apiCallSection.getBodiesAsList().get(i), bodies[i]);
+ }
+
+ String[] returnedBodies = apiCallSection.getBody().split("\n");
+ assertThat(returnedBodies.length, equalTo(bodies.length));
+ for (int i = 0; i < bodies.length; i++) {
+ assertJsonEquals(returnedBodies[i], bodies[i]);
+ }
+ }
+
+ @Test
+ public void testParseDoSectionWithYamlBodyMultiGet() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "mget:\n" +
+ " body:\n" +
+ " docs:\n" +
+ " - { _index: test_2, _type: test, _id: 1}\n" +
+ " - { _index: test_1, _type: none, _id: 1}"
+ );
+ String body = "{ \"docs\": [ " +
+ "{\"_index\": \"test_2\", \"_type\":\"test\", \"_id\":1}, " +
+ "{\"_index\": \"test_1\", \"_type\":\"none\", \"_id\":1} " +
+ "]}";
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("mget"));
+ assertThat(apiCallSection.getParams().size(), equalTo(0));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
+ assertJsonEquals(apiCallSection.getBodiesAsList().get(0), body);
+ assertJsonEquals(apiCallSection.getBody(), body);
+ }
+
+ @Test
+ public void testParseDoSectionWithBodyStringified() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "index:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body: \"{ _source: true, query: { match_all: {} } }\""
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection, notNullValue());
+ assertThat(apiCallSection.getApi(), equalTo("index"));
+ assertThat(apiCallSection.getParams().size(), equalTo(3));
+ assertThat(apiCallSection.getParams().get("index"), equalTo("test_1"));
+ assertThat(apiCallSection.getParams().get("type"), equalTo("test"));
+ assertThat(apiCallSection.getParams().get("id"), equalTo("1"));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(1));
+ //stringified body is taken as is
+ assertThat(apiCallSection.getBodiesAsList().get(0), equalTo("{ _source: true, query: { match_all: {} } }"));
+ }
+
+ @Test
+ public void testParseDoSectionWithBodiesStringifiedAndNot() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "index:\n" +
+ " body:\n" +
+ " - \"{ _source: true, query: { match_all: {} } }\"\n" +
+ " - { size: 100, query: { match_all: {} } }"
+ );
+
+ String body = "{ \"size\": 100, \"query\": { \"match_all\": {} } }";
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ ApiCallSection apiCallSection = doSection.getApiCallSection();
+
+ assertThat(apiCallSection.getApi(), equalTo("index"));
+ assertThat(apiCallSection.getParams().size(), equalTo(0));
+ assertThat(apiCallSection.hasBody(), equalTo(true));
+ assertThat(apiCallSection.getBodiesAsList().size(), equalTo(2));
+ //stringified body is taken as is
+ assertThat(apiCallSection.getBodiesAsList().get(0), equalTo("{ _source: true, query: { match_all: {} } }"));
+ assertJsonEquals(apiCallSection.getBodiesAsList().get(1), body);
+ }
+
+ @Test
+ public void testParseDoSectionWithCatch() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "catch: missing\n" +
+ "indices.get_warmer:\n" +
+ " index: test_index\n" +
+ " name: test_warmer"
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(doSection.getCatch(), equalTo("missing"));
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_warmer"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(2));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+ }
+
+ @Test (expected = RestTestParseException.class)
+ public void testParseDoSectionWithoutClientCallSection() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "catch: missing\n"
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ }
+
+ @Test
+ public void testParseDoSectionMultivaluedField() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "indices.get_field_mapping:\n" +
+ " index: test_index\n" +
+ " type: test_type\n" +
+ " field: [ text , text1 ]"
+ );
+
+ DoSectionParser doSectionParser = new DoSectionParser();
+ DoSection doSection = doSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_field_mapping"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().getParams().get("index"), equalTo("test_index"));
+ assertThat(doSection.getApiCallSection().getParams().get("type"), equalTo("test_type"));
+ assertThat(doSection.getApiCallSection().getParams().get("field"), equalTo("text,text1"));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+ assertThat(doSection.getApiCallSection().getBodiesAsList().size(), equalTo(0));
+ }
+
+ private static void assertJsonEquals(String actual, String expected) throws IOException {
+ Map actualMap = JsonXContent.jsonXContent.createParser(actual).mapOrderedAndClose();
+ Map expectedMap = JsonXContent.jsonXContent.createParser(expected).mapOrderedAndClose();
+ MatcherAssert.assertThat(actualMap, equalTo(expectedMap));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/FileUtilsTests.java b/src/test/java/org/elasticsearch/test/rest/test/FileUtilsTests.java
new file mode 100644
index 000000000000..1fdbbb84ba91
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/FileUtilsTests.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.elasticsearch.test.rest.support.FileUtils;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.Matchers.greaterThan;
+
+public class FileUtilsTests extends ElasticsearchTestCase {
+
+ @Test
+ public void testLoadSingleYamlSuite() throws Exception {
+ Map> yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "/rest-spec/test/get/10_basic");
+ assertSingleFile(yamlSuites, "get", "10_basic.yaml");
+
+ //the path prefix is optional
+ yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "get/10_basic.yaml");
+ assertSingleFile(yamlSuites, "get", "10_basic.yaml");
+
+ //extension .yaml is optional
+ yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "get/10_basic");
+ assertSingleFile(yamlSuites, "get", "10_basic.yaml");
+ }
+
+ @Test
+ public void testLoadMultipleYamlSuites() throws Exception {
+ //single directory
+ Map> yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "get");
+ assertThat(yamlSuites, notNullValue());
+ assertThat(yamlSuites.size(), equalTo(1));
+ assertThat(yamlSuites.containsKey("get"), equalTo(true));
+ assertThat(yamlSuites.get("get").size(), greaterThan(1));
+
+ //multiple directories
+ yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "get", "index");
+ assertThat(yamlSuites, notNullValue());
+ assertThat(yamlSuites.size(), equalTo(2));
+ assertThat(yamlSuites.containsKey("get"), equalTo(true));
+ assertThat(yamlSuites.get("get").size(), greaterThan(1));
+ assertThat(yamlSuites.containsKey("index"), equalTo(true));
+ assertThat(yamlSuites.get("index").size(), greaterThan(1));
+
+ //multiple paths, which can be both directories or yaml test suites (with optional file extension)
+ yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "get/10_basic", "index");
+ assertThat(yamlSuites, notNullValue());
+ assertThat(yamlSuites.size(), equalTo(2));
+ assertThat(yamlSuites.containsKey("get"), equalTo(true));
+ assertThat(yamlSuites.get("get").size(), equalTo(1));
+ assertSingleFile(yamlSuites.get("get"), "get", "10_basic.yaml");
+ assertThat(yamlSuites.containsKey("index"), equalTo(true));
+ assertThat(yamlSuites.get("index").size(), greaterThan(1));
+
+ //files can be loaded from classpath and from file system too
+ File dir = newTempDir();
+ File file = new File(dir, "test_loading.yaml");
+ assertThat(file.createNewFile(), equalTo(true));
+
+ //load from directory outside of the classpath
+ yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "get/10_basic", dir.getAbsolutePath());
+ assertThat(yamlSuites, notNullValue());
+ assertThat(yamlSuites.size(), equalTo(2));
+ assertThat(yamlSuites.containsKey("get"), equalTo(true));
+ assertThat(yamlSuites.get("get").size(), equalTo(1));
+ assertSingleFile(yamlSuites.get("get"), "get", "10_basic.yaml");
+ assertThat(yamlSuites.containsKey(dir.getName()), equalTo(true));
+ assertSingleFile(yamlSuites.get(dir.getName()), dir.getName(), file.getName());
+
+ //load from external file (optional extension)
+ yamlSuites = FileUtils.findYamlSuites("/rest-spec/test", "get/10_basic", dir.getAbsolutePath() + File.separator + "test_loading");
+ assertThat(yamlSuites, notNullValue());
+ assertThat(yamlSuites.size(), equalTo(2));
+ assertThat(yamlSuites.containsKey("get"), equalTo(true));
+ assertThat(yamlSuites.get("get").size(), equalTo(1));
+ assertSingleFile(yamlSuites.get("get"), "get", "10_basic.yaml");
+ assertThat(yamlSuites.containsKey(dir.getName()), equalTo(true));
+ assertSingleFile(yamlSuites.get(dir.getName()), dir.getName(), file.getName());
+ }
+
+ private static void assertSingleFile(Map> yamlSuites, String dirName, String fileName) {
+ assertThat(yamlSuites, notNullValue());
+ assertThat(yamlSuites.size(), equalTo(1));
+ assertThat(yamlSuites.containsKey(dirName), equalTo(true));
+ assertSingleFile(yamlSuites.get(dirName), dirName, fileName);
+ }
+
+ private static void assertSingleFile(Set files, String dirName, String fileName) {
+ assertThat(files.size(), equalTo(1));
+ File file = files.iterator().next();
+ assertThat(file.getName(), equalTo(fileName));
+ assertThat(file.getParentFile().getName(), equalTo(dirName));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/JsonPathTests.java b/src/test/java/org/elasticsearch/test/rest/test/JsonPathTests.java
new file mode 100644
index 000000000000..3382ca5430a9
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/JsonPathTests.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.elasticsearch.test.rest.json.JsonPath;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.*;
+
+public class JsonPathTests extends ElasticsearchTestCase {
+
+ @Test
+ public void testEvaluateObjectPathEscape() throws Exception {
+ String json = "{ \"field1\": { \"field2.field3\" : \"value2\" } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.field2\\.field3");
+ assertThat(object, instanceOf(String.class));
+ assertThat((String)object, equalTo("value2"));
+ }
+
+ @Test
+ public void testEvaluateObjectPathWithDoubleDot() throws Exception {
+ String json = "{ \"field1\": { \"field2\" : \"value2\" } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1..field2");
+ assertThat(object, instanceOf(String.class));
+ assertThat((String)object, equalTo("value2"));
+ }
+
+ @Test
+ public void testEvaluateObjectPathEndsWithDot() throws Exception {
+ String json = "{ \"field1\": { \"field2\" : \"value2\" } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.field2.");
+ assertThat(object, instanceOf(String.class));
+ assertThat((String)object, equalTo("value2"));
+ }
+
+ @Test
+ public void testEvaluateString() throws Exception {
+ String json = "{ \"field1\": { \"field2\" : \"value2\" } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.field2");
+ assertThat(object, instanceOf(String.class));
+ assertThat((String)object, equalTo("value2"));
+ }
+
+ @Test
+ public void testEvaluateInteger() throws Exception {
+ String json = "{ \"field1\": { \"field2\" : 333 } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.field2");
+ assertThat(object, instanceOf(Integer.class));
+ assertThat((Integer)object, equalTo(333));
+ }
+
+ @Test
+ public void testEvaluateDouble() throws Exception {
+ String json = "{ \"field1\": { \"field2\" : 3.55 } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.field2");
+ assertThat(object, instanceOf(Double.class));
+ assertThat((Double)object, equalTo(3.55));
+ }
+
+ @Test
+ public void testEvaluateArray() throws Exception {
+ String json = "{ \"field1\": { \"array1\" : [ \"value1\", \"value2\" ] } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.array1");
+ assertThat(object, instanceOf(List.class));
+ List list = (List) object;
+ assertThat(list.size(), equalTo(2));
+ assertThat(list.get(0), instanceOf(String.class));
+ assertThat((String)list.get(0), equalTo("value1"));
+ assertThat(list.get(1), instanceOf(String.class));
+ assertThat((String)list.get(1), equalTo("value2"));
+ }
+
+ @Test
+ public void testEvaluateArrayElement() throws Exception {
+ String json = "{ \"field1\": { \"array1\" : [ \"value1\", \"value2\" ] } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.array1.1");
+ assertThat(object, instanceOf(String.class));
+ assertThat((String)object, equalTo("value2"));
+ }
+
+ @Test
+ public void testEvaluateArrayElementObject() throws Exception {
+ String json = "{ \"field1\": { \"array1\" : [ {\"element\": \"value1\"}, {\"element\":\"value2\"} ] } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.array1.1.element");
+ assertThat(object, instanceOf(String.class));
+ assertThat((String)object, equalTo("value2"));
+ }
+
+ @Test
+ public void testEvaluateArrayElementObjectWrongPath() throws Exception {
+ String json = "{ \"field1\": { \"array1\" : [ {\"element\": \"value1\"}, {\"element\":\"value2\"} ] } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("field1.array2.1.element");
+ assertThat(object, nullValue());
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testEvaluateObjectKeys() throws Exception {
+ String json = "{ \"metadata\": { \"templates\" : {\"template_1\": { \"field\" : \"value\"}, \"template_2\": { \"field\" : \"value\"} } } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("metadata.templates");
+ assertThat(object, instanceOf(Map.class));
+ Map map = (Map)object;
+ assertThat(map.size(), equalTo(2));
+ Set strings = map.keySet();
+ assertThat(strings, contains("template_1", "template_2"));
+ }
+
+ @Test
+ @SuppressWarnings("unchecked")
+ public void testEvaluateEmptyPath() throws Exception {
+ String json = "{ \"field1\": { \"array1\" : [ {\"element\": \"value1\"}, {\"element\":\"value2\"} ] } }";
+ JsonPath jsonPath = new JsonPath(json);
+ Object object = jsonPath.evaluate("");
+ assertThat(object, notNullValue());
+ assertThat(object, instanceOf(Map.class));
+ assertThat(((Map)object).containsKey("field1"), equalTo(true));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/RestApiParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/RestApiParserTests.java
new file mode 100644
index 000000000000..718598c7c8ef
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/RestApiParserTests.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+import org.elasticsearch.test.rest.spec.RestApi;
+import org.elasticsearch.test.rest.spec.RestApiParser;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class RestApiParserTests extends AbstractParserTests {
+
+ @Test
+ public void testParseRestSpecIndexApi() throws Exception {
+ parser = JsonXContent.jsonXContent.createParser(REST_SPEC_INDEX_API);
+ RestApi restApi = new RestApiParser().parse(parser);
+
+ assertThat(restApi, notNullValue());
+ assertThat(restApi.getName(), equalTo("index"));
+ assertThat(restApi.getMethods().size(), equalTo(2));
+ assertThat(restApi.getMethods().get(0), equalTo("POST"));
+ assertThat(restApi.getMethods().get(1), equalTo("PUT"));
+ assertThat(restApi.getPaths().size(), equalTo(2));
+ assertThat(restApi.getPaths().get(0), equalTo("/{index}/{type}"));
+ assertThat(restApi.getPaths().get(1), equalTo("/{index}/{type}/{id}"));
+ assertThat(restApi.getPathParts().size(), equalTo(3));
+ assertThat(restApi.getPathParts().get(0), equalTo("id"));
+ assertThat(restApi.getPathParts().get(1), equalTo("index"));
+ assertThat(restApi.getPathParts().get(2), equalTo("type"));
+ }
+
+ @Test
+ public void testParseRestSpecGetTemplateApi() throws Exception {
+ parser = JsonXContent.jsonXContent.createParser(REST_SPEC_GET_TEMPLATE_API);
+ RestApi restApi = new RestApiParser().parse(parser);
+ assertThat(restApi, notNullValue());
+ assertThat(restApi.getName(), equalTo("indices.get_template"));
+ assertThat(restApi.getMethods().size(), equalTo(1));
+ assertThat(restApi.getMethods().get(0), equalTo("GET"));
+ assertThat(restApi.getPaths().size(), equalTo(2));
+ assertThat(restApi.getPaths().get(0), equalTo("/_template"));
+ assertThat(restApi.getPaths().get(1), equalTo("/_template/{name}"));
+ assertThat(restApi.getPathParts().size(), equalTo(1));
+ assertThat(restApi.getPathParts().get(0), equalTo("name"));
+ }
+
+ private static final String REST_SPEC_GET_TEMPLATE_API = "{\n" +
+ " \"indices.get_template\": {\n" +
+ " \"documentation\": \"http://www.elasticsearch.org/guide/reference/api/admin-indices-templates/\",\n" +
+ " \"methods\": [\"GET\"],\n" +
+ " \"url\": {\n" +
+ " \"path\": \"/_template/{name}\",\n" +
+ " \"paths\": [\"/_template\", \"/_template/{name}\"],\n" +
+ " \"parts\": {\n" +
+ " \"name\": {\n" +
+ " \"type\" : \"string\",\n" +
+ " \"required\" : false,\n" +
+ " \"description\" : \"The name of the template\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"params\": {\n" +
+ " }\n" +
+ " },\n" +
+ " \"body\": null\n" +
+ " }\n" +
+ "}";
+
+ private static final String REST_SPEC_INDEX_API = "{\n" +
+ " \"index\": {\n" +
+ " \"documentation\": \"http://elasticsearch.org/guide/reference/api/index_/\",\n" +
+ " \"methods\": [\"POST\", \"PUT\"],\n" +
+ " \"url\": {\n" +
+ " \"path\": \"/{index}/{type}\",\n" +
+ " \"paths\": [\"/{index}/{type}\", \"/{index}/{type}/{id}\"],\n" +
+ " \"parts\": {\n" +
+ " \"id\": {\n" +
+ " \"type\" : \"string\",\n" +
+ " \"description\" : \"Document ID\"\n" +
+ " },\n" +
+ " \"index\": {\n" +
+ " \"type\" : \"string\",\n" +
+ " \"required\" : true,\n" +
+ " \"description\" : \"The name of the index\"\n" +
+ " },\n" +
+ " \"type\": {\n" +
+ " \"type\" : \"string\",\n" +
+ " \"required\" : true,\n" +
+ " \"description\" : \"The type of the document\"\n" +
+ " }\n" +
+ " } ,\n" +
+ " \"params\": {\n" +
+ " \"consistency\": {\n" +
+ " \"type\" : \"enum\",\n" +
+ " \"options\" : [\"one\", \"quorum\", \"all\"],\n" +
+ " \"description\" : \"Explicit write consistency setting for the operation\"\n" +
+ " },\n" +
+ " \"op_type\": {\n" +
+ " \"type\" : \"enum\",\n" +
+ " \"options\" : [\"index\", \"create\"],\n" +
+ " \"default\" : \"index\",\n" +
+ " \"description\" : \"Explicit operation type\"\n" +
+ " },\n" +
+ " \"parent\": {\n" +
+ " \"type\" : \"string\",\n" +
+ " \"description\" : \"ID of the parent document\"\n" +
+ " },\n" +
+ " \"percolate\": {\n" +
+ " \"type\" : \"string\",\n" +
+ " \"description\" : \"Percolator queries to execute while indexing the document\"\n" +
+ " },\n" +
+ " \"refresh\": {\n" +
+ " \"type\" : \"boolean\",\n" +
+ " \"description\" : \"Refresh the index after performing the operation\"\n" +
+ " },\n" +
+ " \"replication\": {\n" +
+ " \"type\" : \"enum\",\n" +
+ " \"options\" : [\"sync\",\"async\"],\n" +
+ " \"default\" : \"sync\",\n" +
+ " \"description\" : \"Specific replication type\"\n" +
+ " },\n" +
+ " \"routing\": {\n" +
+ " \"type\" : \"string\",\n" +
+ " \"description\" : \"Specific routing value\"\n" +
+ " },\n" +
+ " \"timeout\": {\n" +
+ " \"type\" : \"time\",\n" +
+ " \"description\" : \"Explicit operation timeout\"\n" +
+ " },\n" +
+ " \"timestamp\": {\n" +
+ " \"type\" : \"time\",\n" +
+ " \"description\" : \"Explicit timestamp for the document\"\n" +
+ " },\n" +
+ " \"ttl\": {\n" +
+ " \"type\" : \"duration\",\n" +
+ " \"description\" : \"Expiration time for the document\"\n" +
+ " },\n" +
+ " \"version\" : {\n" +
+ " \"type\" : \"number\",\n" +
+ " \"description\" : \"Explicit version number for concurrency control\"\n" +
+ " },\n" +
+ " \"version_type\": {\n" +
+ " \"type\" : \"enum\",\n" +
+ " \"options\" : [\"internal\",\"external\"],\n" +
+ " \"description\" : \"Specific version type\"\n" +
+ " }\n" +
+ " }\n" +
+ " },\n" +
+ " \"body\": {\n" +
+ " \"description\" : \"The document\",\n" +
+ " \"required\" : true\n" +
+ " }\n" +
+ " }\n" +
+ "}\n";
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/RestTestParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/RestTestParserTests.java
new file mode 100644
index 000000000000..04e1fe30fbd6
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/RestTestParserTests.java
@@ -0,0 +1,501 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParseContext;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParser;
+import org.elasticsearch.test.rest.section.DoSection;
+import org.elasticsearch.test.rest.section.IsTrueAssertion;
+import org.elasticsearch.test.rest.section.MatchAssertion;
+import org.elasticsearch.test.rest.section.RestTestSuite;
+import org.junit.After;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+
+public class RestTestParserTests extends ElasticsearchTestCase {
+
+ private XContentParser parser;
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ //makes sure that we consumed the whole stream, XContentParser doesn't expose isClosed method
+ //next token can be null even in the middle of the document (e.g. with "---"), but not too many consecutive times
+ assertThat(parser.currentToken(), nullValue());
+ assertThat(parser.nextToken(), nullValue());
+ assertThat(parser.nextToken(), nullValue());
+ parser.close();
+ }
+
+ @Test
+ public void testParseTestSetupAndSections() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "setup:\n" +
+ " - do:\n" +
+ " indices.create:\n" +
+ " index: test_index\n" +
+ "\n" +
+ "---\n" +
+ "\"Get index mapping\":\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ "\n" +
+ " - match: {test_index.test_type.properties.text.type: string}\n" +
+ " - match: {test_index.test_type.properties.text.analyzer: whitespace}\n" +
+ "\n" +
+ "---\n" +
+ "\"Get type mapping - pre 1.0\":\n" +
+ "\n" +
+ " - skip:\n" +
+ " version: \"0.90.9 - 999\"\n" +
+ " reason: \"for newer versions the index name is always returned\"\n" +
+ "\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ " type: test_type\n" +
+ "\n" +
+ " - match: {test_type.properties.text.type: string}\n" +
+ " - match: {test_type.properties.text.analyzer: whitespace}\n"
+ );
+
+ RestTestSuiteParser testParser = new RestTestSuiteParser();
+ RestTestSuite restTestSuite = testParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(restTestSuite, notNullValue());
+ assertThat(restTestSuite.getName(), equalTo("suite"));
+ assertThat(restTestSuite.getSetupSection(), notNullValue());
+ assertThat(restTestSuite.getSetupSection().getSkipSection().isEmpty(), equalTo(true));
+
+ assertThat(restTestSuite.getSetupSection().getDoSections().size(), equalTo(1));
+ assertThat(restTestSuite.getSetupSection().getDoSections().get(0).getApiCallSection().getApi(), equalTo("indices.create"));
+ assertThat(restTestSuite.getSetupSection().getDoSections().get(0).getApiCallSection().getParams().size(), equalTo(1));
+ assertThat(restTestSuite.getSetupSection().getDoSections().get(0).getApiCallSection().getParams().get("index"), equalTo("test_index"));
+
+ assertThat(restTestSuite.getTestSections().size(), equalTo(2));
+
+ assertThat(restTestSuite.getTestSections().get(0).getName(), equalTo("Get index mapping"));
+ assertThat(restTestSuite.getTestSections().get(0).getSkipSection().isEmpty(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().size(), equalTo(3));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(0), instanceOf(DoSection.class));
+ DoSection doSection = (DoSection) restTestSuite.getTestSections().get(0).getExecutableSections().get(0);
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_mapping"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(1));
+ assertThat(doSection.getApiCallSection().getParams().get("index"), equalTo("test_index"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(1), instanceOf(MatchAssertion.class));
+ MatchAssertion matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(1);
+ assertThat(matchAssertion.getField(), equalTo("test_index.test_type.properties.text.type"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("string"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(2), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(2);
+ assertThat(matchAssertion.getField(), equalTo("test_index.test_type.properties.text.analyzer"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("whitespace"));
+
+ assertThat(restTestSuite.getTestSections().get(1).getName(), equalTo("Get type mapping - pre 1.0"));
+ assertThat(restTestSuite.getTestSections().get(1).getSkipSection().isEmpty(), equalTo(false));
+ assertThat(restTestSuite.getTestSections().get(1).getSkipSection().getReason(), equalTo("for newer versions the index name is always returned"));
+ assertThat(restTestSuite.getTestSections().get(1).getSkipSection().getVersion(), equalTo("0.90.9 - 999"));
+ assertThat(restTestSuite.getTestSections().get(1).getExecutableSections().size(), equalTo(3));
+ assertThat(restTestSuite.getTestSections().get(1).getExecutableSections().get(0), instanceOf(DoSection.class));
+ doSection = (DoSection) restTestSuite.getTestSections().get(1).getExecutableSections().get(0);
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_mapping"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(2));
+ assertThat(doSection.getApiCallSection().getParams().get("index"), equalTo("test_index"));
+ assertThat(doSection.getApiCallSection().getParams().get("type"), equalTo("test_type"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(1), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(1).getExecutableSections().get(1);
+ assertThat(matchAssertion.getField(), equalTo("test_type.properties.text.type"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("string"));
+ assertThat(restTestSuite.getTestSections().get(1).getExecutableSections().get(2), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(1).getExecutableSections().get(2);
+ assertThat(matchAssertion.getField(), equalTo("test_type.properties.text.analyzer"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("whitespace"));
+ }
+
+ @Test
+ public void testParseTestSetupAndSectionsSkipLastSection() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "setup:\n" +
+ " - do:\n" +
+ " indices.create:\n" +
+ " index: test_index\n" +
+ "\n" +
+ "---\n" +
+ "\"Get index mapping\":\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ "\n" +
+ " - match: {test_index.test_type.properties.text.type: string}\n" +
+ " - match: {test_index.test_type.properties.text.analyzer: whitespace}\n" +
+ "\n" +
+ "---\n" +
+ "\"Get type mapping - pre 1.0\":\n" +
+ "\n" +
+ " - skip:\n" +
+ " version: \"0.90.9 - 999\"\n" +
+ " reason: \"for newer versions the index name is always returned\"\n" +
+ "\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ " type: test_type\n" +
+ "\n" +
+ " - match: {test_type.properties.text.type: string}\n" +
+ " - match: {test_type.properties.text.analyzer: whitespace}\n"
+ );
+
+ RestTestSuiteParser testParser = new RestTestSuiteParser();
+ RestTestSuite restTestSuite = testParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "1.0.0"));
+
+ assertThat(restTestSuite, notNullValue());
+ assertThat(restTestSuite.getName(), equalTo("suite"));
+ assertThat(restTestSuite.getSetupSection(), notNullValue());
+ assertThat(restTestSuite.getSetupSection().getSkipSection().isEmpty(), equalTo(true));
+
+ assertThat(restTestSuite.getSetupSection().getDoSections().size(), equalTo(1));
+ assertThat(restTestSuite.getSetupSection().getDoSections().get(0).getApiCallSection().getApi(), equalTo("indices.create"));
+ assertThat(restTestSuite.getSetupSection().getDoSections().get(0).getApiCallSection().getParams().size(), equalTo(1));
+ assertThat(restTestSuite.getSetupSection().getDoSections().get(0).getApiCallSection().getParams().get("index"), equalTo("test_index"));
+
+ assertThat(restTestSuite.getTestSections().size(), equalTo(2));
+
+ assertThat(restTestSuite.getTestSections().get(0).getName(), equalTo("Get index mapping"));
+ assertThat(restTestSuite.getTestSections().get(0).getSkipSection().isEmpty(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().size(), equalTo(3));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(0), instanceOf(DoSection.class));
+ DoSection doSection = (DoSection) restTestSuite.getTestSections().get(0).getExecutableSections().get(0);
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_mapping"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(1));
+ assertThat(doSection.getApiCallSection().getParams().get("index"), equalTo("test_index"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(1), instanceOf(MatchAssertion.class));
+ MatchAssertion matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(1);
+ assertThat(matchAssertion.getField(), equalTo("test_index.test_type.properties.text.type"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("string"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(2), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(2);
+ assertThat(matchAssertion.getField(), equalTo("test_index.test_type.properties.text.analyzer"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("whitespace"));
+
+ assertThat(restTestSuite.getTestSections().get(1).getName(), equalTo("Get type mapping - pre 1.0"));
+ assertThat(restTestSuite.getTestSections().get(1).getSkipSection().isEmpty(), equalTo(false));
+ assertThat(restTestSuite.getTestSections().get(1).getSkipSection().getReason(), equalTo("for newer versions the index name is always returned"));
+ assertThat(restTestSuite.getTestSections().get(1).getSkipSection().getVersion(), equalTo("0.90.9 - 999"));
+ assertThat(restTestSuite.getTestSections().get(1).getExecutableSections().size(), equalTo(0));
+ }
+
+ @Test
+ public void testParseTestSetupAndSectionsSkipEntireFile() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "setup:\n" +
+ " - skip:\n" +
+ " version: \"0.90.3 - 0.90.6\"\n" +
+ " reason: \"test skip entire file\"\n" +
+ " - do:\n" +
+ " indices.create:\n" +
+ " index: test_index\n" +
+ "\n" +
+ "---\n" +
+ "\"Get index mapping\":\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ "\n" +
+ " - match: {test_index.test_type.properties.text.type: string}\n" +
+ " - match: {test_index.test_type.properties.text.analyzer: whitespace}\n" +
+ "\n" +
+ "---\n" +
+ "\"Get type mapping - pre 1.0\":\n" +
+ "\n" +
+ " - skip:\n" +
+ " version: \"0.90.9 - 999\"\n" +
+ " reason: \"for newer versions the index name is always returned\"\n" +
+ "\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ " type: test_type\n" +
+ "\n" +
+ " - match: {test_type.properties.text.type: string}\n" +
+ " - match: {test_type.properties.text.analyzer: whitespace}\n"
+ );
+
+ RestTestSuiteParser testParser = new RestTestSuiteParser();
+ RestTestSuite restTestSuite = testParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(restTestSuite, notNullValue());
+ assertThat(restTestSuite.getName(), equalTo("suite"));
+ assertThat(restTestSuite.getSetupSection(), notNullValue());
+
+ assertThat(restTestSuite.getSetupSection().getSkipSection().isEmpty(), equalTo(false));
+ assertThat(restTestSuite.getSetupSection().getSkipSection().getVersion(), equalTo("0.90.3 - 0.90.6"));
+ assertThat(restTestSuite.getSetupSection().getSkipSection().getReason(), equalTo("test skip entire file"));
+
+ assertThat(restTestSuite.getSetupSection().getDoSections().size(), equalTo(0));
+
+ assertThat(restTestSuite.getTestSections().size(), equalTo(0));
+ }
+
+ @Test
+ public void testParseTestSetupAndSectionsSkipEntireFileNoDo() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "setup:\n" +
+ " - skip:\n" +
+ " version: \"0.90.3 - 0.90.6\"\n" +
+ " reason: \"test skip entire file\"\n" +
+ "\n" +
+ "---\n" +
+ "\"Get index mapping\":\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ "\n" +
+ " - match: {test_index.test_type.properties.text.type: string}\n" +
+ " - match: {test_index.test_type.properties.text.analyzer: whitespace}\n" +
+ "\n" +
+ "---\n" +
+ "\"Get type mapping - pre 1.0\":\n" +
+ "\n" +
+ " - skip:\n" +
+ " version: \"0.90.9 - 999\"\n" +
+ " reason: \"for newer versions the index name is always returned\"\n" +
+ "\n" +
+ " - do:\n" +
+ " indices.get_mapping:\n" +
+ " index: test_index\n" +
+ " type: test_type\n" +
+ "\n" +
+ " - match: {test_type.properties.text.type: string}\n" +
+ " - match: {test_type.properties.text.analyzer: whitespace}\n"
+ );
+
+ RestTestSuiteParser testParser = new RestTestSuiteParser();
+ RestTestSuite restTestSuite = testParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(restTestSuite, notNullValue());
+ assertThat(restTestSuite.getName(), equalTo("suite"));
+ assertThat(restTestSuite.getSetupSection(), notNullValue());
+
+ assertThat(restTestSuite.getSetupSection().getSkipSection().isEmpty(), equalTo(false));
+ assertThat(restTestSuite.getSetupSection().getSkipSection().getVersion(), equalTo("0.90.3 - 0.90.6"));
+ assertThat(restTestSuite.getSetupSection().getSkipSection().getReason(), equalTo("test skip entire file"));
+
+ assertThat(restTestSuite.getSetupSection().getDoSections().size(), equalTo(0));
+
+ assertThat(restTestSuite.getTestSections().size(), equalTo(0));
+ }
+
+ @Test
+ public void testParseTestSingleTestSection() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "---\n" +
+ "\"Index with ID\":\n" +
+ "\n" +
+ " - do:\n" +
+ " index:\n" +
+ " index: test-weird-index-中文\n" +
+ " type: weird.type\n" +
+ " id: 1\n" +
+ " body: { foo: bar }\n" +
+ "\n" +
+ " - is_true: ok\n" +
+ " - match: { _index: test-weird-index-中文 }\n" +
+ " - match: { _type: weird.type }\n" +
+ " - match: { _id: \"1\"}\n" +
+ " - match: { _version: 1}\n" +
+ "\n" +
+ " - do:\n" +
+ " get:\n" +
+ " index: test-weird-index-中文\n" +
+ " type: weird.type\n" +
+ " id: 1\n" +
+ "\n" +
+ " - match: { _index: test-weird-index-中文 }\n" +
+ " - match: { _type: weird.type }\n" +
+ " - match: { _id: \"1\"}\n" +
+ " - match: { _version: 1}\n" +
+ " - match: { _source: { foo: bar }}"
+ );
+
+ RestTestSuiteParser testParser = new RestTestSuiteParser();
+ RestTestSuite restTestSuite = testParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(restTestSuite, notNullValue());
+ assertThat(restTestSuite.getName(), equalTo("suite"));
+
+ assertThat(restTestSuite.getSetupSection().isEmpty(), equalTo(true));
+
+ assertThat(restTestSuite.getTestSections().size(), equalTo(1));
+
+ assertThat(restTestSuite.getTestSections().get(0).getName(), equalTo("Index with ID"));
+ assertThat(restTestSuite.getTestSections().get(0).getSkipSection().isEmpty(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().size(), equalTo(12));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(0), instanceOf(DoSection.class));
+ DoSection doSection = (DoSection) restTestSuite.getTestSections().get(0).getExecutableSections().get(0);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("index"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(1), instanceOf(IsTrueAssertion.class));
+ IsTrueAssertion trueAssertion = (IsTrueAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(1);
+ assertThat(trueAssertion.getField(), equalTo("ok"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(2), instanceOf(MatchAssertion.class));
+ MatchAssertion matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(2);
+ assertThat(matchAssertion.getField(), equalTo("_index"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("test-weird-index-中文"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(3), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(3);
+ assertThat(matchAssertion.getField(), equalTo("_type"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("weird.type"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(4), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(4);
+ assertThat(matchAssertion.getField(), equalTo("_id"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("1"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(5), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(5);
+ assertThat(matchAssertion.getField(), equalTo("_version"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("1"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(6), instanceOf(DoSection.class));
+ doSection = (DoSection) restTestSuite.getTestSections().get(0).getExecutableSections().get(6);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("get"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(7), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(7);
+ assertThat(matchAssertion.getField(), equalTo("_index"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("test-weird-index-中文"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(8), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(8);
+ assertThat(matchAssertion.getField(), equalTo("_type"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("weird.type"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(9), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(9);
+ assertThat(matchAssertion.getField(), equalTo("_id"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("1"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(10), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(10);
+ assertThat(matchAssertion.getField(), equalTo("_version"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("1"));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(11), instanceOf(MatchAssertion.class));
+ matchAssertion = (MatchAssertion) restTestSuite.getTestSections().get(0).getExecutableSections().get(11);
+ assertThat(matchAssertion.getField(), equalTo("_source"));
+ assertThat(matchAssertion.getExpectedValue(), instanceOf(Map.class));
+ assertThat(((Map) matchAssertion.getExpectedValue()).get("foo").toString(), equalTo("bar"));
+ }
+
+ @Test
+ public void testParseTestMultipleTestSections() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "---\n" +
+ "\"Missing document (partial doc)\":\n" +
+ "\n" +
+ " - do:\n" +
+ " catch: missing\n" +
+ " update:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body: { doc: { foo: bar } }\n" +
+ "\n" +
+ " - do:\n" +
+ " update:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body: { doc: { foo: bar } }\n" +
+ " ignore: 404\n" +
+ "\n" +
+ "---\n" +
+ "\"Missing document (script)\":\n" +
+ "\n" +
+ "\n" +
+ " - do:\n" +
+ " catch: missing\n" +
+ " update:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body:\n" +
+ " script: \"ctx._source.foo = bar\"\n" +
+ " params: { bar: 'xxx' }\n" +
+ "\n" +
+ " - do:\n" +
+ " update:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " ignore: 404\n" +
+ " body:\n" +
+ " script: \"ctx._source.foo = bar\"\n" +
+ " params: { bar: 'xxx' }\n"
+ );
+
+ RestTestSuiteParser testParser = new RestTestSuiteParser();
+ RestTestSuite restTestSuite = testParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(restTestSuite, notNullValue());
+ assertThat(restTestSuite.getName(), equalTo("suite"));
+
+ assertThat(restTestSuite.getSetupSection().isEmpty(), equalTo(true));
+
+ assertThat(restTestSuite.getTestSections().size(), equalTo(2));
+
+ assertThat(restTestSuite.getTestSections().get(0).getName(), equalTo("Missing document (partial doc)"));
+ assertThat(restTestSuite.getTestSections().get(0).getSkipSection().isEmpty(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().size(), equalTo(2));
+
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(0), instanceOf(DoSection.class));
+ DoSection doSection = (DoSection) restTestSuite.getTestSections().get(0).getExecutableSections().get(0);
+ assertThat(doSection.getCatch(), equalTo("missing"));
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("update"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(1), instanceOf(DoSection.class));
+ doSection = (DoSection) restTestSuite.getTestSections().get(0).getExecutableSections().get(1);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("update"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(4));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(true));
+
+ assertThat(restTestSuite.getTestSections().get(1).getName(), equalTo("Missing document (script)"));
+ assertThat(restTestSuite.getTestSections().get(1).getSkipSection().isEmpty(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(1).getExecutableSections().size(), equalTo(2));
+ assertThat(restTestSuite.getTestSections().get(1).getExecutableSections().get(0), instanceOf(DoSection.class));
+ assertThat(restTestSuite.getTestSections().get(1).getExecutableSections().get(1), instanceOf(DoSection.class));
+ doSection = (DoSection) restTestSuite.getTestSections().get(1).getExecutableSections().get(0);
+ assertThat(doSection.getCatch(), equalTo("missing"));
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("update"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(true));
+ assertThat(restTestSuite.getTestSections().get(0).getExecutableSections().get(1), instanceOf(DoSection.class));
+ doSection = (DoSection) restTestSuite.getTestSections().get(1).getExecutableSections().get(1);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("update"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(4));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(true));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/SetSectionParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/SetSectionParserTests.java
new file mode 100644
index 000000000000..ee0a16290dec
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/SetSectionParserTests.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.rest.parser.RestTestParseException;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParseContext;
+import org.elasticsearch.test.rest.parser.SetSectionParser;
+import org.elasticsearch.test.rest.section.SetSection;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class SetSectionParserTests extends AbstractParserTests {
+
+ @Test
+ public void testParseSetSectionSingleValue() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ _id: id }"
+ );
+
+ SetSectionParser setSectionParser = new SetSectionParser();
+
+ SetSection setSection = setSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(setSection, notNullValue());
+ assertThat(setSection.getStash(), notNullValue());
+ assertThat(setSection.getStash().size(), equalTo(1));
+ assertThat(setSection.getStash().get("_id"), equalTo("id"));
+ }
+
+ @Test
+ public void testParseSetSectionMultipleValues() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ _id: id, _type: type, _index: index }"
+ );
+
+ SetSectionParser setSectionParser = new SetSectionParser();
+
+ SetSection setSection = setSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(setSection, notNullValue());
+ assertThat(setSection.getStash(), notNullValue());
+ assertThat(setSection.getStash().size(), equalTo(3));
+ assertThat(setSection.getStash().get("_id"), equalTo("id"));
+ assertThat(setSection.getStash().get("_type"), equalTo("type"));
+ assertThat(setSection.getStash().get("_index"), equalTo("index"));
+ }
+
+ @Test(expected = RestTestParseException.class)
+ public void testParseSetSectionNoValues() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "{ }"
+ );
+
+ SetSectionParser setSectionParser = new SetSectionParser();
+
+ setSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/elasticsearch/test/rest/test/SetupSectionParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/SetupSectionParserTests.java
new file mode 100644
index 000000000000..9043e297634a
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/SetupSectionParserTests.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParseContext;
+import org.elasticsearch.test.rest.parser.SetupSectionParser;
+import org.elasticsearch.test.rest.section.SetupSection;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class SetupSectionParserTests extends AbstractParserTests {
+
+ @Test
+ public void testParseSetupSection() throws Exception {
+
+ parser = YamlXContent.yamlXContent.createParser(
+ " - do:\n" +
+ " index1:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body: { \"include\": { \"field1\": \"v1\", \"field2\": \"v2\" }, \"count\": 1 }\n" +
+ " - do:\n" +
+ " index2:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 2\n" +
+ " body: { \"include\": { \"field1\": \"v1\", \"field2\": \"v2\" }, \"count\": 1 }\n"
+ );
+
+ SetupSectionParser setupSectionParser = new SetupSectionParser();
+ SetupSection setupSection = setupSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(setupSection, notNullValue());
+ assertThat(setupSection.getSkipSection().isEmpty(), equalTo(true));
+ assertThat(setupSection.getDoSections().size(), equalTo(2));
+ assertThat(setupSection.getDoSections().get(0).getApiCallSection().getApi(), equalTo("index1"));
+ assertThat(setupSection.getDoSections().get(1).getApiCallSection().getApi(), equalTo("index2"));
+ }
+
+ @Test
+ public void testParseSetupAndSkipSectionSkip() throws Exception {
+
+ parser = YamlXContent.yamlXContent.createParser(
+ " - skip:\n" +
+ " version: \"0.90.0 - 0.90.7\"\n" +
+ " reason: \"Update doesn't return metadata fields, waiting for #3259\"\n" +
+ " - do:\n" +
+ " index1:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body: { \"include\": { \"field1\": \"v1\", \"field2\": \"v2\" }, \"count\": 1 }\n" +
+ " - do:\n" +
+ " index2:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 2\n" +
+ " body: { \"include\": { \"field1\": \"v1\", \"field2\": \"v2\" }, \"count\": 1 }\n"
+ );
+
+ SetupSectionParser setupSectionParser = new SetupSectionParser();
+ SetupSection setupSection = setupSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(setupSection, notNullValue());
+ assertThat(setupSection.getSkipSection().isEmpty(), equalTo(false));
+ assertThat(setupSection.getSkipSection(), notNullValue());
+ assertThat(setupSection.getSkipSection().getVersion(), equalTo("0.90.0 - 0.90.7"));
+ assertThat(setupSection.getSkipSection().getReason(), equalTo("Update doesn't return metadata fields, waiting for #3259"));
+ assertThat(setupSection.getDoSections().size(), equalTo(0));
+ }
+
+ @Test
+ public void testParseSetupAndSkipSectionNoSkip() throws Exception {
+
+ parser = YamlXContent.yamlXContent.createParser(
+ " - skip:\n" +
+ " version: \"0.90.0 - 0.90.7\"\n" +
+ " reason: \"Update doesn't return metadata fields, waiting for #3259\"\n" +
+ " - do:\n" +
+ " index1:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 1\n" +
+ " body: { \"include\": { \"field1\": \"v1\", \"field2\": \"v2\" }, \"count\": 1 }\n" +
+ " - do:\n" +
+ " index2:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 2\n" +
+ " body: { \"include\": { \"field1\": \"v1\", \"field2\": \"v2\" }, \"count\": 1 }\n"
+ );
+
+ SetupSectionParser setupSectionParser = new SetupSectionParser();
+ SetupSection setupSection = setupSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.8"));
+
+ assertThat(setupSection, notNullValue());
+ assertThat(setupSection.getSkipSection().isEmpty(), equalTo(false));
+ assertThat(setupSection.getSkipSection(), notNullValue());
+ assertThat(setupSection.getSkipSection().getVersion(), equalTo("0.90.0 - 0.90.7"));
+ assertThat(setupSection.getSkipSection().getReason(), equalTo("Update doesn't return metadata fields, waiting for #3259"));
+ assertThat(setupSection.getDoSections().size(), equalTo(2));
+ assertThat(setupSection.getDoSections().get(0).getApiCallSection().getApi(), equalTo("index1"));
+ assertThat(setupSection.getDoSections().get(1).getApiCallSection().getApi(), equalTo("index2"));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/SkipSectionParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/SkipSectionParserTests.java
new file mode 100644
index 000000000000..87f40a49ab74
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/SkipSectionParserTests.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.rest.parser.RestTestParseException;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParseContext;
+import org.elasticsearch.test.rest.parser.SkipSectionParser;
+import org.elasticsearch.test.rest.section.SkipSection;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+
+public class SkipSectionParserTests extends AbstractParserTests {
+
+ @Test
+ public void testParseSkipSection() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "version: \"0 - 0.90.2\"\n" +
+ "reason: Delete ignores the parent param"
+ );
+
+ SkipSectionParser skipSectionParser = new SkipSectionParser();
+
+ SkipSection skipSection = skipSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(skipSection, notNullValue());
+ assertThat(skipSection.getVersion(), equalTo("0 - 0.90.2"));
+ assertThat(skipSection.getReason(), equalTo("Delete ignores the parent param"));
+ }
+
+ @Test(expected = RestTestParseException.class)
+ public void testParseSkipSectionNoReason() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "version: \"0 - 0.90.2\"\n"
+ );
+
+ SkipSectionParser skipSectionParser = new SkipSectionParser();
+ skipSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ }
+
+ @Test(expected = RestTestParseException.class)
+ public void testParseSkipSectionNoVersion() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "reason: Delete ignores the parent param\n"
+ );
+
+ SkipSectionParser skipSectionParser = new SkipSectionParser();
+ skipSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/elasticsearch/test/rest/test/TestSectionParserTests.java b/src/test/java/org/elasticsearch/test/rest/test/TestSectionParserTests.java
new file mode 100644
index 000000000000..43ca15807f1c
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/TestSectionParserTests.java
@@ -0,0 +1,274 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.common.xcontent.yaml.YamlXContent;
+import org.elasticsearch.test.rest.parser.RestTestSectionParser;
+import org.elasticsearch.test.rest.parser.RestTestSuiteParseContext;
+import org.elasticsearch.test.rest.section.*;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+
+public class TestSectionParserTests extends AbstractParserTests {
+
+ @Test
+ public void testParseTestSectionWithDoSection() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "\"First test section\": \n" +
+ " - do :\n" +
+ " catch: missing\n" +
+ " indices.get_warmer:\n" +
+ " index: test_index\n" +
+ " name: test_warmer"
+ );
+
+ RestTestSectionParser testSectionParser = new RestTestSectionParser();
+ TestSection testSection = testSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(testSection, notNullValue());
+ assertThat(testSection.getName(), equalTo("First test section"));
+ assertThat(testSection.getSkipSection(), equalTo(SkipSection.EMPTY));
+ assertThat(testSection.getExecutableSections().size(), equalTo(1));
+ DoSection doSection = (DoSection)testSection.getExecutableSections().get(0);
+ assertThat(doSection.getCatch(), equalTo("missing"));
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_warmer"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(2));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+ }
+
+ @Test
+ public void testParseTestSectionWithDoSetAndSkipSectionsSkip() throws Exception {
+ String yaml =
+ "\"First test section\": \n" +
+ " - skip:\n" +
+ " version: \"0.90.0 - 0.90.7\"\n" +
+ " reason: \"Update doesn't return metadata fields, waiting for #3259\"\n" +
+ " - do :\n" +
+ " catch: missing\n" +
+ " indices.get_warmer:\n" +
+ " index: test_index\n" +
+ " name: test_warmer\n" +
+ " - set: {_scroll_id: scroll_id}";
+
+
+ RestTestSectionParser testSectionParser = new RestTestSectionParser();
+ parser = YamlXContent.yamlXContent.createParser(yaml);
+ TestSection testSection = testSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.7"));
+
+ assertThat(testSection, notNullValue());
+ assertThat(testSection.getName(), equalTo("First test section"));
+ assertThat(testSection.getSkipSection(), notNullValue());
+ assertThat(testSection.getSkipSection().getVersion(), equalTo("0.90.0 - 0.90.7"));
+ assertThat(testSection.getSkipSection().getReason(), equalTo("Update doesn't return metadata fields, waiting for #3259"));
+ //skip parsing when needed
+ assertThat(testSection.getExecutableSections().size(), equalTo(0));
+ }
+
+ @Test
+ public void testParseTestSectionWithDoSetAndSkipSectionsNoSkip() throws Exception {
+ String yaml =
+ "\"First test section\": \n" +
+ " - skip:\n" +
+ " version: \"0.90.0 - 0.90.7\"\n" +
+ " reason: \"Update doesn't return metadata fields, waiting for #3259\"\n" +
+ " - do :\n" +
+ " catch: missing\n" +
+ " indices.get_warmer:\n" +
+ " index: test_index\n" +
+ " name: test_warmer\n" +
+ " - set: {_scroll_id: scroll_id}";
+
+
+ RestTestSectionParser testSectionParser = new RestTestSectionParser();
+ parser = YamlXContent.yamlXContent.createParser(yaml);
+ TestSection testSection = testSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.8"));
+
+ assertThat(testSection, notNullValue());
+ assertThat(testSection.getName(), equalTo("First test section"));
+ assertThat(testSection.getSkipSection(), notNullValue());
+ assertThat(testSection.getSkipSection().getVersion(), equalTo("0.90.0 - 0.90.7"));
+ assertThat(testSection.getSkipSection().getReason(), equalTo("Update doesn't return metadata fields, waiting for #3259"));
+ assertThat(testSection.getExecutableSections().size(), equalTo(2));
+ DoSection doSection = (DoSection)testSection.getExecutableSections().get(0);
+ assertThat(doSection.getCatch(), equalTo("missing"));
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("indices.get_warmer"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(2));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+ SetSection setSection = (SetSection) testSection.getExecutableSections().get(1);
+ assertThat(setSection.getStash().size(), equalTo(1));
+ assertThat(setSection.getStash().get("_scroll_id"), equalTo("scroll_id"));
+ }
+
+ @Test
+ public void testParseTestSectionWithMultipleDoSections() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "\"Basic\":\n" +
+ "\n" +
+ " - do:\n" +
+ " index:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 中文\n" +
+ " body: { \"foo\": \"Hello: 中文\" }\n" +
+ " - do:\n" +
+ " get:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 中文"
+ );
+
+ RestTestSectionParser testSectionParser = new RestTestSectionParser();
+ TestSection testSection = testSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(testSection, notNullValue());
+ assertThat(testSection.getName(), equalTo("Basic"));
+ assertThat(testSection.getSkipSection(), equalTo(SkipSection.EMPTY));
+ assertThat(testSection.getExecutableSections().size(), equalTo(2));
+ DoSection doSection = (DoSection)testSection.getExecutableSections().get(0);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("index"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(true));
+ doSection = (DoSection)testSection.getExecutableSections().get(1);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("get"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+ }
+
+ @Test
+ public void testParseTestSectionWithDoSectionsAndAssertions() throws Exception {
+ parser = YamlXContent.yamlXContent.createParser(
+ "\"Basic\":\n" +
+ "\n" +
+ " - do:\n" +
+ " index:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 中文\n" +
+ " body: { \"foo\": \"Hello: 中文\" }\n" +
+ "\n" +
+ " - do:\n" +
+ " get:\n" +
+ " index: test_1\n" +
+ " type: test\n" +
+ " id: 中文\n" +
+ "\n" +
+ " - match: { _index: test_1 }\n" +
+ " - is_true: _source\n" +
+ " - match: { _source: { foo: \"Hello: 中文\" } }\n" +
+ "\n" +
+ " - do:\n" +
+ " get:\n" +
+ " index: test_1\n" +
+ " id: 中文\n" +
+ "\n" +
+ " - length: { _index: 6 }\n" +
+ " - is_false: whatever\n" +
+ " - gt: { size: 5 }\n" +
+ " - lt: { size: 10 }"
+ );
+
+ RestTestSectionParser testSectionParser = new RestTestSectionParser();
+ TestSection testSection = testSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+
+ assertThat(testSection, notNullValue());
+ assertThat(testSection.getName(), equalTo("Basic"));
+ assertThat(testSection.getSkipSection(), equalTo(SkipSection.EMPTY));
+ assertThat(testSection.getExecutableSections().size(), equalTo(10));
+
+ DoSection doSection = (DoSection)testSection.getExecutableSections().get(0);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("index"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(true));
+
+ doSection = (DoSection)testSection.getExecutableSections().get(1);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("get"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(3));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+
+ MatchAssertion matchAssertion = (MatchAssertion)testSection.getExecutableSections().get(2);
+ assertThat(matchAssertion.getField(), equalTo("_index"));
+ assertThat(matchAssertion.getExpectedValue().toString(), equalTo("test_1"));
+
+ IsTrueAssertion trueAssertion = (IsTrueAssertion)testSection.getExecutableSections().get(3);
+ assertThat(trueAssertion.getField(), equalTo("_source"));
+
+ matchAssertion = (MatchAssertion)testSection.getExecutableSections().get(4);
+ assertThat(matchAssertion.getField(), equalTo("_source"));
+ assertThat(matchAssertion.getExpectedValue(), instanceOf(Map.class));
+ Map map = (Map) matchAssertion.getExpectedValue();
+ assertThat(map.size(), equalTo(1));
+ assertThat(map.get("foo").toString(), equalTo("Hello: 中文"));
+
+ doSection = (DoSection)testSection.getExecutableSections().get(5);
+ assertThat(doSection.getCatch(), nullValue());
+ assertThat(doSection.getApiCallSection(), notNullValue());
+ assertThat(doSection.getApiCallSection().getApi(), equalTo("get"));
+ assertThat(doSection.getApiCallSection().getParams().size(), equalTo(2));
+ assertThat(doSection.getApiCallSection().hasBody(), equalTo(false));
+
+ LengthAssertion lengthAssertion = (LengthAssertion) testSection.getExecutableSections().get(6);
+ assertThat(lengthAssertion.getField(), equalTo("_index"));
+ assertThat(lengthAssertion.getExpectedValue(), instanceOf(Integer.class));
+ assertThat((Integer) lengthAssertion.getExpectedValue(), equalTo(6));
+
+ IsFalseAssertion falseAssertion = (IsFalseAssertion)testSection.getExecutableSections().get(7);
+ assertThat(falseAssertion.getField(), equalTo("whatever"));
+
+ GreaterThanAssertion greaterThanAssertion = (GreaterThanAssertion) testSection.getExecutableSections().get(8);
+ assertThat(greaterThanAssertion.getField(), equalTo("size"));
+ assertThat(greaterThanAssertion.getExpectedValue(), instanceOf(Integer.class));
+ assertThat((Integer) greaterThanAssertion.getExpectedValue(), equalTo(5));
+
+ LessThanAssertion lessThanAssertion = (LessThanAssertion) testSection.getExecutableSections().get(9);
+ assertThat(lessThanAssertion.getField(), equalTo("size"));
+ assertThat(lessThanAssertion.getExpectedValue(), instanceOf(Integer.class));
+ assertThat((Integer) lessThanAssertion.getExpectedValue(), equalTo(10));
+ }
+
+ @Test
+ public void testSmallSection() throws Exception {
+
+ parser = YamlXContent.yamlXContent.createParser(
+ "\"node_info test\":\n" +
+ " - do:\n" +
+ " cluster.node_info: {}\n" +
+ " \n" +
+ " - is_true: ok\n" +
+ " - is_true: nodes\n" +
+ " - is_true: cluster_name\n");
+ RestTestSectionParser testSectionParser = new RestTestSectionParser();
+ TestSection testSection = testSectionParser.parse(new RestTestSuiteParseContext("api", "suite", parser, "0.90.5"));
+ assertThat(testSection, notNullValue());
+ assertThat(testSection.getName(), equalTo("node_info test"));
+ assertThat(testSection.getExecutableSections().size(), equalTo(4));
+ }
+}
diff --git a/src/test/java/org/elasticsearch/test/rest/test/VersionUtilsTests.java b/src/test/java/org/elasticsearch/test/rest/test/VersionUtilsTests.java
new file mode 100644
index 000000000000..b05acb58d61b
--- /dev/null
+++ b/src/test/java/org/elasticsearch/test/rest/test/VersionUtilsTests.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to ElasticSearch and Shay Banon under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. ElasticSearch 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.elasticsearch.test.rest.test;
+
+import org.elasticsearch.test.ElasticsearchTestCase;
+import org.junit.Test;
+
+import static org.elasticsearch.test.rest.support.VersionUtils.parseVersionNumber;
+import static org.elasticsearch.test.rest.support.VersionUtils.skipCurrentVersion;
+import static org.hamcrest.Matchers.*;
+
+public class VersionUtilsTests extends ElasticsearchTestCase {
+
+ @Test
+ public void testParseVersionNumber() {
+
+ int[] versionNumber = parseVersionNumber("0.90.6");
+ assertThat(versionNumber.length, equalTo(3));
+ assertThat(versionNumber[0], equalTo(0));
+ assertThat(versionNumber[1], equalTo(90));
+ assertThat(versionNumber[2], equalTo(6));
+
+ versionNumber = parseVersionNumber("0.90.999");
+ assertThat(versionNumber.length, equalTo(3));
+ assertThat(versionNumber[0], equalTo(0));
+ assertThat(versionNumber[1], equalTo(90));
+ assertThat(versionNumber[2], equalTo(999));
+
+ versionNumber = parseVersionNumber("0.20.11");
+ assertThat(versionNumber.length, equalTo(3));
+ assertThat(versionNumber[0], equalTo(0));
+ assertThat(versionNumber[1], equalTo(20));
+ assertThat(versionNumber[2], equalTo(11));
+
+ versionNumber = parseVersionNumber("1.0.0.Beta1");
+ assertThat(versionNumber.length, equalTo(3));
+ assertThat(versionNumber[0], equalTo(1));
+ assertThat(versionNumber[1], equalTo(0));
+ assertThat(versionNumber[2], equalTo(0));
+
+ versionNumber = parseVersionNumber("1.0.0.RC1");
+ assertThat(versionNumber.length, equalTo(3));
+ assertThat(versionNumber[0], equalTo(1));
+ assertThat(versionNumber[1], equalTo(0));
+ assertThat(versionNumber[2], equalTo(0));
+
+ versionNumber = parseVersionNumber("1.0.0");
+ assertThat(versionNumber.length, equalTo(3));
+ assertThat(versionNumber[0], equalTo(1));
+ assertThat(versionNumber[1], equalTo(0));
+ assertThat(versionNumber[2], equalTo(0));
+
+ versionNumber = parseVersionNumber("1.0");
+ assertThat(versionNumber.length, equalTo(2));
+ assertThat(versionNumber[0], equalTo(1));
+ assertThat(versionNumber[1], equalTo(0));
+
+ versionNumber = parseVersionNumber("999");
+ assertThat(versionNumber.length, equalTo(1));
+ assertThat(versionNumber[0], equalTo(999));
+
+ versionNumber = parseVersionNumber("0");
+ assertThat(versionNumber.length, equalTo(1));
+ assertThat(versionNumber[0], equalTo(0));
+
+ try {
+ parseVersionNumber("1.0.Beta1");
+ fail("parseVersionNumber should have thrown an error");
+ } catch(IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("version is not a number"));
+ assertThat(e.getCause(), instanceOf(NumberFormatException.class));
+ }
+ }
+
+ @Test
+ public void testSkipCurrentVersion() {
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.6", "0.90.2"), equalTo(true));
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.6", "0.90.3"), equalTo(true));
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.6", "0.90.6"), equalTo(true));
+
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.6", "0.20.10"), equalTo(false));
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.6", "0.90.1"), equalTo(false));
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.6", "0.90.7"), equalTo(false));
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.6", "1.0.0"), equalTo(false));
+
+ assertThat(skipCurrentVersion(" 0.90.2 - 0.90.999 ", "0.90.15"), equalTo(true));
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.999", "1.0.0"), equalTo(false));
+
+ assertThat(skipCurrentVersion("0 - 999", "0.90.15"), equalTo(true));
+ assertThat(skipCurrentVersion("0 - 999", "0.20.1"), equalTo(true));
+ assertThat(skipCurrentVersion("0 - 999", "1.0.0"), equalTo(true));
+
+ assertThat(skipCurrentVersion("0.90.9 - 999", "1.0.0"), equalTo(true));
+ assertThat(skipCurrentVersion("0.90.9 - 999", "0.90.8"), equalTo(false));
+
+ try {
+ assertThat(skipCurrentVersion("0.90.2 - 0.90.999 - 1.0.0", "1.0.0"), equalTo(false));
+ fail("skipCurrentVersion should have thrown an error");
+ } catch(IllegalArgumentException e) {
+ assertThat(e.getMessage(), containsString("too many skip versions found"));
+ }
+
+ }
+}
diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties
index 526e22d4de0e..22f54ef68e52 100644
--- a/src/test/resources/log4j.properties
+++ b/src/test/resources/log4j.properties
@@ -1,6 +1,9 @@
es.logger.level=INFO
log4j.rootLogger=${es.logger.level}, out
+log4j.logger.org.apache.http=INFO, out
+log4j.additivity.org.apache.http=false
+
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.conversionPattern=[%d{ISO8601}][%-5p][%-25c] %m%n