Repository: incubator-beam Updated Branches: refs/heads/master 3e71ed481 -> 2f8ba65fa
DisplayData tweaks based on transform usage. * Add boolean-valued display data. * Implement equality for DislpayData.Item * Add ability to override namespace for included subcomponents. * Additional Matchers for testing display data * Update DisplayData inner class privacy Project: http://git-wip-us.apache.org/repos/asf/incubator-beam/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-beam/commit/014a9a5a Tree: http://git-wip-us.apache.org/repos/asf/incubator-beam/tree/014a9a5a Diff: http://git-wip-us.apache.org/repos/asf/incubator-beam/diff/014a9a5a Branch: refs/heads/master Commit: 014a9a5a2241b4f780efea30c14496961c55041d Parents: 3e71ed4 Author: Scott Wegner <sweg...@google.com> Authored: Mon Mar 28 13:38:35 2016 -0700 Committer: bchambers <bchamb...@google.com> Committed: Tue Apr 5 09:56:47 2016 -0700 ---------------------------------------------------------------------- .../sdk/transforms/display/DisplayData.java | 85 +++++++-- .../transforms/display/DisplayDataMatchers.java | 181 ++++++++++++++++++- .../display/DisplayDataMatchersTest.java | 63 ++++++- .../sdk/transforms/display/DisplayDataTest.java | 128 ++++++++----- 4 files changed, 391 insertions(+), 66 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/014a9a5a/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java ---------------------------------------------------------------------- diff --git a/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java b/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java index bfdf73e..63ec7fc 100644 --- a/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java +++ b/sdks/java/core/src/main/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayData.java @@ -88,7 +88,7 @@ public class DisplayData { public String toString() { StringBuilder builder = new StringBuilder(); boolean isFirstLine = true; - for (Map.Entry<Identifier, Item> entry : entries.entrySet()) { + for (Item entry : entries.values()) { if (isFirstLine) { isFirstLine = false; } else { @@ -107,14 +107,19 @@ public class DisplayData { */ public interface Builder { /** - * Include display metadata from the specified subcomponent. For example, a {@link ParDo} + * Register display metadata from the specified subcomponent. For example, a {@link ParDo} * transform includes display metadata from the encapsulated {@link DoFn}. - * - * @return A builder instance to continue to build in a fluent-style. */ Builder include(HasDisplayData subComponent); /** + * Register display metadata from the specified subcomponent, using the specified namespace. + * For example, a {@link ParDo} transform includes display metadata from the encapsulated + * {@link DoFn}. + */ + Builder include(HasDisplayData subComponent, Class<?> namespace); + + /** * Register the given string display metadata. The metadata item will be registered with type * {@link DisplayData.Type#STRING}, and is identified by the specified key and namespace from * the current transform or component. @@ -136,6 +141,13 @@ public class DisplayData { ItemBuilder add(String key, double value); /** + * Register the given floating point display metadata. The metadata item will be registered with + * type {@link DisplayData.Type#BOOLEAN}, and is identified by the specified key and namespace + * from the current transform or component. + */ + ItemBuilder add(String key, boolean value); + + /** * Register the given timestamp display metadata. The metadata item will be registered with type * {@link DisplayData.Type#TIMESTAMP}, and is identified by the specified key and namespace from * the current transform or component. @@ -287,7 +299,35 @@ public class DisplayData { @Override public String toString() { - return getValue(); + return String.format("%s:%s=%s", ns, key, value); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Item) { + Item that = (Item) obj; + return Objects.equals(this.ns, that.ns) + && Objects.equals(this.key, that.key) + && Objects.equals(this.type, that.type) + && Objects.equals(this.value, that.value) + && Objects.equals(this.shortValue, that.shortValue) + && Objects.equals(this.label, that.label) + && Objects.equals(this.url, that.url); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash( + this.ns, + this.key, + this.type, + this.value, + this.shortValue, + this.label, + this.url); } private Item withLabel(String label) { @@ -313,8 +353,12 @@ public class DisplayData { private final String ns; private final String key; - static Identifier of(Class<?> namespace, String key) { - return new Identifier(namespace.getName(), key); + public static Identifier of(Class<?> namespace, String key) { + return of(namespace.getName(), key); + } + + public static Identifier of(String namespace, String key) { + return new Identifier(namespace, key); } private Identifier(String ns, String key) { @@ -355,7 +399,7 @@ public class DisplayData { /** * Display metadata type. */ - enum Type { + public enum Type { STRING { @Override FormattedItemValue format(Object value) { @@ -374,6 +418,12 @@ public class DisplayData { return new FormattedItemValue(Double.toString((Double) value)); } }, + BOOLEAN() { + @Override + FormattedItemValue format(Object value) { + return new FormattedItemValue(Boolean.toString((boolean) value)); + } + }, TIMESTAMP() { @Override FormattedItemValue format(Object value) { @@ -403,7 +453,7 @@ public class DisplayData { abstract FormattedItemValue format(Object value); } - private static class FormattedItemValue { + static class FormattedItemValue { private final String shortValue; private final String longValue; @@ -416,11 +466,11 @@ public class DisplayData { this.shortValue = shortValue; } - private String getLongValue () { + String getLongValue() { return this.longValue; } - private String getShortValue() { + String getShortValue() { return this.shortValue; } } @@ -447,10 +497,16 @@ public class DisplayData { @Override public Builder include(HasDisplayData subComponent) { checkNotNull(subComponent); + return include(subComponent, subComponent.getClass()); + } + + @Override + public Builder include(HasDisplayData subComponent, Class<?> namespace) { + checkNotNull(subComponent); boolean newComponent = visited.add(subComponent); if (newComponent) { Class prevNs = this.latestNs; - this.latestNs = subComponent.getClass(); + this.latestNs = namespace; subComponent.populateDisplayData(this); this.latestNs = prevNs; } @@ -475,6 +531,11 @@ public class DisplayData { } @Override + public ItemBuilder add(String key, boolean value) { + return addItem(key, Type.BOOLEAN, value); + } + + @Override public ItemBuilder add(String key, Instant value) { checkNotNull(value); return addItem(key, Type.TIMESTAMP, value); http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/014a9a5a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java ---------------------------------------------------------------------- diff --git a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java index 385bc42..d540b4b 100644 --- a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java +++ b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchers.java @@ -17,13 +17,19 @@ */ package com.google.cloud.dataflow.sdk.transforms.display; +import static org.hamcrest.Matchers.allOf; + import com.google.cloud.dataflow.sdk.transforms.display.DisplayData.Item; +import 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; import java.util.Collection; @@ -44,6 +50,71 @@ public class DisplayDataMatchers { } /** + * 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}. */ @@ -69,7 +140,8 @@ public class DisplayDataMatchers { Collection<Item> items = data.items(); boolean isMatch = Matchers.hasItem(itemMatcher).matches(items); if (!isMatch) { - mismatchDescription.appendText("found " + items.size() + " non-matching items"); + mismatchDescription.appendText("found " + items.size() + " non-matching item(s):\n"); + mismatchDescription.appendValue(data); } return isMatch; @@ -77,6 +149,85 @@ public class DisplayDataMatchers { } /** + * Create a matcher that matches if the examined {@link DisplayData} contains all display data + * registered from the specified subcomponent. + */ + public static Matcher<DisplayData> includes(final HasDisplayData subComponent) { + return includes(subComponent, subComponent.getClass()); + } + + /** + * 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> includes( + final HasDisplayData subComponent, final Class<? extends HasDisplayData> namespace) { + return new CustomTypeSafeMatcher<DisplayData>("includes subcomponent") { + @Override + protected boolean matchesSafely(DisplayData displayData) { + DisplayData subComponentData = DisplayData.from(subComponent); + if (subComponentData.items().size() == 0) { + throw new UnsupportedOperationException("subComponent contains no display data; " + + "cannot verify whether it is included"); + } + + DisplayDataComparision comparison = checkSubset(displayData, subComponentData, namespace); + return comparison.missingItems.isEmpty(); + } + + + @Override + protected void describeMismatchSafely( + DisplayData displayData, Description mismatchDescription) { + DisplayData subComponentDisplayData = DisplayData.from(subComponent); + DisplayDataComparision comparison = checkSubset( + displayData, subComponentDisplayData, subComponent.getClass()); + + mismatchDescription + .appendText("did not include:\n") + .appendValue(comparison.missingItems) + .appendText("\nNon-matching items:\n") + .appendValue(comparison.unmatchedItems); + } + + private DisplayDataComparision checkSubset( + DisplayData displayData, DisplayData included, Class<?> namespace) { + DisplayDataComparision comparison = new DisplayDataComparision(displayData.items()); + for (Item item : included.items()) { + Item matchedItem = displayData.asMap().get( + DisplayData.Identifier.of(namespace, item.getKey())); + + if (matchedItem != null) { + comparison.matched(matchedItem); + } else { + comparison.missing(item); + } + } + + return comparison; + } + + class DisplayDataComparision { + Collection<DisplayData.Item> missingItems; + Collection<DisplayData.Item> unmatchedItems; + + DisplayDataComparision(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. */ @@ -96,4 +247,32 @@ public class DisplayDataMatchers { } }; } + + public static Matcher<DisplayData.Item> hasType(DisplayData.Type type) { + return hasType(Matchers.is(type)); + } + + 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(); + } + }; + } + + public static Matcher<DisplayData.Item> hasValue(String value) { + return hasValue(Matchers.is(value)); + } + + public static Matcher<DisplayData.Item> hasValue(Matcher<String> valueMatcher) { + return new FeatureMatcher<DisplayData.Item, String>( + valueMatcher, "with value", "value") { + @Override + protected String featureValueOf(DisplayData.Item actual) { + return actual.getValue(); + } + }; + } } http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/014a9a5a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java ---------------------------------------------------------------------- diff --git a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java index 3ade923..1b43ff7 100644 --- a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java +++ b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataMatchersTest.java @@ -19,11 +19,13 @@ package com.google.cloud.dataflow.sdk.transforms.display; import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasDisplayItem; import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasKey; +import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasType; +import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasValue; +import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.includes; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.core.StringStartsWith.startsWith; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; import com.google.cloud.dataflow.sdk.transforms.PTransform; import com.google.cloud.dataflow.sdk.transforms.display.DisplayData.Builder; @@ -46,7 +48,7 @@ public class DisplayDataMatchersTest { Matcher<DisplayData> matcher = hasDisplayItem(); assertFalse(matcher.matches(DisplayData.none())); - assertTrue(matcher.matches(createDisplayDataWithItem("foo", "bar"))); + assertThat(createDisplayDataWithItem("foo", "bar"), matcher); } @Test @@ -59,15 +61,68 @@ public class DisplayDataMatchersTest { matcher.describeMismatch(DisplayData.none(), mismatchDesc); assertThat(desc.toString(), startsWith("display data with item: ")); - assertThat(mismatchDesc.toString(), containsString("found 0 non-matching items")); + assertThat(mismatchDesc.toString(), containsString("found 0 non-matching item(s)")); } @Test public void testHasKey() { Matcher<DisplayData> matcher = hasDisplayItem(hasKey("foo")); - assertTrue(matcher.matches(createDisplayDataWithItem("foo", "bar"))); assertFalse(matcher.matches(createDisplayDataWithItem("fooz", "bar"))); + + assertThat(createDisplayDataWithItem("foo", "bar"), matcher); + } + + @Test + public void testHasType() { + Matcher<DisplayData> matcher = hasDisplayItem(hasType(DisplayData.Type.JAVA_CLASS)); + + DisplayData data = DisplayData.from(new PTransform<PCollection<String>, PCollection<String>>() { + @Override + public void populateDisplayData(Builder builder) { + builder.add("foo", DisplayDataMatchersTest.class); + } + }); + + assertFalse(matcher.matches(createDisplayDataWithItem("fooz", "bar"))); + assertThat(data, matcher); + } + + @Test + public void testHasValue() { + Matcher<DisplayData> matcher = hasDisplayItem(hasValue("bar")); + + assertFalse(matcher.matches(createDisplayDataWithItem("foo", "baz"))); + assertThat(createDisplayDataWithItem("foo", "bar"), matcher); + } + + @Test + public void testIncludes() { + final HasDisplayData subComponent = new HasDisplayData() { + @Override + public void populateDisplayData(Builder builder) { + builder.add("foo", "bar"); + } + }; + HasDisplayData hasSubcomponent = new HasDisplayData() { + @Override + public void populateDisplayData(Builder builder) { + builder + .include(subComponent) + .add("foo2", "bar2"); + } + }; + HasDisplayData sameKeyDifferentNamespace = new HasDisplayData() { + @Override + public void populateDisplayData(Builder builder) { + builder.add("foo", "bar"); + } + }; + Matcher<DisplayData> matcher = includes(subComponent); + + assertFalse(matcher.matches(DisplayData.from(sameKeyDifferentNamespace))); + assertThat(DisplayData.from(hasSubcomponent), matcher); + assertThat(DisplayData.from(subComponent), matcher); } private DisplayData createDisplayDataWithItem(final String key, final String value) { http://git-wip-us.apache.org/repos/asf/incubator-beam/blob/014a9a5a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java ---------------------------------------------------------------------- diff --git a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java index 397e102..4d75e26 100644 --- a/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java +++ b/sdks/java/core/src/test/java/com/google/cloud/dataflow/sdk/transforms/display/DisplayDataTest.java @@ -19,6 +19,9 @@ package com.google.cloud.dataflow.sdk.transforms.display; import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasDisplayItem; import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasKey; +import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasType; +import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.hasValue; +import static com.google.cloud.dataflow.sdk.transforms.display.DisplayDataMatchers.includes; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.everyItem; @@ -92,7 +95,7 @@ public class DisplayDataTest { .include(subComponent2) .add("MinSproggles", 200) .withLabel("Mimimum Required Sproggles") - .add("LazerOrientation", "NORTH") + .add("FireLazers", true) .add("TimeBomb", Instant.now().plus(Duration.standardDays(1))) .add("FilterLogic", subComponent1.getClass()) .add("ServiceUrl", "google.com/fizzbang") @@ -127,12 +130,12 @@ public class DisplayDataTest { DisplayData.from(new HasDisplayData() { @Override public void populateDisplayData(DisplayData.Builder builder) { - builder.add("Foo", "bar"); + builder.add("foo", "bar"); } }); assertThat(data.items(), hasSize(1)); - assertThat(data, hasDisplayItem(hasKey("Foo"))); + assertThat(data, hasDisplayItem("foo", "bar")); } @Test @@ -148,7 +151,7 @@ public class DisplayDataTest { Map<DisplayData.Identifier, DisplayData.Item> map = data.asMap(); assertEquals(map.size(), 1); - assertThat(data, hasDisplayItem(hasKey("foo"))); + assertThat(data, hasDisplayItem("foo", "bar")); assertEquals(map.values(), data.items()); } @@ -164,8 +167,8 @@ public class DisplayDataTest { allOf( hasNamespace(Matchers.<Class<?>>is(ConcreteComponent.class)), hasKey("now"), - hasType(is(DisplayData.Type.TIMESTAMP)), - hasValue(is(ISO_FORMATTER.print(value))), + hasType(DisplayData.Type.TIMESTAMP), + hasValue(ISO_FORMATTER.print(value)), hasShortValue(nullValue(String.class)), hasLabel(is("the current instant")), hasUrl(is("http://time.gov")))); @@ -219,14 +222,35 @@ public class DisplayDataTest { } }); - assertThat( - data, - hasDisplayItem( - allOf( - hasKey("foo"), - hasNamespace(Matchers.<Class<?>>is(subComponent.getClass()))))); + assertThat(data, includes(subComponent)); + } + + @Test + public void testIncludesNamespaceOverride() { + final HasDisplayData subComponent = new HasDisplayData() { + @Override + public void populateDisplayData(DisplayData.Builder builder) { + builder.add("foo", "bar"); + } + }; + + final HasDisplayData namespaceOverride = new HasDisplayData(){ + @Override + public void populateDisplayData(Builder builder) { + } + }; + + DisplayData data = DisplayData.from(new HasDisplayData() { + @Override + public void populateDisplayData(DisplayData.Builder builder) { + builder.include(subComponent, namespaceOverride.getClass()); + } + }); + + assertThat(data, includes(subComponent, namespaceOverride.getClass())); } + @Test public void testIdentifierEquality() { new EqualsTester() @@ -239,6 +263,33 @@ public class DisplayDataTest { } @Test + public void testItemEquality() { + HasDisplayData component1 = new HasDisplayData() { + @Override + public void populateDisplayData(Builder builder) { + builder.add("foo", "bar"); + } + }; + HasDisplayData component2 = new HasDisplayData() { + @Override + public void populateDisplayData(Builder builder) { + builder.add("foo", "bar"); + } + }; + + DisplayData component1DisplayData1 = DisplayData.from(component1); + DisplayData component1DisplayData2 = DisplayData.from(component1); + DisplayData component2DisplayData = DisplayData.from(component2); + + new EqualsTester() + .addEqualityGroup( + component1DisplayData1.items().toArray()[0], + component1DisplayData2.items().toArray()[0]) + .addEqualityGroup(component2DisplayData.items().toArray()[0]) + .testEquals(); + } + + @Test public void testAnonymousClassNamespace() { DisplayData data = DisplayData.from( @@ -404,6 +455,7 @@ public class DisplayDataTest { .add("string", "foobar") .add("integer", 123) .add("float", 3.14) + .add("boolean", true) .add("java_class", DisplayDataTest.class) .add("timestamp", Instant.now()) .add("duration", Duration.standardHours(1)); @@ -412,18 +464,19 @@ public class DisplayDataTest { Collection<Item> items = data.items(); assertThat( - items, hasItem(allOf(hasKey("string"), hasType(is(DisplayData.Type.STRING))))); + items, hasItem(allOf(hasKey("string"), hasType(DisplayData.Type.STRING)))); assertThat( - items, hasItem(allOf(hasKey("integer"), hasType(is(DisplayData.Type.INTEGER))))); - assertThat(items, hasItem(allOf(hasKey("float"), hasType(is(DisplayData.Type.FLOAT))))); + items, hasItem(allOf(hasKey("integer"), hasType(DisplayData.Type.INTEGER)))); + assertThat(items, hasItem(allOf(hasKey("float"), hasType(DisplayData.Type.FLOAT)))); + assertThat(items, hasItem(allOf(hasKey("boolean"), hasType(DisplayData.Type.BOOLEAN)))); assertThat( items, - hasItem(allOf(hasKey("java_class"), hasType(is(DisplayData.Type.JAVA_CLASS))))); + hasItem(allOf(hasKey("java_class"), hasType(DisplayData.Type.JAVA_CLASS)))); assertThat( items, - hasItem(allOf(hasKey("timestamp"), hasType(is(DisplayData.Type.TIMESTAMP))))); + hasItem(allOf(hasKey("timestamp"), hasType(DisplayData.Type.TIMESTAMP)))); assertThat( - items, hasItem(allOf(hasKey("duration"), hasType(is(DisplayData.Type.DURATION))))); + items, hasItem(allOf(hasKey("duration"), hasType(DisplayData.Type.DURATION)))); } @Test @@ -438,6 +491,7 @@ public class DisplayDataTest { .add("string", "foobar") .add("integer", 123) .add("float", 3.14) + .add("boolean", true) .add("java_class", DisplayDataTest.class) .add("timestamp", now) .add("duration", oneHour); @@ -445,17 +499,13 @@ public class DisplayDataTest { }; DisplayData data = DisplayData.from(component); - Collection<Item> items = data.items(); - assertThat(items, hasItem(allOf(hasKey("string"), hasValue(is("foobar"))))); - assertThat(items, hasItem(allOf(hasKey("integer"), hasValue(is("123"))))); - assertThat(items, hasItem(allOf(hasKey("float"), hasValue(is("3.14"))))); - assertThat(items, hasItem(allOf(hasKey("java_class"), - hasValue(is(DisplayDataTest.class.getName())), - hasShortValue(is(DisplayDataTest.class.getSimpleName()))))); - assertThat(items, hasItem(allOf(hasKey("timestamp"), - hasValue(is(ISO_FORMATTER.print(now)))))); - assertThat(items, hasItem(allOf(hasKey("duration"), - hasValue(is(Long.toString(oneHour.getMillis())))))); + assertThat(data, hasDisplayItem("string", "foobar")); + assertThat(data, hasDisplayItem("integer", 123)); + assertThat(data, hasDisplayItem("float", 3.14)); + assertThat(data, hasDisplayItem("boolean", true)); + assertThat(data, hasDisplayItem("java_class", DisplayDataTest.class)); + assertThat(data, hasDisplayItem("timestamp", now)); + assertThat(data, hasDisplayItem("duration", oneHour)); } @Test @@ -582,16 +632,6 @@ public class DisplayDataTest { }; } - private static Matcher<DisplayData.Item> hasType(Matcher<DisplayData.Type> typeMatcher) { - return new FeatureMatcher<DisplayData.Item, DisplayData.Type>( - typeMatcher, "display item with type", "type") { - @Override - protected DisplayData.Type featureValueOf(DisplayData.Item actual) { - return actual.getType(); - } - }; - } - private static Matcher<DisplayData.Item> hasLabel(Matcher<String> labelMatcher) { return new FeatureMatcher<DisplayData.Item, String>( labelMatcher, "display item with label", "label") { @@ -612,16 +652,6 @@ public class DisplayDataTest { }; } - private static Matcher<DisplayData.Item> hasValue(Matcher<String> valueMatcher) { - return new FeatureMatcher<DisplayData.Item, String>( - valueMatcher, "display item with value", "value") { - @Override - protected String featureValueOf(DisplayData.Item actual) { - return actual.getValue(); - } - }; - } - private static Matcher<DisplayData.Item> hasShortValue(Matcher<String> valueStringMatcher) { return new FeatureMatcher<DisplayData.Item, String>( valueStringMatcher, "display item with short value", "short value") {