diff --git a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java index 16329974fb3cc..a4c08c2b6f13d 100644 --- a/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java +++ b/qa/full-cluster-restart/src/test/java/org/elasticsearch/upgrades/FullClusterRestartIT.java @@ -52,8 +52,6 @@ import java.util.regex.Pattern; import java.util.stream.IntStream; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; @@ -61,6 +59,8 @@ import static org.elasticsearch.cluster.metadata.IndexNameExpressionResolver.SYSTEM_INDEX_ENFORCEMENT_VERSION; import static org.elasticsearch.cluster.routing.UnassignedInfo.INDEX_DELAYED_NODE_LEFT_TIMEOUT_SETTING; import static org.elasticsearch.cluster.routing.allocation.decider.MaxRetryAllocationDecider.SETTING_ALLOCATION_MAX_RETRY; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.elasticsearch.transport.RemoteClusterService.REMOTE_CLUSTER_COMPRESS; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.containsString; diff --git a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/IndexingIT.java b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/IndexingIT.java index 1b9eec1387961..ddf44ba7bd89b 100644 --- a/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/IndexingIT.java +++ b/qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/IndexingIT.java @@ -7,8 +7,6 @@ */ package org.elasticsearch.upgrades; -import io.github.nik9000.mapmatcher.ListMatcher; - import org.apache.http.util.EntityUtils; import org.elasticsearch.Version; import org.elasticsearch.client.Request; @@ -19,6 +17,7 @@ import org.elasticsearch.common.xcontent.support.XContentMapValues; import org.elasticsearch.core.Booleans; import org.elasticsearch.index.mapper.DateFieldMapper; +import org.elasticsearch.test.ListMatcher; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.json.JsonXContent; @@ -30,10 +29,10 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import static io.github.nik9000.mapmatcher.ListMatcher.matchesList; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static org.elasticsearch.rest.action.search.RestSearchAction.TOTAL_HITS_AS_INT_PARAM; +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.either; diff --git a/server/src/internalClusterTest/java/org/elasticsearch/search/profile/aggregation/AggregationProfilerIT.java b/server/src/internalClusterTest/java/org/elasticsearch/search/profile/aggregation/AggregationProfilerIT.java index a1a6041c340ac..78ce52ddc8070 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/search/profile/aggregation/AggregationProfilerIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/search/profile/aggregation/AggregationProfilerIT.java @@ -8,8 +8,6 @@ package org.elasticsearch.search.profile.aggregation; -import io.github.nik9000.mapmatcher.MapMatcher; - import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.common.settings.Settings; @@ -25,6 +23,7 @@ import org.elasticsearch.search.profile.ProfileResult; import org.elasticsearch.search.profile.SearchProfileShardResult; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.MapMatcher; import java.io.IOException; import java.time.Instant; @@ -34,14 +33,14 @@ import java.util.Set; import java.util.stream.Collectors; -import static io.github.nik9000.mapmatcher.ListMatcher.matchesList; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static org.elasticsearch.search.aggregations.AggregationBuilders.avg; import static org.elasticsearch.search.aggregations.AggregationBuilders.diversifiedSampler; import static org.elasticsearch.search.aggregations.AggregationBuilders.histogram; import static org.elasticsearch.search.aggregations.AggregationBuilders.max; import static org.elasticsearch.search.aggregations.AggregationBuilders.terms; +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse; import static org.elasticsearch.xcontent.XContentFactory.jsonBuilder; diff --git a/server/src/internalClusterTest/java/org/elasticsearch/timeseries/support/TimeSeriesMetricsIT.java b/server/src/internalClusterTest/java/org/elasticsearch/timeseries/support/TimeSeriesMetricsIT.java index f66bab6588340..1d8fef2ea855a 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/timeseries/support/TimeSeriesMetricsIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/timeseries/support/TimeSeriesMetricsIT.java @@ -8,8 +8,6 @@ package org.elasticsearch.timeseries.support; -import io.github.nik9000.mapmatcher.MapMatcher; - import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.support.IndicesOptions; @@ -23,6 +21,7 @@ import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.search.aggregations.MultiBucketConsumerService; import org.elasticsearch.test.ESIntegTestCase; +import org.elasticsearch.test.MapMatcher; import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.json.JsonXContent; @@ -39,9 +38,9 @@ import java.util.function.BiConsumer; import java.util.function.IntFunction; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static java.time.temporal.ChronoField.INSTANT_SECONDS; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; @TestLogging(value = "org.elasticsearch.timeseries.support:debug", reason = "test") public class TimeSeriesMetricsIT extends ESIntegTestCase { diff --git a/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java b/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java index 14b733bee3952..faf24fe13c444 100644 --- a/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java +++ b/server/src/test/java/org/elasticsearch/common/xcontent/support/XContentMapValuesTests.java @@ -28,10 +28,10 @@ import java.util.Map; import java.util.Set; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap; import static org.elasticsearch.common.xcontent.XContentHelper.toXContent; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasEntry; diff --git a/server/src/test/java/org/elasticsearch/index/mapper/DocumentMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/DocumentMapperTests.java index 161c950946d36..f63a5b502a104 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/DocumentMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/DocumentMapperTests.java @@ -32,8 +32,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import static io.github.nik9000.mapmatcher.ListMatcher.matchesList; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; diff --git a/server/src/test/java/org/elasticsearch/index/mapper/TimeSeriesIdFieldMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/TimeSeriesIdFieldMapperTests.java index f95c342d054da..c8849fb9c7415 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/TimeSeriesIdFieldMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/TimeSeriesIdFieldMapperTests.java @@ -18,8 +18,8 @@ import java.io.IOException; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/FiltersAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/FiltersAggregatorTests.java index c4ab7f47b5b13..1192aa36988c5 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/FiltersAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/filter/FiltersAggregatorTests.java @@ -7,8 +7,6 @@ */ package org.elasticsearch.search.aggregations.bucket.filter; -import io.github.nik9000.mapmatcher.MapMatcher; - import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.LongPoint; @@ -72,6 +70,7 @@ import org.elasticsearch.search.aggregations.support.AggregationContext; import org.elasticsearch.search.aggregations.support.AggregationInspectionHelper; import org.elasticsearch.search.internal.ContextIndexSearcherTests.DocumentSubsetDirectoryReader; +import org.elasticsearch.test.MapMatcher; import java.io.IOException; import java.util.ArrayList; @@ -84,9 +83,9 @@ import java.util.concurrent.TimeUnit; import java.util.function.IntFunction; -import static io.github.nik9000.mapmatcher.ListMatcher.matchesList; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.greaterThanOrEqualTo; diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregatorTests.java index 45ae206fbefd6..8866fae5147f4 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/histogram/DateHistogramAggregatorTests.java @@ -46,10 +46,10 @@ import java.util.function.Consumer; import java.util.stream.IntStream; -import static io.github.nik9000.mapmatcher.ListMatcher.matchesList; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static java.util.stream.Collectors.toList; +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java index 73683351c85b6..3a77874825e98 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/range/RangeAggregatorTests.java @@ -45,10 +45,10 @@ import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static java.util.Collections.singleton; import static java.util.stream.Collectors.toList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; diff --git a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java index dccf1fc5a02da..323250bfe006f 100644 --- a/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java +++ b/server/src/test/java/org/elasticsearch/search/aggregations/bucket/terms/TermsAggregatorTests.java @@ -7,8 +7,6 @@ */ package org.elasticsearch.search.aggregations.bucket.terms; -import io.github.nik9000.mapmatcher.MapMatcher; - import org.apache.lucene.document.BinaryDocValuesField; import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; @@ -113,6 +111,7 @@ import org.elasticsearch.search.runtime.StringScriptFieldTermQuery; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.ScoreSortBuilder; +import org.elasticsearch.test.MapMatcher; import org.elasticsearch.test.geo.RandomGeoGenerator; import java.io.IOException; @@ -131,13 +130,13 @@ import java.util.function.Function; import java.util.stream.LongStream; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static java.util.Collections.singleton; import static java.util.stream.Collectors.toList; import static org.elasticsearch.index.mapper.SeqNoFieldMapper.PRIMARY_TERM_NAME; import static org.elasticsearch.search.aggregations.AggregationBuilders.terms; import static org.elasticsearch.search.aggregations.PipelineAggregatorBuilders.bucketScript; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.either; import static org.hamcrest.Matchers.equalTo; diff --git a/server/src/test/java/org/elasticsearch/search/fetch/FetchProfilerTests.java b/server/src/test/java/org/elasticsearch/search/fetch/FetchProfilerTests.java index d3c4a805bcf63..66400d6a62439 100644 --- a/server/src/test/java/org/elasticsearch/search/fetch/FetchProfilerTests.java +++ b/server/src/test/java/org/elasticsearch/search/fetch/FetchProfilerTests.java @@ -17,8 +17,8 @@ import java.util.List; import java.util.Set; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.equalTo; public class FetchProfilerTests extends ESTestCase { diff --git a/test/framework/build.gradle b/test/framework/build.gradle index 6fc3b6d464a86..36e3e147a366b 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -31,7 +31,6 @@ dependencies { api 'org.objenesis:objenesis:3.2' api "org.elasticsearch:mocksocket:${versions.mocksocket}" - api "io.github.nik9000:mapmatcher:0.0.3" // json schema validation dependencies api "com.fasterxml.jackson.core:jackson-core:${versions.jackson}" diff --git a/test/framework/src/main/java/org/elasticsearch/test/ListMatcher.java b/test/framework/src/main/java/org/elasticsearch/test/ListMatcher.java new file mode 100644 index 0000000000000..717837b117cd9 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/test/ListMatcher.java @@ -0,0 +1,155 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.test; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import static java.util.Collections.emptyList; +import static org.elasticsearch.test.MapMatcher.describeEntry; +import static org.elasticsearch.test.MapMatcher.describeEntryMissing; +import static org.elasticsearch.test.MapMatcher.describeEntryUnexepected; +import static org.elasticsearch.test.MapMatcher.describeEntryValue; +import static org.elasticsearch.test.MapMatcher.describeMatcher; +import static org.elasticsearch.test.MapMatcher.matcherFor; +import static org.elasticsearch.test.MapMatcher.maxKeyWidthForMatcher; +import static org.hamcrest.Matchers.nullValue; + +/** + * Matcher for {@link List Lists} that reports all errors at once. + */ +public class ListMatcher extends TypeSafeMatcher> { + /** + * Create a {@linkplain ListMatcher} that matches empty lists. + */ + public static ListMatcher matchesList() { + return new ListMatcher(emptyList()); + } + + /** + * Create a {@linkplain ListMatcher} that matches a list. + */ + public static ListMatcher matchesList(List list) { + ListMatcher matcher = matchesList(); + for (Object item : list) { + matcher = matcher.item(item); + } + return matcher; + } + + private final List> matchers; + + private ListMatcher(List> matchers) { + this.matchers = matchers; + } + + /** + * Expect a value. + *

+ * Passing a {@link Matcher} to this method will function as though you + * passed it directly to {@link #item(Matcher)}. + * + * @return a new {@link ListMatcher} that expects all items this matcher + * expected followed by the provided item + */ + public ListMatcher item(Object value) { + return item(matcherFor(value)); + } + + /** + * Expect a {@link Matcher}. + * + * @return a new {@link ListMatcher} that expects all items this matcher + * expected followed by the provided item + */ + public ListMatcher item(Matcher valueMatcher) { + if (valueMatcher == null) { + valueMatcher = nullValue(); + } + List> matchers = new ArrayList<>(this.matchers); + matchers.add(valueMatcher); + return new ListMatcher(matchers); + } + + /** + * {@inheritDoc} + * + * @hidden + */ + @Override + public void describeTo(Description description) { + describeTo(keyWidth(emptyList()), description); + } + + int keyWidth(List item) { + int max = Integer.toString(matchers.size()).length(); + Iterator value = item.iterator(); + Iterator> matcher = matchers.iterator(); + while (matcher.hasNext()) { + max = Math.max(max, maxKeyWidthForMatcher(value.hasNext() ? value.next() : null, matcher.next())); + } + return max; + } + + void describeTo(int keyWidth, Description description) { + description.appendText(matchers.isEmpty() ? "an empty list" : "a list containing"); + int index = 0; + for (Matcher matcher : matchers) { + describeMatcher(keyWidth, index++, matcher, description); + } + } + + @Override + protected boolean matchesSafely(List item) { + if (item.size() != matchers.size()) { + return false; + } + Iterator value = item.iterator(); + Iterator> matcher = matchers.iterator(); + while (matcher.hasNext()) { + if (false == matcher.next().matches(value.next())) { + return false; + } + } + return true; + } + + @Override + protected void describeMismatchSafely(List item, Description description) { + describePotentialMismatch(keyWidth(item), item, description); + } + + void describePotentialMismatch(int keyWidth, List item, Description description) { + description.appendText(matchers.isEmpty() ? "an empty list" : "a list containing"); + int maxKeyWidth = Integer.toString(Math.max(item.size(), matchers.size())).length(); + String keyFormat = "%" + maxKeyWidth + "s"; + + Iterator value = item.iterator(); + Iterator> matcher = matchers.iterator(); + int index = 0; + while (matcher.hasNext()) { + describeEntry(keyWidth, String.format(Locale.ROOT, keyFormat, index++), description); + if (false == value.hasNext()) { + describeEntryMissing(matcher.next(), description); + continue; + } + describeEntryValue(keyWidth, matcher.next(), value.next(), description); + } + while (value.hasNext()) { + describeEntry(keyWidth, String.format(Locale.ROOT, keyFormat, index++), description); + describeEntryUnexepected(value.next(), description); + } + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/test/MapMatcher.java b/test/framework/src/main/java/org/elasticsearch/test/MapMatcher.java new file mode 100644 index 0000000000000..7a788eaacc6d4 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/test/MapMatcher.java @@ -0,0 +1,310 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.test; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.StringDescription; +import org.hamcrest.TypeSafeMatcher; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Stream; + +import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; + +/** + * Matcher for {@link Map Maps} that reports all errors at once. + */ +public class MapMatcher extends TypeSafeMatcher> { + private static final int INDENT = 2; + + /** + * Create a {@linkplain MapMatcher} that matches empty {@link Map}s. + */ + public static MapMatcher matchesMap() { + return new MapMatcher(emptyMap(), false); + } + + /** + * Create a {@linkplain MapMatcher} that matches a {@link Map}. + *

The description and mismatch message are sorted as {@link Map#entrySet} + * because error messages with a consistent order are easier to debug. + * So you should care about this order and provide {@link LinkedHashMap} or a + * {@link TreeMap} or some other {@link Map} that has a nice order. Or build + * an empty matcher with {@link #matchesMap()} and fill it in the order you + * like by calling {@link #entry entry}. + */ + public static MapMatcher matchesMap(Map map) { + MapMatcher matcher = matchesMap(); + for (Map.Entry e : map.entrySet()) { + matcher = matcher.entry(e.getKey(), e.getValue()); + } + return matcher; + } + + /** + * Assert match. Shorter output on failure than + * {@link MatcherAssert#assertThat(Object, Matcher)} that looks better for + * {@link MapMatcher} and {@link ListMatcher}. + */ + public static void assertMap(T actual, Matcher matcher) { + assertMap("", actual, matcher); + } + + /** + * Assert match. Shorter output on failure than + * {@link MatcherAssert#assertThat(Object, Matcher)} that looks better for + * {@link MapMatcher} and {@link ListMatcher}. + */ + public static void assertMap(String reason, T actual, Matcher matcher) { + if (matcher.matches(actual)) { + return; + } + + Description description = new StringDescription(); + description.appendText(reason).appendText("Expected "); + matcher.describeMismatch(actual, description); + + throw new AssertionError(description.toString()); + } + + private final Map> matchers; + + private final boolean extraOk; + + private MapMatcher(Map> matchers, boolean extraOk) { + this.matchers = matchers; + this.extraOk = extraOk; + } + + /** + * Ignore extra entries. + * + * @return a new {@link MapMatcher} that will not fail if it encounters extra entries + */ + public MapMatcher extraOk() { + return new MapMatcher(matchers, true); + } + + /** + * Expect a value. + *

+ * Passing a {@link Matcher} to this method will function as though you + * passed it directly to {@link #entry(Object, Matcher)}. + * + * @return a new {@link MapMatcher} that expects another entry + */ + public MapMatcher entry(Object key, Object value) { + return entry(key, matcherFor(value)); + } + + /** + * Expect a {@link Matcher}. + * + * @return a new {@link MapMatcher} that expects another entry + */ + public MapMatcher entry(Object key, Matcher valueMatcher) { + if (valueMatcher == null) { + valueMatcher = nullValue(); + } + Map> matchers = new LinkedHashMap<>(this.matchers); + Matcher old = matchers.put(key, valueMatcher); + if (old != null) { + throw new IllegalArgumentException("Already had an entry for [" + key + "]: " + old); + } + return new MapMatcher(matchers, extraOk); + } + + /** + * {@inheritDoc} + * + * @hidden + */ + @Override + public void describeTo(Description description) { + describeTo(keyWidth(emptyMap()), description); + } + + int keyWidth(Map item) { + int max = 0; + for (Object k : item.keySet()) { + max = Math.max(max, k.toString().length()); + } + for (Map.Entry> e : matchers.entrySet()) { + max = Math.max(max, e.getKey().toString().length()); + max = Math.max(max, maxKeyWidthForMatcher(item.get(e.getKey()), e.getValue())); + } + return max; + } + + static int maxKeyWidthForMatcher(Object item, Matcher matcher) { + if (matcher instanceof MapMatcher) { + Map longestSubMap = item instanceof Map ? (Map) item : emptyMap(); + return ((MapMatcher) matcher).keyWidth(longestSubMap) - INDENT; + } + if (matcher instanceof ListMatcher) { + List longestSubList = item instanceof List ? (List) item : emptyList(); + return ((ListMatcher) matcher).keyWidth(longestSubList) - INDENT; + } + return 0; + } + + void describeTo(int keyWidth, Description description) { + description.appendText(matchers.isEmpty() ? "an empty map" : "a map containing"); + for (Map.Entry> e : matchers.entrySet()) { + describeMatcher(keyWidth, e.getKey(), e.getValue(), description); + } + } + + static void describeMatcher(int keyWidth, Object key, Matcher matcher, Description description) { + String keyFormat = "\n%" + keyWidth + "s"; + description.appendText(String.format(Locale.ROOT, keyFormat, key)).appendText(": "); + if (matcher instanceof MapMatcher) { + ((MapMatcher) matcher).describeTo(keyWidth + INDENT, description); + return; + } + if (matcher instanceof ListMatcher) { + ((ListMatcher) matcher).describeTo(keyWidth + INDENT, description); + return; + } + description.appendDescriptionOf(matcher); + } + + @Override + protected boolean matchesSafely(Map item) { + if (extraOk) { + if (false == item.keySet().containsAll(matchers.keySet())) { + return false; + } + } else { + if (false == item.keySet().equals(matchers.keySet())) { + return false; + } + } + for (Map.Entry> e : matchers.entrySet()) { + if (false == item.containsKey(e.getKey())) { + return false; + } + Object v = item.get(e.getKey()); + if (false == e.getValue().matches(v)) { + return false; + } + } + return true; + } + + @Override + protected void describeMismatchSafely(Map item, Description description) { + describePotentialMismatch(keyWidth(item), item, description); + } + + void describePotentialMismatch(int keyWidth, Map item, Description description) { + description.appendText(matchers.isEmpty() ? "an empty map" : "a map containing"); + int maxKeyWidth = Stream.concat(matchers.keySet().stream(), item.keySet().stream()) + .mapToInt(k -> k.toString().length()) + .max() + .orElse(keyWidth); + String keyFormat = "%" + maxKeyWidth + "s"; + + for (Map.Entry> e : matchers.entrySet()) { + describeEntry(keyWidth, String.format(Locale.ROOT, keyFormat, e.getKey()), description); + if (false == item.containsKey(e.getKey())) { + describeEntryMissing(e.getValue(), description); + continue; + } + describeEntryValue(keyWidth, e.getValue(), item.get(e.getKey()), description); + } + for (Map.Entry e : item.entrySet()) { + if (false == matchers.containsKey(e.getKey())) { + describeEntry(keyWidth, String.format(Locale.ROOT, keyFormat, e.getKey()), description); + if (extraOk) { + describeEntryUnexepectedButOk(e.getValue(), description); + } else { + describeEntryUnexepected(e.getValue(), description); + } + } + } + } + + /** + * Converts an unknown {@link Object} to an equality {@link Matcher} + * for the public API methods that take {@linkplain Object}. + */ + static Matcher matcherFor(Object value) { + if (value == null) { + return nullValue(); + } + if (value instanceof List) { + return ListMatcher.matchesList((List) value); + } + if (value instanceof Map) { + return matchesMap((Map) value); + } + if (value instanceof Matcher) { + return (Matcher) value; + } + return equalTo(value); + } + + static void describeEntry(int keyWidth, Object key, Description description) { + String keyFormat = "\n%" + keyWidth + "s"; + description.appendText(String.format(Locale.ROOT, keyFormat, key)).appendText(": "); + } + + static void describeEntryMissing(Matcher matcher, Description description) { + description.appendText("expected "); + /* + * Use a short description for multi-line matchers so the "but was " + * bit of the erro is more prominent. It's the more important part. + */ + if (matcher instanceof MapMatcher) { + description.appendText("a map"); + } else if (matcher instanceof ListMatcher) { + description.appendText("a list"); + } else { + description.appendDescriptionOf(matcher); + } + description.appendText(" but was "); + } + + static void describeEntryUnexepected(Object value, Description description) { + description.appendText(" but was "); + description.appendValue(value); + } + + static void describeEntryUnexepectedButOk(Object value, Description description) { + description.appendValue(value); + description.appendText(" unexpected but ok"); + } + + static void describeEntryValue(int keyWidth, Matcher matcher, Object v, Description description) { + if (v instanceof Map && matcher instanceof MapMatcher) { + ((MapMatcher) matcher).describePotentialMismatch(keyWidth + INDENT, (Map) v, description); + return; + } + if (v instanceof List && matcher instanceof ListMatcher) { + ((ListMatcher) matcher).describePotentialMismatch(keyWidth + INDENT, (List) v, description); + return; + } + if (false == matcher.matches(v)) { + description.appendText("expected ").appendDescriptionOf(matcher).appendText(" but "); + matcher.describeMismatch(v, description); + return; + } + description.appendValue(v); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java index 7035139e18920..4ecf86081574e 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java +++ b/test/framework/src/main/java/org/elasticsearch/test/rest/yaml/section/MatchAssertion.java @@ -18,9 +18,9 @@ import java.util.Map; import java.util.regex.Pattern; -import static io.github.nik9000.mapmatcher.ListMatcher.matchesList; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.elasticsearch.test.hamcrest.RegexMatcher.matches; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; diff --git a/test/framework/src/test/java/org/elasticsearch/test/ListMatcherTests.java b/test/framework/src/test/java/org/elasticsearch/test/ListMatcherTests.java new file mode 100644 index 0000000000000..9590b1835ebc6 --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/test/ListMatcherTests.java @@ -0,0 +1,257 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; +import static org.elasticsearch.test.MapMatcherTests.SUBMATCHER; +import static org.elasticsearch.test.MapMatcherTests.SUBMATCHER_ERR; +import static org.elasticsearch.test.MapMatcherTests.assertDescribeTo; +import static org.elasticsearch.test.MapMatcherTests.assertMismatch; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.equalTo; + +public class ListMatcherTests extends ESTestCase { + public void testEmptyMatchesEmpty() { + assertThat(List.of(), matchesList()); + } + + public void testEmptyMismatch() { + assertMismatch(List.of(1), matchesList(), equalTo(""" + an empty list + 0: but was <1>""")); + } + + public void testExpectedEmptyMismatch() { + assertMismatch(List.of("foo", "bar"), matchesList(), equalTo(""" + an empty list + 0: but was "foo" + 1: but was "bar" + """.strip())); + } + + public void testMissing() { + assertMismatch(List.of(), matchesList().item("foo"), equalTo(""" + a list containing + 0: expected "foo" but was """)); + } + + public void testWrongSimpleValue() { + assertMismatch(List.of("foo"), matchesList().item("bar"), equalTo(""" + a list containing + 0: expected "bar" but was "foo" + """.strip())); + } + + public void testExtra() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a list containing\n"); + mismatch.append("0: <1>\n"); + mismatch.append("1: but was <2>"); + assertMismatch(List.of(1, 2), matchesList().item(1), equalTo(mismatch.toString())); + } + + public void testManyExtra() { + assertMismatch(List.of(1, 2, 3), matchesList().item(1), equalTo(""" + a list containing + 0: <1> + 1: but was <2> + 2: but was <3>""")); + } + + public void testManyWrongSimpleValue() { + assertMismatch(List.of(5, 6, 7), matchesList().item(1).item(6).item(10), equalTo(""" + a list containing + 0: expected <1> but was <5> + 1: <6> + 2: expected <10> but was <7>""")); + } + + public void testNullValue() { + List list = new ArrayList<>(); + list.add("foo"); + list.add(null); + assertMap(list, expectNull()); + } + + public void testExpectedNull() { + assertMismatch(List.of("foo", "bar"), expectNull(), equalTo(""" + a list containing + 0: "foo" + 1: expected null but was "bar" + """.trim())); + } + + private ListMatcher expectNull() { + return matchesList().item("foo").item(null); + } + + public void testExpectedButWasNull() { + List list = new ArrayList<>(); + list.add("foo"); + list.add(null); + assertMismatch(list, matchesList().item("foo").item("bar"), equalTo(""" + a list containing + 0: "foo" + 1: expected "bar" but was null""")); + } + + public void testSubMap() { + assertMismatch(List.of(Map.of("bar", 2), 2), matchesList().item(Map.of("bar", 1)).item(2), equalTo(""" + a list containing + 0: a map containing + bar: expected <1> but was <2> + 1: <2>""")); + } + + public void testSubMapMismatchEmpty() { + assertMismatch(List.of(1), matchesList().item(1).item(Map.of("bar", 1)), equalTo(""" + a list containing + 0: <1> + 1: expected a map but was """)); + } + + public void testSubMapMatcher() { + assertMismatch(List.of(Map.of("bar", 2), 2), matchesList().item(matchesMap().entry("bar", 1)).item(2), equalTo(""" + a list containing + 0: a map containing + bar: expected <1> but was <2> + 1: <2>""")); + } + + public void testSubEmptyMap() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a list containing\n"); + mismatch.append("0: an empty map\n"); + mismatch.append("bar: but was <2>\n"); + mismatch.append("1: <2>"); + assertMismatch(List.of(Map.of("bar", 2), 2), matchesList().item(Map.of()).item(2), equalTo(mismatch.toString())); + } + + void testSubList() { + assertMismatch(List.of(List.of(2), 2), matchesList().item(List.of(1)).item(2), equalTo(""" + a list containing + 0: a list containing + 0: expected <1> but was <2> + 1: <2>""")); + } + + public void testSubListMismatchEmpty() { + assertMismatch(List.of(1), matchesList().item(1).item(List.of("bar", 1)), equalTo(""" + a list containing + 0: <1> + 1: expected a list but was """)); + } + + public void testSubListMatcher() { + assertMismatch(List.of(List.of(2), 2), matchesList().item(matchesList().item(1)).item(2), equalTo(""" + a list containing + 0: a list containing + 0: expected <1> but was <2> + 1: <2>""")); + } + + public void testSubEmptyList() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a list containing\n"); + mismatch.append("0: an empty list\n"); + mismatch.append(" 0: but was <2>\n"); + mismatch.append("1: <2>"); + assertMismatch(List.of(List.of(2), 2), matchesList().item(List.of()).item(2), equalTo(mismatch.toString())); + } + + public void testSubMatcher() { + assertMismatch(List.of(2.0, 2), matchesList().item(SUBMATCHER).item(2), equalTo(""" + a list containing + 0: %ERR + 1: <2>""".replace("%ERR", SUBMATCHER_ERR))); + } + + public void testSubMatcherAsValue() { + Object item0 = SUBMATCHER; + assertMismatch(List.of(2.0, 2), matchesList().item(item0).item(2), equalTo(""" + a list containing + 0: %ERR + 1: <2>""".replace("%ERR", SUBMATCHER_ERR))); + } + + public void testProvideList() { + assertMismatch( + List.of(List.of(1), Map.of("bar", 2), 2.0), + matchesList(List.of(List.of(1), Map.of("bar", 1), closeTo(1.0, 0.5))), + equalTo(""" + a list containing + 0: a list containing + 0: <1> + 1: a map containing + bar: expected <1> but was <2> + 2: %ERR""".replace("%ERR", SUBMATCHER_ERR)) + ); + } + + public void testProvideMapContainingNullMatch() { + List list = new ArrayList<>(); + list.add(1); + list.add(null); + assertMap(list, provideListContainingNull()); + } + + public void testProvideMapContainingNullMismatch() { + assertMismatch(List.of(1, "c"), provideListContainingNull(), equalTo(""" + a list containing + 0: <1> + 1: expected null but was "c" + """.trim())); + } + + private ListMatcher provideListContainingNull() { + List spec = new ArrayList<>(); + spec.add(1); + spec.add(null); + return matchesList(spec); + } + + public void testImmutable() { + ListMatcher matcher = matchesList(); + assertMap(List.of("a"), matcher.item("a")); + assertMap(List.of(), matcher); + } + + public void testEmptyDescribeTo() { + assertDescribeTo(matchesList(), equalTo("an empty list")); + } + + public void testSimpleDescribeTo() { + assertDescribeTo(matchesList().item(1).item(3), equalTo(""" + a list containing + 0: <1> + 1: <3>""")); + } + + public void testSubListDescribeTo() { + assertDescribeTo(matchesList().item(1).item(matchesList().item(0)), equalTo(""" + a list containing + 0: <1> + 1: a list containing + 0: <0>""")); + } + + public void testSubMapDescribeTo() { + assertDescribeTo(matchesList().item(1).item(matchesMap().entry("foo", 0)), equalTo(""" + a list containing + 0: <1> + 1: a map containing + foo: <0>""")); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/test/MapMatcherTests.java b/test/framework/src/test/java/org/elasticsearch/test/MapMatcherTests.java new file mode 100644 index 0000000000000..48c9fcab3898a --- /dev/null +++ b/test/framework/src/test/java/org/elasticsearch/test/MapMatcherTests.java @@ -0,0 +1,410 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.test; + +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.XContentParserConfiguration; +import org.elasticsearch.xcontent.json.JsonXContent; +import org.hamcrest.Matcher; +import org.hamcrest.StringDescription; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; + +public class MapMatcherTests extends ESTestCase { + static final Matcher SUBMATCHER = closeTo(1.0, .5); + static final String SUBMATCHER_ERR = "expected a numeric value within <0.5> of <1.0> " + + "but <2.0> differed by <0.5> more than delta <0.5>"; + + public void testEmptyMatchesEmpty() { + assertThat(Map.of(), matchesMap()); + } + + public void testExpectedEmptyMismatch() { + assertMismatch(Map.of("foo", "bar"), matchesMap(), equalTo(""" + an empty map + foo: but was "bar" + """.strip())); + } + + public void testMissing() { + assertMismatch(Map.of(), matchesMap().entry("foo", "bar"), equalTo(""" + a map containing + foo: expected "bar" but was """)); + } + + public void testWrongSimpleValue() { + assertMismatch(Map.of("foo", "baz"), matchesMap().entry("foo", "bar"), equalTo(""" + a map containing + foo: expected "bar" but was "baz" + """.strip())); + } + + public void testExtra() { + assertMismatch(Map.of("foo", 1), matchesMap().entry("bar", 1), equalTo(""" + a map containing + bar: expected <1> but was + foo: but was <1>""")); + } + + /** + * When there are extra entries in the comparison map we iterate them in order. + */ + public void testManyExtra() { + Map map = new LinkedHashMap<>(); + map.put("foo", 1); + map.put("baz", 2); + assertMismatch(map, matchesMap().entry("bar", 1), equalTo(""" + a map containing + bar: expected <1> but was + foo: but was <1> + baz: but was <2>""")); + } + + void testExtraOk() { + assertMap(Map.of("foo", 1), matchesMap().extraOk()); + } + + void testExtraOkMismatchSimple() { + assertMismatch(Map.of("foo", 1), matchesMap().entry("bar", 1).extraOk(), equalTo(""" + a map containing + bar: expected <1> but was + foo: <1> unexpected but ok""")); + } + + void testExtraOkMismatchExtraMap() { + assertMismatch(Map.of("foo", Map.of("i", 1)), matchesMap().entry("bar", 1).extraOk(), equalTo(""" + a map containing + bar: expected <1> but was + foo: <{i=1}> unexpected but ok""")); + } + + public void testExtraOkMismatchExtraList() { + assertMismatch(Map.of("foo", List.of(1)), matchesMap().entry("bar", 1).extraOk(), equalTo(""" + a map containing + bar: expected <1> but was + foo: <[1]> unexpected but ok""")); + } + + public void testManyWrongSimpleValue() { + assertMismatch(Map.of("foo", 1, "bar", 2, "baz", 3), matchesMap().entry("foo", 2).entry("bar", 2).entry("baz", 4), equalTo(""" + a map containing + foo: expected <2> but was <1> + bar: <2> + baz: expected <4> but was <3>""")); + } + + public void testNullValue() { + Map map = new HashMap<>(); + map.put("a", "foo"); + map.put("b", null); + assertMap(map, expectNull()); + } + + public void testExpectedNull() { + assertMismatch(Map.of("a", "foo", "b", "bar"), expectNull(), equalTo(""" + a map containing + a: "foo" + b: expected null but was "bar" + """.trim())); + } + + private MapMatcher expectNull() { + return matchesMap().entry("a", "foo").entry("b", null); + } + + public void testExpectedButWasNull() { + Map map = new HashMap<>(); + map.put("a", "foo"); + map.put("b", null); + assertMismatch(map, matchesMap().entry("a", "foo").entry("b", "bar"), equalTo(""" + a map containing + a: "foo" + b: expected "bar" but was null""")); + } + + public void testSubMap() { + assertMismatch(Map.of("foo", Map.of("bar", 2), "baz", 2), matchesMap().entry("foo", Map.of("bar", 1)).entry("baz", 2), equalTo(""" + a map containing + foo: a map containing + bar: expected <1> but was <2> + baz: <2>""")); + } + + public void testSubMapMismatchEmpty() { + assertMismatch(Map.of(), matchesMap().entry("foo", Map.of("bar", 1)).entry("baz", 2), equalTo(""" + a map containing + foo: expected a map but was + baz: expected <2> but was """)); + } + + public void testSubMapMatcher() { + assertMismatch( + Map.of("foo", Map.of("bar", 2), "baz", 2), + matchesMap().entry("foo", matchesMap().entry("bar", 1)).entry("baz", 2), + equalTo(""" + a map containing + foo: a map containing + bar: expected <1> but was <2> + baz: <2>""") + ); + } + + public void testSubEmptyExpectedMap() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a map containing\n"); + mismatch.append("foo: an empty map\n"); + mismatch.append(" bar: but was <2>\n"); + mismatch.append("baz: <2>"); + assertMismatch( + Map.of("foo", Map.of("bar", 2), "baz", 2), + matchesMap().entry("foo", Map.of()).entry("baz", 2), + equalTo(mismatch.toString()) + ); + } + + public void testSubEmptyActualMap() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a map containing\n"); + mismatch.append("foo: a map containing\n"); + mismatch.append(" bar: expected <2> but was \n"); + mismatch.append("baz: <2>"); + assertMismatch( + Map.of("foo", Map.of(), "baz", 2), + matchesMap().entry("foo", Map.of("bar", 2)).entry("baz", 2), + equalTo(mismatch.toString()) + ); + } + + public void testSubEmptyActualAndExpectedMap() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a map containing\n"); + mismatch.append("foo: an empty map\n"); + mismatch.append("bar: expected <2> but was <1>"); + assertMismatch( + Map.of("foo", Map.of(), "bar", 1), + matchesMap().entry("foo", Map.of()).entry("bar", 2), + equalTo(mismatch.toString()) + ); + } + + public void testSubList() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a map containing\n"); + mismatch.append("foo: a list containing\n"); + mismatch.append(" 0: expected <1> but was <2>\n"); + mismatch.append("bar: <2>"); + assertMismatch( + Map.of("foo", List.of(2), "bar", 2), + matchesMap().entry("foo", List.of(1)).entry("bar", 2), + equalTo(mismatch.toString()) + ); + } + + public void testSubListMismatchEmpty() { + assertMismatch(Map.of(), matchesMap().entry("foo", List.of(1)).entry("baz", 2), equalTo(""" + a map containing + foo: expected a list but was + baz: expected <2> but was """)); + } + + public void testSubListMatcher() { + assertMismatch(Map.of("foo", List.of(2), "bar", 2), matchesMap().entry("foo", matchesList().item(1)).entry("bar", 2), equalTo(""" + a map containing + foo: a list containing + 0: expected <1> but was <2> + bar: <2>""")); + } + + public void testSubEmptyList() { + StringBuilder mismatch = new StringBuilder(); + mismatch.append("a map containing\n"); + mismatch.append("foo: an empty list\n"); + mismatch.append(" 0: but was <2>\n"); + mismatch.append("bar: <2>"); + assertMismatch( + Map.of("foo", List.of(2), "bar", 2), + matchesMap().entry("foo", List.of()).entry("bar", 2), + equalTo(mismatch.toString()) + ); + } + + public void testSubMatcher() { + assertMismatch(Map.of("foo", 2.0, "bar", 2), matchesMap().entry("foo", SUBMATCHER).entry("bar", 2), equalTo(""" + a map containing + foo: %ERR + bar: <2>""".replace("%ERR", SUBMATCHER_ERR))); + } + + public void testSubMatcherAsValue() { + Object foo = SUBMATCHER; + assertMismatch(Map.of("foo", 2.0, "bar", 2), matchesMap().entry("foo", foo).entry("bar", 2), equalTo(""" + a map containing + foo: %ERR + bar: <2>""".replace("%ERR", SUBMATCHER_ERR))); + } + + public void testProvideMap() { + /* + * Iteration order of the specification map gives the order of the + * error message so we use a LinkedHashMap to preserve our order. + */ + Map spec = new LinkedHashMap<>(); + spec.put("foo", List.of(1)); + spec.put("bar", Map.of("a", 2)); + spec.put("baz", SUBMATCHER); + assertMismatch(Map.of("foo", List.of(2), "bar", Map.of("a", 2), "baz", 2.0), matchesMap(spec), equalTo(""" + a map containing + foo: a list containing + 0: expected <1> but was <2> + bar: a map containing + a: <2> + baz: %ERR""".replace("%ERR", SUBMATCHER_ERR))); + } + + public void testProvideMapContainingNullMatch() { + Map map = new HashMap<>(); + map.put("foo", 1); + map.put("bar", null); + assertMap(map, provideMapContainingNull()); + } + + public void testProvideMapContainingNullMismatch() { + assertMismatch(Map.of("foo", 1, "bar", "c"), provideMapContainingNull(), equalTo(""" + a map containing + foo: <1> + bar: expected null but was "c" + """.trim())); + } + + private MapMatcher provideMapContainingNull() { + /* + * Iteration order of the specification map gives the order of the + * error message so we use a LinkedHashMap to preserve our order. + */ + Map spec = new LinkedHashMap<>(); + spec.put("foo", 1); + spec.put("bar", null); + return matchesMap(spec); + } + + public void testBig() throws IOException { + assertMap( + read("es-response.json"), + matchesMap().entry("took", 57) + .entry("timed_out", false) + .entry("_shards", matchesMap().entry("total", 1).entry("successful", 1).entry("skipped", 0).entry("failed", 0)) + .entry( + "hits", + matchesMap().entry("total", matchesMap().entry("value", 10000).entry("relation", "gte")) + .entry("max_score", 1.0) + .entry( + "hits", + matchesList().item( + matchesMap().entry("_index", "nyc_taxis") + .entry("_id", "SIjZyXcBsaR104_0ECjx") + .entry("_score", 1.0) + .entry( + "_source", + matchesMap().entry("extra", 0.5) + .entry("tolls_amount", 0.0) + .entry("passenger_count", 1) + .entry("store_and_fwd_flag", "N") + .entry("tip_amount", 1.76) + .entry("mta_tax", 0.5) + .entry("improvement_surcharge", 0.3) + .entry("fare_amount", 7.5) + .entry("dropoff_datetime", "2015-07-23 21:45:16") + .entry("total_amount", 10.56) + .entry("rate_code_id", "1") + .entry("payment_type", "1") + .entry("vendor_id", "2") + .entry("pickup_datetime", "2015-07-23 21:37:38") + .entry("trip_distance", 1.59) + .entry( + "pickup_location", + matchesList().item(closeTo(-73.97788, 0.000005)).item(closeTo(40.75482, 0.000005)) + ) + .entry( + "dropoff_location", + matchesList().item(closeTo(-73.95908, 0.000005)).item(closeTo(40.76345, 0.000005)) + ) + ) + ) + ) + ) + ); + } + + public void testImmutable() { + MapMatcher matcher = matchesMap(); + assertMap(Map.of("a", "a"), matcher.entry("a", "a")); + assertMap(Map.of(), matcher); + } + + private Map read(String file) throws IOException { + try ( + InputStream data = Thread.currentThread().getContextClassLoader().getResourceAsStream(file); + XContentParser parser = JsonXContent.jsonXContent.createParser(XContentParserConfiguration.EMPTY, data) + ) { + return parser.mapOrdered(); + } + } + + public void testEmptyDescribeTo() { + assertDescribeTo(matchesMap(), equalTo("an empty map")); + } + + public void testSimpleDescribeTo() { + assertDescribeTo(matchesMap().entry("foo", 1).entry("bar", 3), equalTo(""" + a map containing + foo: <1> + bar: <3>""")); + } + + public void testSubListDescribeTo() { + assertDescribeTo(matchesMap().entry("foo", 1).entry("bar", matchesList().item(0)), equalTo(""" + a map containing + foo: <1> + bar: a list containing + 0: <0>""")); + } + + public void testSubMapDescribeTo() { + assertDescribeTo(matchesMap().entry("foo", 1).entry("bar", matchesMap().entry("baz", 0)), equalTo(""" + a map containing + foo: <1> + bar: a map containing + baz: <0>""")); + } + + static void assertMismatch(T v, Matcher matcher, Matcher mismatchDescriptionMatcher) { + assertMap(v, not(matcher)); + StringDescription description = new StringDescription(); + matcher.describeMismatch(v, description); + assertThat(description.toString(), mismatchDescriptionMatcher); + } + + static void assertDescribeTo(Matcher matcher, Matcher describeToMatcher) { + StringDescription description = new StringDescription(); + matcher.describeTo(description); + assertThat(description.toString(), describeToMatcher); + } +} diff --git a/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/StashTests.java b/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/StashTests.java index 4e9a63f56e8af..91378cafebedf 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/StashTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/rest/yaml/StashTests.java @@ -15,9 +15,9 @@ import java.util.HashMap; import java.util.Map; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; import static java.util.Collections.singletonMap; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.sameInstance; diff --git a/test/framework/src/test/resources/es-response.json b/test/framework/src/test/resources/es-response.json new file mode 100644 index 0000000000000..903bd4fdc362e --- /dev/null +++ b/test/framework/src/test/resources/es-response.json @@ -0,0 +1,49 @@ +{ + "took" : 57, + "timed_out" : false, + "_shards" : { + "total" : 1, + "successful" : 1, + "skipped" : 0, + "failed" : 0 + }, + "hits" : { + "total" : { + "value" : 10000, + "relation" : "gte" + }, + "max_score" : 1.0, + "hits" : [ + { + "_index" : "nyc_taxis", + "_id" : "SIjZyXcBsaR104_0ECjx", + "_score" : 1.0, + "_source" : { + "extra" : 0.5, + "tolls_amount" : 0.0, + "passenger_count" : 1, + "store_and_fwd_flag" : "N", + "tip_amount" : 1.76, + "mta_tax" : 0.5, + "improvement_surcharge" : 0.3, + "fare_amount" : 7.5, + "dropoff_datetime" : "2015-07-23 21:45:16", + "total_amount" : 10.56, + "rate_code_id" : "1", + "payment_type" : "1", + "vendor_id" : "2", + "pickup_datetime" : "2015-07-23 21:37:38", + "trip_distance" : 1.59, + "pickup_location" : [ + -73.9778823852539, + 40.75482177734375 + ], + "dropoff_location" : [ + -73.9590835571289, + 40.763450622558594 + ] + } + } + ] + } +} diff --git a/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java b/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java index d2f6b0215d081..e8b1ee702fc3d 100644 --- a/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java +++ b/x-pack/plugin/ccr/qa/multi-cluster/src/test/java/org/elasticsearch/xpack/ccr/FollowIndexIT.java @@ -26,8 +26,8 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import static io.github.nik9000.mapmatcher.MapMatcher.assertMap; -import static io.github.nik9000.mapmatcher.MapMatcher.matchesMap; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyOrNullString; import static org.hamcrest.Matchers.equalTo;