blob: de5d0ab1d338a90fa725cdd9d883195740acb483 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.beam.sdk.transforms.display;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.is;
import java.util.Arrays;
import java.util.Collection;
import org.apache.beam.sdk.transforms.display.DisplayData.Item;
import org.apache.beam.vendor.guava.v26_0_jre.com.google.common.collect.Sets;
import org.hamcrest.CustomTypeSafeMatcher;
import org.hamcrest.Description;
import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeDiagnosingMatcher;
import org.joda.time.Duration;
import org.joda.time.Instant;
/** Hamcrest matcher for making assertions on {@link DisplayData} instances. */
public class DisplayDataMatchers {
/** Do not instantiate. */
private DisplayDataMatchers() {}
/** Creates a matcher that matches if the examined {@link DisplayData} contains any items. */
public static Matcher<DisplayData> hasDisplayItem() {
return new FeatureMatcher<DisplayData, Collection<DisplayData.Item>>(
Matchers.not(Matchers.empty()), "DisplayData", "DisplayData") {
@Override
protected Collection<Item> featureValueOf(DisplayData actual) {
return actual.items();
}
};
}
/**
* Creates a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key.
*/
public static Matcher<DisplayData> hasDisplayItem(String key) {
return hasDisplayItem(hasKey(key));
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key and String value.
*/
public static Matcher<DisplayData> hasDisplayItem(String key, String value) {
return hasDisplayItem(key, DisplayData.Type.STRING, value);
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key and Boolean value.
*/
public static Matcher<DisplayData> hasDisplayItem(String key, Boolean value) {
return hasDisplayItem(key, DisplayData.Type.BOOLEAN, value);
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key and Duration value.
*/
public static Matcher<DisplayData> hasDisplayItem(String key, Duration value) {
return hasDisplayItem(key, DisplayData.Type.DURATION, value);
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key and Float value.
*/
public static Matcher<DisplayData> hasDisplayItem(String key, double value) {
return hasDisplayItem(key, DisplayData.Type.FLOAT, value);
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key and Integer value.
*/
public static Matcher<DisplayData> hasDisplayItem(String key, long value) {
return hasDisplayItem(key, DisplayData.Type.INTEGER, value);
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key and Class value.
*/
public static Matcher<DisplayData> hasDisplayItem(String key, Class<?> value) {
return hasDisplayItem(key, DisplayData.Type.JAVA_CLASS, value);
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains an item with the
* specified key and Timestamp value.
*/
public static Matcher<DisplayData> hasDisplayItem(String key, Instant value) {
return hasDisplayItem(key, DisplayData.Type.TIMESTAMP, value);
}
private static Matcher<DisplayData> hasDisplayItem(
String key, DisplayData.Type type, Object value) {
DisplayData.FormattedItemValue formattedValue = type.format(value);
return hasDisplayItem(
allOf(hasKey(key), hasType(type), hasValue(formattedValue.getLongValue())));
}
/**
* Creates a matcher that matches if the examined {@link DisplayData} contains any item matching
* the specified {@code itemMatcher}.
*/
public static Matcher<DisplayData> hasDisplayItem(Matcher<DisplayData.Item> itemMatcher) {
return new HasDisplayDataItemMatcher(itemMatcher);
}
private static class HasDisplayDataItemMatcher extends TypeSafeDiagnosingMatcher<DisplayData> {
private final Matcher<Item> itemMatcher;
private HasDisplayDataItemMatcher(Matcher<DisplayData.Item> itemMatcher) {
this.itemMatcher = itemMatcher;
}
@Override
public void describeTo(Description description) {
description.appendText("display data with item: ");
itemMatcher.describeTo(description);
}
@Override
protected boolean matchesSafely(DisplayData data, Description mismatchDescription) {
Collection<Item> items = data.items();
boolean isMatch = Matchers.hasItem(itemMatcher).matches(items);
if (!isMatch) {
mismatchDescription.appendText("found " + items.size() + " non-matching item(s):\n");
mismatchDescription.appendValue(data);
}
return isMatch;
}
}
/**
* Create a matcher that matches if the examined {@link DisplayData} contains all display data
* registered from the specified subcomponent and namespace.
*/
public static Matcher<DisplayData> includesDisplayDataFor(
final String path, final HasDisplayData subComponent) {
return new CustomTypeSafeMatcher<DisplayData>("includes subcomponent") {
@Override
protected boolean matchesSafely(DisplayData displayData) {
DisplayData subComponentData = subComponentData(path);
if (subComponentData.items().isEmpty()) {
throw new UnsupportedOperationException(
"subComponent contains no display data; " + "cannot verify whether it is included");
}
DisplayDataComparison comparison = checkSubset(displayData, subComponentData, path);
return comparison.missingItems.isEmpty();
}
@Override
protected void describeMismatchSafely(
DisplayData displayData, Description mismatchDescription) {
DisplayData subComponentDisplayData = subComponentData(path);
DisplayDataComparison comparison = checkSubset(displayData, subComponentDisplayData, path);
mismatchDescription
.appendText("did not include:\n")
.appendValue(comparison.missingItems)
.appendText("\nNon-matching items:\n")
.appendValue(comparison.unmatchedItems);
}
private DisplayData subComponentData(final String path) {
return DisplayData.from(builder -> builder.include(path, subComponent));
}
private DisplayDataComparison checkSubset(
DisplayData displayData, DisplayData included, String path) {
DisplayDataComparison comparison = new DisplayDataComparison(displayData.items());
for (Item item : included.items()) {
Item matchedItem =
displayData
.asMap()
.get(
DisplayData.Identifier.of(
DisplayData.Path.absolute(path), item.getNamespace(), item.getKey()));
if (matchedItem != null) {
comparison.matched(matchedItem);
} else {
comparison.missing(item);
}
}
return comparison;
}
class DisplayDataComparison {
Collection<Item> missingItems;
Collection<Item> unmatchedItems;
DisplayDataComparison(Collection<Item> superset) {
missingItems = Sets.newHashSet();
unmatchedItems = Sets.newHashSet(superset);
}
void matched(Item supersetItem) {
unmatchedItems.remove(supersetItem);
}
void missing(Item subsetItem) {
missingItems.add(subsetItem);
}
}
};
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} contains a key with the
* specified value.
*/
public static Matcher<DisplayData.Item> hasKey(String key) {
return hasKey(is(key));
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} contains a key matching
* the specified key matcher.
*/
public static Matcher<DisplayData.Item> hasKey(Matcher<String> keyMatcher) {
return new FeatureMatcher<DisplayData.Item, String>(keyMatcher, "with key", "key") {
@Override
protected String featureValueOf(DisplayData.Item actual) {
return actual.getKey();
}
};
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} contains a path
* matching the specified namespace.
*/
public static Matcher<DisplayData.Item> hasPath(String... paths) {
DisplayData.Path path =
(paths.length == 0)
? DisplayData.Path.root()
: DisplayData.Path.absolute(paths[0], Arrays.copyOfRange(paths, 1, paths.length));
return new FeatureMatcher<DisplayData.Item, DisplayData.Path>(
is(path), " with namespace", "namespace") {
@Override
protected DisplayData.Path featureValueOf(DisplayData.Item actual) {
return actual.getPath();
}
};
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} contains the specified
* namespace.
*/
public static Matcher<DisplayData.Item> hasNamespace(Class<?> namespace) {
return hasNamespace(Matchers.<Class<?>>is(namespace));
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} contains a namespace
* matching the specified namespace matcher.
*/
public static Matcher<DisplayData.Item> hasNamespace(Matcher<Class<?>> namespaceMatcher) {
return new FeatureMatcher<DisplayData.Item, Class<?>>(
namespaceMatcher, " with namespace", "namespace") {
@Override
protected Class<?> featureValueOf(DisplayData.Item actual) {
return actual.getNamespace();
}
};
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} matches the specified
* type.
*/
public static Matcher<DisplayData.Item> hasType(DisplayData.Type type) {
return hasType(is(type));
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} has a type matching the
* specified type matcher.
*/
public static Matcher<DisplayData.Item> hasType(Matcher<DisplayData.Type> typeMatcher) {
return new FeatureMatcher<DisplayData.Item, DisplayData.Type>(
typeMatcher, "with type", "type") {
@Override
protected DisplayData.Type featureValueOf(DisplayData.Item actual) {
return actual.getType();
}
};
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} has the specified
* value.
*/
public static Matcher<DisplayData.Item> hasValue(Object value) {
return hasValue(is(value));
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} contains a value
* matching the specified value matcher.
*/
public static <T> Matcher<DisplayData.Item> hasValue(Matcher<T> valueMatcher) {
return new FeatureMatcher<DisplayData.Item, T>(valueMatcher, "with value", "value") {
@Override
protected T featureValueOf(DisplayData.Item actual) {
@SuppressWarnings("unchecked")
T value = (T) actual.getValue();
return value;
}
};
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} has the specified
* label.
*/
public static Matcher<DisplayData.Item> hasLabel(String label) {
return hasLabel(is(label));
}
/**
* Creates a matcher that matches if the examined {@link DisplayData.Item} has a label matching
* the specified label matcher.
*/
public static Matcher<DisplayData.Item> hasLabel(Matcher<String> labelMatcher) {
return new FeatureMatcher<DisplayData.Item, String>(
labelMatcher, "display item with label", "label") {
@Override
protected String featureValueOf(DisplayData.Item actual) {
return actual.getLabel();
}
};
}
}