diff --git a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java index 2eec53f008b..3004cce4044 100644 --- a/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java +++ b/core/src/test/java/org/elasticsearch/index/query/AbstractQueryTestCase.java @@ -22,6 +22,7 @@ package org.elasticsearch.index.query; import com.carrotsearch.randomizedtesting.generators.CodepointSetGenerator; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.io.JsonStringEncoder; + import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TermQuery; @@ -381,7 +382,8 @@ public abstract class AbstractQueryTestCase> for (int runs = 0; runs < NUMBER_OF_TESTQUERIES; runs++) { QB testQuery = createTestQueryBuilder(); XContentBuilder builder = toXContent(testQuery, randomFrom(XContentType.values())); - assertParsedQuery(builder.bytes(), testQuery); + XContentBuilder shuffled = shuffleXContent(builder, shuffleProtectedFields()); + assertParsedQuery(shuffled.bytes(), testQuery); for (Map.Entry alternateVersion : getAlternateVersions().entrySet()) { String queryAsString = alternateVersion.getKey(); assertParsedQuery(new BytesArray(queryAsString), alternateVersion.getValue(), ParseFieldMatcher.EMPTY); @@ -389,6 +391,14 @@ public abstract class AbstractQueryTestCase> } } + /** + * Subclasses can override this method and return a set of fields which should be protected from + * recursive random shuffling in the {@link #testFromXContent()} test case + */ + protected Set shuffleProtectedFields() { + return Collections.emptySet(); + } + protected static XContentBuilder toXContent(QueryBuilder query, XContentType contentType) throws IOException { XContentBuilder builder = XContentFactory.contentBuilder(contentType); if (randomBoolean()) { diff --git a/core/src/test/java/org/elasticsearch/index/query/PercolatorQueryBuilderTests.java b/core/src/test/java/org/elasticsearch/index/query/PercolatorQueryBuilderTests.java index 30d4ec908b8..bafdc6225c0 100644 --- a/core/src/test/java/org/elasticsearch/index/query/PercolatorQueryBuilderTests.java +++ b/core/src/test/java/org/elasticsearch/index/query/PercolatorQueryBuilderTests.java @@ -20,6 +20,7 @@ package org.elasticsearch.index.query; import com.fasterxml.jackson.core.JsonParseException; + import org.apache.lucene.search.Query; import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ResourceNotFoundException; @@ -37,12 +38,15 @@ import org.hamcrest.Matchers; import java.io.IOException; import java.util.Collections; +import java.util.Set; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; public class PercolatorQueryBuilderTests extends AbstractQueryTestCase { + private static final Set SHUFFLE_PROTECTED_FIELDS = + Collections.singleton(PercolatorQueryParser.DOCUMENT_FIELD.getPreferredName()); private String indexedDocumentIndex; private String indexedDocumentType; private String indexedDocumentId; @@ -75,6 +79,16 @@ public class PercolatorQueryBuilderTests extends AbstractQueryTestCase shuffleProtectedFields() { + return SHUFFLE_PROTECTED_FIELDS; + } + @Override protected GetResponse executeGet(GetRequest getRequest) { assertThat(getRequest.index(), Matchers.equalTo(indexedDocumentIndex)); @@ -132,6 +146,7 @@ public class PercolatorQueryBuilderTests extends AbstractQueryTestCase exceptFieldNames) throws IOException { + BytesReference bytes = builder.bytes(); + XContentParser parser = XContentFactory.xContent(bytes).createParser(bytes); + // use ordered maps for reproducibility + Map shuffledMap = shuffleMap(parser.mapOrdered(), exceptFieldNames); + XContentBuilder jsonBuilder = XContentFactory.contentBuilder(builder.contentType()); + return jsonBuilder.map(shuffledMap); + } + + private static Map shuffleMap(Map map, Set exceptFieldNames) { + List keys = new ArrayList<>(map.keySet()); + // even though we shuffle later, we need this to make tests reproduce on different jvms + Collections.sort(keys); + Map targetMap = new TreeMap<>(); + Collections.shuffle(keys, random()); + for (String key : keys) { + Object value = map.get(key); + if (value instanceof Map && exceptFieldNames.contains(key) == false) { + targetMap.put(key, shuffleMap((Map) value, exceptFieldNames)); + } else { + targetMap.put(key, value); + } + } + return targetMap; + } + /** * Returns true iff assertions for elasticsearch packages are enabled */ diff --git a/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java b/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java index 0d44fc4abcd..a08e44d46b6 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/test/ESTestCaseTests.java @@ -20,9 +20,22 @@ package org.elasticsearch.test.test; import junit.framework.AssertionFailedError; + +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.test.ESTestCase; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class ESTestCaseTests extends ESTestCase { + public void testExpectThrows() { IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> { throw new IllegalArgumentException("bad arg"); @@ -48,4 +61,52 @@ public class ESTestCaseTests extends ESTestCase { assertEquals("Expected exception IllegalArgumentException", assertFailed.getMessage()); } } + + public void testShuffleXContent() throws IOException { + Map randomStringObjectMap = randomStringObjectMap(5); + XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values())); + builder.map(randomStringObjectMap); + XContentBuilder shuffleXContent = shuffleXContent(builder, Collections.emptySet()); + XContentParser parser = XContentFactory.xContent(shuffleXContent.bytes()).createParser(shuffleXContent.bytes()); + Map resultMap = parser.map(); + assertEquals("both maps should contain the same mappings", randomStringObjectMap, resultMap); + assertNotEquals("Both builders string representations should be different", builder.bytes(), shuffleXContent.bytes()); + } + + private static Map randomStringObjectMap(int depth) { + Map result = new HashMap<>(); + int entries = randomInt(10); + for (int i = 0; i < entries; i++) { + String key = randomAsciiOfLengthBetween(5, 15); + int suprise = randomIntBetween(0, 4); + switch (suprise) { + case 0: + result.put(key, randomUnicodeOfCodepointLength(20)); + break; + case 1: + result.put(key, randomInt(100)); + break; + case 2: + result.put(key, randomDoubleBetween(-100.0, 100.0, true)); + break; + case 3: + result.put(key, randomBoolean()); + break; + case 4: + List stringList = new ArrayList<>(); + int size = randomInt(5); + for (int s = 0; s < size; s++) { + stringList.add(randomUnicodeOfCodepointLength(20)); + } + result.put(key, stringList); + break; + default: + throw new IllegalArgumentException("unexpected random option: " + suprise); + } + } + if (depth > 0) { + result.put(randomAsciiOfLengthBetween(5, 15), randomStringObjectMap(depth - 1)); + } + return result; + } }