Add assertBusy helper test method

We use awaitBusy in our tests, the problem is that we have to check if it awaited or not, and then try and keep around somehow more info around why the predicate failed and a timeout happened.
The idea of assertBusy is to allow to simply write "regular" test code, and if the test code trips, it will busy wait till a timeout. This allows us to keep around the assertion information and properly throw it for information that is inherently kept in the failure itself.
This commit is contained in:
Shay Banon 2014-07-06 12:34:41 +02:00
parent 7335b5db22
commit 10030a63cc
2 changed files with 94 additions and 105 deletions

View File

@ -56,6 +56,7 @@ import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.metadata.MappingMetaData; import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.service.PendingClusterTask;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Priority; import org.elasticsearch.common.Priority;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -91,6 +92,7 @@ import org.elasticsearch.indices.store.IndicesStore;
import org.elasticsearch.rest.RestStatus; import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.SearchService; import org.elasticsearch.search.SearchService;
import org.elasticsearch.test.client.RandomizingClient; import org.elasticsearch.test.client.RandomizingClient;
import org.hamcrest.Matchers;
import org.junit.*; import org.junit.*;
import java.io.IOException; import java.io.IOException;
@ -99,10 +101,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_REPLICAS;
import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS; import static org.elasticsearch.cluster.metadata.IndexMetaData.SETTING_NUMBER_OF_SHARDS;
@ -112,6 +111,7 @@ import static org.elasticsearch.test.InternalTestCluster.clusterName;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
import static org.hamcrest.Matchers.emptyIterable; import static org.hamcrest.Matchers.emptyIterable;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
/** /**
* {@link ElasticsearchIntegrationTest} is an abstract base class to run integration * {@link ElasticsearchIntegrationTest} is an abstract base class to run integration
@ -715,152 +715,89 @@ public abstract class ElasticsearchIntegrationTest extends ElasticsearchTestCase
/** /**
* Waits until all nodes have no pending tasks. * Waits until all nodes have no pending tasks.
*/ */
public void waitNoPendingTasksOnAll() throws InterruptedException { public void waitNoPendingTasksOnAll() throws Exception {
client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get(); assertNoTimeout(client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get());
final PendingClusterTasksResponse[] reference = new PendingClusterTasksResponse[1]; assertBusy(new Runnable() {
boolean applied = awaitBusy(new Predicate<Object>() {
@Override @Override
public boolean apply(Object input) { public void run() {
reference[0] = null;
for (Client client : clients()) { for (Client client : clients()) {
PendingClusterTasksResponse pendingTasks = client.admin().cluster().preparePendingClusterTasks().setLocal(true).get(); PendingClusterTasksResponse pendingTasks = client.admin().cluster().preparePendingClusterTasks().setLocal(true).get();
if (!pendingTasks.pendingTasks().isEmpty()) { assertThat("client " + client + " still has pending tasks " + pendingTasks.prettyPrint(), pendingTasks, Matchers.emptyIterable());
reference[0] = pendingTasks;
return false;
}
} }
return true;
} }
}); });
if (!applied) { assertNoTimeout(client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get());
fail(reference[0].prettyPrint());
}
client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get();
} }
/** /**
* Waits until the elected master node has no pending tasks. * Waits until the elected master node has no pending tasks.
*/ */
public void waitNoPendingTasksOnMaster() throws InterruptedException { public void waitNoPendingTasksOnMaster() throws Exception {
client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get(); assertNoTimeout(client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get());
final PendingClusterTasksResponse[] reference = new PendingClusterTasksResponse[1]; assertBusy(new Runnable() {
boolean applied = awaitBusy(new Predicate<Object>() {
@Override @Override
public boolean apply(Object input) { public void run() {
reference[0] = null; PendingClusterTasksResponse pendingTasks = client().admin().cluster().preparePendingClusterTasks().setLocal(true).get();
PendingClusterTasksResponse pendingTasks = client().admin().cluster().preparePendingClusterTasks().get(); assertThat("master still has pending tasks " + pendingTasks.prettyPrint(), pendingTasks, Matchers.emptyIterable());
if (!pendingTasks.pendingTasks().isEmpty()) {
reference[0] = pendingTasks;
return false;
}
return true;
} }
}); });
if (!applied) { assertNoTimeout(client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get());
fail(reference[0].prettyPrint());
}
client().admin().cluster().prepareHealth().setWaitForEvents(Priority.LANGUID).get();
} }
/** /**
* Waits till a (pattern) field name mappings concretely exists on all nodes. Note, this waits for the current * Waits till a (pattern) field name mappings concretely exists on all nodes. Note, this waits for the current
* started shards and checks for concrete mappings. * started shards and checks for concrete mappings.
*/ */
public void waitForConcreteMappingsOnAll(final String index, final String type, final String... fieldNames) throws InterruptedException { public void waitForConcreteMappingsOnAll(final String index, final String type, final String... fieldNames) throws Exception {
boolean applied = awaitBusy(new Predicate<Object>() { assertBusy(new Runnable() {
@Override @Override
public boolean apply(Object input) { public void run() {
try { Set<String> nodes = internalCluster().nodesInclude(index);
Set<String> nodes = internalCluster().nodesInclude(index); assertThat(nodes, Matchers.not(Matchers.emptyIterable()));
if (nodes.isEmpty()) { // we expect at least one node to hold an index, so wait if not allocated yet for (String node : nodes) {
return false; IndicesService indicesService = internalCluster().getInstance(IndicesService.class, node);
IndexService indexService = indicesService.indexService(index);
assertThat("index service doesn't exists on " + node, indexService, notNullValue());
DocumentMapper documentMapper = indexService.mapperService().documentMapper(type);
assertThat("document mapper doesn't exists on " + node, documentMapper, notNullValue());
for (String fieldName : fieldNames) {
Set<String> matches = documentMapper.mappers().simpleMatchToFullName(fieldName);
assertThat("field " + fieldName + " doesn't exists on " + node, matches, Matchers.not(emptyIterable()));
} }
for (String node : nodes) {
IndicesService indicesService = internalCluster().getInstance(IndicesService.class, node);
IndexService indexService = indicesService.indexService(index);
if (indexService == null) {
return false;
}
DocumentMapper documentMapper = indexService.mapperService().documentMapper(type);
if (documentMapper == null) {
return false;
}
for (String fieldName : fieldNames) {
Set<String> matches = documentMapper.mappers().simpleMatchToFullName(fieldName);
if (matches.isEmpty()) {
return false;
}
}
}
} catch (Exception e) {
logger.info("got exception waiting for concrete mappings", e);
return false;
} }
return true;
} }
}); });
if (!applied) {
fail("failed to find mappings on all nodes for index " + index + ", type " + type + ", and fieldName " + Arrays.toString(fieldNames));
}
waitForMappingOnMaster(index, type, fieldNames); waitForMappingOnMaster(index, type, fieldNames);
} }
/** /**
* Waits for the given mapping type to exists on the master node. * Waits for the given mapping type to exists on the master node.
*/ */
public void waitForMappingOnMaster(final String index, final String type, final String... fieldNames) throws InterruptedException { public void waitForMappingOnMaster(final String index, final String type, final String... fieldNames) throws Exception {
final GetMappingsResponse[] lastResponse = new GetMappingsResponse[1]; assertBusy(new Callable() {
boolean applied = awaitBusy(new Predicate<Object>() {
@Override @Override
public boolean apply(Object input) { public Object call() throws Exception {
GetMappingsResponse response = lastResponse[0] = client().admin().indices().prepareGetMappings(index).setTypes(type).get(); GetMappingsResponse response = client().admin().indices().prepareGetMappings(index).setTypes(type).get();
ImmutableOpenMap<String, MappingMetaData> mappings = response.getMappings().get(index); ImmutableOpenMap<String, MappingMetaData> mappings = response.getMappings().get(index);
if (mappings == null) { assertThat(mappings, notNullValue());
return false;
}
MappingMetaData mappingMetaData = mappings.get(type); MappingMetaData mappingMetaData = mappings.get(type);
if (mappingMetaData == null) { assertThat(mappingMetaData, notNullValue());
return false;
}
Map<String, Object> mappingSource; Map<String, Object> mappingSource = mappingMetaData.getSourceAsMap();
try { assertFalse(mappingSource.isEmpty());
mappingSource = mappingMetaData.getSourceAsMap(); assertTrue(mappingSource.containsKey("properties"));
} catch (IOException e) {
throw ExceptionsHelper.convertToElastic(e);
}
if (mappingSource.isEmpty() && !mappingSource.containsKey("properties")) {
return false;
}
for (String fieldName : fieldNames) { for (String fieldName : fieldNames) {
Map<String, Object> mappingProperties = (Map<String, Object>) mappingSource.get("properties"); Map<String, Object> mappingProperties = (Map<String, Object>) mappingSource.get("properties");
if (fieldName.indexOf('.') != -1) { if (fieldName.indexOf('.') != -1) {
fieldName = fieldName.replace(".", ".properties."); fieldName = fieldName.replace(".", ".properties.");
} }
if (XContentMapValues.extractValue(fieldName, mappingProperties) == null) { assertThat("field " + fieldName + " doesn't exists in mapping " + mappingMetaData.source().string(), XContentMapValues.extractValue(fieldName, mappingProperties), notNullValue());
return false;
}
} }
return true; return null;
} }
}); });
if (!applied) {
String source = null;
ImmutableOpenMap<String, MappingMetaData> mappings = lastResponse[0].getMappings().get(index);
if (mappings != null) {
MappingMetaData mappingMetaData = mappings.get(type);
if (mappingMetaData != null) {
try {
source = mappingMetaData.source().string();
} catch (IOException e) {
throw ExceptionsHelper.convertToElastic(e);
}
}
}
fail("failed to find mappings for index " + index + ", type " + type + ", fields " + fieldNames + ", on master node, mapping source [" + source + "]");
}
} }
/** /**

View File

@ -55,6 +55,8 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.net.URI; import java.net.URI;
import java.util.*; import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAllFilesClosed; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAllFilesClosed;
@ -97,6 +99,56 @@ public abstract class ElasticsearchTestCase extends AbstractRandomizedTest {
} }
/**
* Runs the code block for 10 seconds waiting for no assertion to trip.
*/
public static void assertBusy(Runnable codeBlock) throws Exception {
assertBusy(Executors.callable(codeBlock), 10, TimeUnit.SECONDS);
}
public static void assertBusy(Runnable codeBlock, long maxWaitTime, TimeUnit unit) throws Exception {
assertBusy(Executors.callable(codeBlock), maxWaitTime, unit);
}
/**
* Runs the code block for 10 seconds waiting for no assertion to trip.
*/
public static <V> V assertBusy(Callable<V> codeBlock) throws Exception {
return assertBusy(codeBlock, 10, TimeUnit.SECONDS);
}
/**
* Runs the code block for the provided interval, waiting for no assertions to trip.
*/
public static <V> V assertBusy(Callable<V> codeBlock, long maxWaitTime, TimeUnit unit) throws Exception {
long maxTimeInMillis = TimeUnit.MILLISECONDS.convert(maxWaitTime, unit);
long iterations = Math.max(Math.round(Math.log10(maxTimeInMillis) / Math.log10(2)), 1);
long timeInMillis = 1;
long sum = 0;
List<AssertionError> failures = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
try {
return codeBlock.call();
} catch (AssertionError e) {
failures.add(e);
}
sum += timeInMillis;
Thread.sleep(timeInMillis);
timeInMillis *= 2;
}
timeInMillis = maxTimeInMillis - sum;
Thread.sleep(Math.max(timeInMillis, 0));
try {
return codeBlock.call();
} catch (AssertionError e) {
for (AssertionError failure : failures) {
e.addSuppressed(failure);
}
throw e;
}
}
public static boolean awaitBusy(Predicate<?> breakPredicate) throws InterruptedException { public static boolean awaitBusy(Predicate<?> breakPredicate) throws InterruptedException {
return awaitBusy(breakPredicate, 10, TimeUnit.SECONDS); return awaitBusy(breakPredicate, 10, TimeUnit.SECONDS);
} }