1717 */
1818package com .google .cloud .dataflow .sdk .transforms .display ;
1919
20+ import static org .hamcrest .Matchers .allOf ;
21+
2022import com .google .cloud .dataflow .sdk .transforms .display .DisplayData .Item ;
2123
24+ import com .google .common .collect .Sets ;
25+ import org .hamcrest .CustomTypeSafeMatcher ;
2226import org .hamcrest .Description ;
2327import org .hamcrest .FeatureMatcher ;
2428import org .hamcrest .Matcher ;
2529import org .hamcrest .Matchers ;
2630import org .hamcrest .TypeSafeDiagnosingMatcher ;
31+ import org .joda .time .Duration ;
32+ import org .joda .time .Instant ;
2733
2834import java .util .Collection ;
2935
@@ -43,6 +49,71 @@ public static Matcher<DisplayData> hasDisplayItem() {
4349 return hasDisplayItem (Matchers .any (DisplayData .Item .class ));
4450 }
4551
52+ /**
53+ * Create a matcher that matches if the examined {@link DisplayData} contains an item with the
54+ * specified key and String value.
55+ */
56+ public static Matcher <DisplayData > hasDisplayItem (String key , String value ) {
57+ return hasDisplayItem (key , DisplayData .Type .STRING , value );
58+ }
59+
60+ /**
61+ * Create a matcher that matches if the examined {@link DisplayData} contains an item with the
62+ * specified key and Boolean value.
63+ */
64+ public static Matcher <DisplayData > hasDisplayItem (String key , Boolean value ) {
65+ return hasDisplayItem (key , DisplayData .Type .BOOLEAN , value );
66+ }
67+
68+ /**
69+ * Create a matcher that matches if the examined {@link DisplayData} contains an item with the
70+ * specified key and Duration value.
71+ */
72+ public static Matcher <DisplayData > hasDisplayItem (String key , Duration value ) {
73+ return hasDisplayItem (key , DisplayData .Type .DURATION , value );
74+ }
75+
76+ /**
77+ * Create a matcher that matches if the examined {@link DisplayData} contains an item with the
78+ * specified key and Float value.
79+ */
80+ public static Matcher <DisplayData > hasDisplayItem (String key , double value ) {
81+ return hasDisplayItem (key , DisplayData .Type .FLOAT , value );
82+ }
83+
84+ /**
85+ * Create a matcher that matches if the examined {@link DisplayData} contains an item with the
86+ * specified key and Integer value.
87+ */
88+ public static Matcher <DisplayData > hasDisplayItem (String key , long value ) {
89+ return hasDisplayItem (key , DisplayData .Type .INTEGER , value );
90+ }
91+
92+ /**
93+ * Create a matcher that matches if the examined {@link DisplayData} contains an item with the
94+ * specified key and Class value.
95+ */
96+ public static Matcher <DisplayData > hasDisplayItem (String key , Class <?> value ) {
97+ return hasDisplayItem (key , DisplayData .Type .JAVA_CLASS , value );
98+ }
99+
100+ /**
101+ * Create a matcher that matches if the examined {@link DisplayData} contains an item with the
102+ * specified key and Timestamp value.
103+ */
104+ public static Matcher <DisplayData > hasDisplayItem (String key , Instant value ) {
105+ return hasDisplayItem (key , DisplayData .Type .TIMESTAMP , value );
106+ }
107+
108+ private static Matcher <DisplayData > hasDisplayItem (
109+ String key , DisplayData .Type type , Object value ) {
110+ DisplayData .FormattedItemValue formattedValue = type .format (value );
111+ return hasDisplayItem (allOf (
112+ hasKey (key ),
113+ hasType (type ),
114+ hasValue (formattedValue .getLongValue ())));
115+ }
116+
46117 /**
47118 * Creates a matcher that matches if the examined {@link DisplayData} contains any item
48119 * matching the specified {@code itemMatcher}.
@@ -69,13 +140,93 @@ protected boolean matchesSafely(DisplayData data, Description mismatchDescriptio
69140 Collection <Item > items = data .items ();
70141 boolean isMatch = Matchers .hasItem (itemMatcher ).matches (items );
71142 if (!isMatch ) {
72- mismatchDescription .appendText ("found " + items .size () + " non-matching items" );
143+ mismatchDescription .appendText ("found " + items .size () + " non-matching item(s):\n " );
144+ mismatchDescription .appendValue (data );
73145 }
74146
75147 return isMatch ;
76148 }
77149 }
78150
151+ /**
152+ * Create a matcher that matches if the examined {@link DisplayData} contains all display data
153+ * registered from the specified subcomponent.
154+ */
155+ public static Matcher <DisplayData > includes (final HasDisplayData subComponent ) {
156+ return includes (subComponent , subComponent .getClass ());
157+ }
158+
159+ /**
160+ * Create a matcher that matches if the examined {@link DisplayData} contains all display data
161+ * registered from the specified subcomponent and namespace.
162+ */
163+ public static Matcher <DisplayData > includes (
164+ final HasDisplayData subComponent , final Class <? extends HasDisplayData > namespace ) {
165+ return new CustomTypeSafeMatcher <DisplayData >("includes subcomponent" ) {
166+ @ Override
167+ protected boolean matchesSafely (DisplayData displayData ) {
168+ DisplayData subComponentData = DisplayData .from (subComponent );
169+ if (subComponentData .items ().size () == 0 ) {
170+ throw new UnsupportedOperationException ("subComponent contains no display data; " +
171+ "cannot verify whether it is included" );
172+ }
173+
174+ DisplayDataComparision comparison = checkSubset (displayData , subComponentData , namespace );
175+ return comparison .missingItems .isEmpty ();
176+ }
177+
178+
179+ @ Override
180+ protected void describeMismatchSafely (
181+ DisplayData displayData , Description mismatchDescription ) {
182+ DisplayData subComponentDisplayData = DisplayData .from (subComponent );
183+ DisplayDataComparision comparison = checkSubset (
184+ displayData , subComponentDisplayData , subComponent .getClass ());
185+
186+ mismatchDescription
187+ .appendText ("did not include:\n " )
188+ .appendValue (comparison .missingItems )
189+ .appendText ("\n Non-matching items:\n " )
190+ .appendValue (comparison .unmatchedItems );
191+ }
192+
193+ private DisplayDataComparision checkSubset (
194+ DisplayData displayData , DisplayData included , Class <?> namespace ) {
195+ DisplayDataComparision comparison = new DisplayDataComparision (displayData .items ());
196+ for (Item item : included .items ()) {
197+ Item matchedItem = displayData .asMap ().get (
198+ DisplayData .Identifier .of (namespace , item .getKey ()));
199+
200+ if (matchedItem != null ) {
201+ comparison .matched (matchedItem );
202+ } else {
203+ comparison .missing (item );
204+ }
205+ }
206+
207+ return comparison ;
208+ }
209+
210+ class DisplayDataComparision {
211+ Collection <DisplayData .Item > missingItems ;
212+ Collection <DisplayData .Item > unmatchedItems ;
213+
214+ DisplayDataComparision (Collection <Item > superset ) {
215+ missingItems = Sets .newHashSet ();
216+ unmatchedItems = Sets .newHashSet (superset );
217+ }
218+
219+ void matched (Item supersetItem ) {
220+ unmatchedItems .remove (supersetItem );
221+ }
222+
223+ void missing (Item subsetItem ) {
224+ missingItems .add (subsetItem );
225+ }
226+ }
227+ };
228+ }
229+
79230 /**
80231 * Creates a matcher that matches if the examined {@link DisplayData.Item} contains a key
81232 * with the specified value.
@@ -96,4 +247,32 @@ protected String featureValueOf(DisplayData.Item actual) {
96247 }
97248 };
98249 }
250+
251+ public static Matcher <DisplayData .Item > hasType (DisplayData .Type type ) {
252+ return hasType (Matchers .is (type ));
253+ }
254+
255+ public static Matcher <DisplayData .Item > hasType (Matcher <DisplayData .Type > typeMatcher ) {
256+ return new FeatureMatcher <DisplayData .Item , DisplayData .Type >(
257+ typeMatcher , "with type" , "type" ) {
258+ @ Override
259+ protected DisplayData .Type featureValueOf (DisplayData .Item actual ) {
260+ return actual .getType ();
261+ }
262+ };
263+ }
264+
265+ public static Matcher <DisplayData .Item > hasValue (String value ) {
266+ return hasValue (Matchers .is (value ));
267+ }
268+
269+ public static Matcher <DisplayData .Item > hasValue (Matcher <String > valueMatcher ) {
270+ return new FeatureMatcher <DisplayData .Item , String >(
271+ valueMatcher , "with value" , "value" ) {
272+ @ Override
273+ protected String featureValueOf (DisplayData .Item actual ) {
274+ return actual .getValue ();
275+ }
276+ };
277+ }
99278}
0 commit comments