View Javadoc

1   /*
2    * Copyright 2008-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.util.test.theories;
17  
18  import static org.hamcrest.CoreMatchers.equalTo;
19  import static org.hamcrest.CoreMatchers.is;
20  import static org.hamcrest.CoreMatchers.not;
21  import static org.junit.Assert.assertThat;
22  import static org.junit.Assume.assumeThat;
23  
24  import org.junit.Assert;
25  import org.junit.experimental.theories.Theories;
26  import org.junit.experimental.theories.Theory;
27  import org.junit.runner.RunWith;
28  
29  /* CHECKSTYLE:OFF */
30  /**
31   * Tests theory on comparable instances.
32   *
33   * @param <T> the type of the comparable to test here.
34   * @author <a href="mailto:robert.reiner@smartics.de">Robert Reiner</a>
35   * @version $Revision:591 $
36   */
37  @RunWith(Theories.class)
38  public abstract class CompareToTheory<T extends Comparable<T>> // NOPMD
39  /* CHECKSTYLE:ON */
40  {
41    // ********************************* Fields *********************************
42  
43    // --- constants ------------------------------------------------------------
44  
45    // --- members --------------------------------------------------------------
46  
47    // ****************************** Inner Classes *****************************
48  
49    // ********************************* Methods ********************************
50  
51    // --- prepare --------------------------------------------------------------
52  
53    // --- helper ---------------------------------------------------------------
54  
55    private static void assumeThatValueIsNotNull(final Object uut)
56    {
57      assumeThat(uut, is(not(equalTo(null))));
58    }
59  
60    // CHECKSTYLE:OFF
61    /**
62     * Determines whether or not the theory should check the
63     * {@link Comparable#compareTo(Object)} is consistent with
64     * {@link Object#equals(Object)}.
65     * <p>
66     * Override this method by your subclass.
67     * </p>
68     *
69     * @return <code>true</code> per default.
70     */
71    protected boolean checkConsistentWithEquals()
72    {
73      return true;
74    }
75  
76    // CHECKSTYLE:ON
77  
78    private static <T extends Comparable<T>> String thrownMessage(
79        final String prefix, final TestAtom<T> atom1, final TestAtom<T> atom2)
80    {
81      final StringBuilder buffer = new StringBuilder(64);
82      buffer.append(prefix).append(" did not throw an exception, while ");
83      appendMessage(buffer, atom1.label, atom1.e);
84      buffer.append(" and");
85      appendMessage(buffer, atom2.label, atom2.e);
86      return buffer.toString();
87    }
88  
89    private static void appendMessage(final StringBuilder buffer,
90        final String label, final Exception thrownException)
91    {
92      buffer.append(label).append(" did ")
93          .append(thrownException == null ? "not " : "");
94    }
95  
96    private static boolean noExceptionThrown(final TestAtom<?>... atoms)
97    {
98      for (final TestAtom<?> e : atoms)
99      {
100       if (e.isExceptionThrown())
101       {
102         return false;
103       }
104     }
105     return true;
106   }
107 
108   // --- tests ----------------------------------------------------------------
109 
110   /**
111    * Executes the test and stores the value, be it an exception or the
112    * calculated value.
113    */
114   private static final class TestAtom<T extends Comparable<T>>
115   {
116     /**
117      * A label for reporting failures.
118      */
119     private final String label;
120 
121     /**
122      * The result comparing the UUTs.
123      */
124     private int value;
125 
126     /**
127      * The exception raised (if any) during the comparing of the UUTs.
128      */
129     private Exception e;
130 
131     private TestAtom(final String label, final T uutX, final T uutY)
132     {
133       this.label = label;
134       try
135       {
136         this.value = uutX.compareTo(uutY);
137       }
138       catch (final Exception e)
139       {
140         this.e = e;
141       }
142     }
143 
144     private boolean isExceptionThrown()
145     {
146       return e != null;
147     }
148   }
149 
150   // CHECKSTYLE:OFF
151   /**
152    * Checks the symmetric property of the {@link Comparable#compareTo(Object)}
153    * method.
154    * <p>
155    * <blockquote>
156    * <p>
157    * The implementor must ensure <tt>sgn(x.compareTo(y)) ==
158    * -sgn(y.compareTo(x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This implies
159    * that <tt>x.compareTo(y)</tt> must throw an exception iff
160    * <tt>y.compareTo(x)</tt> throws an exception.)
161    * </p>
162    * </blockquote>
163    *
164    * @param uutX the unit under test to test for symmetry.
165    * @param uutY the unit under test to test for symmetry.
166    * @see Comparable#compareTo(Object)
167    */
168   @Theory
169   public void compareToIsSymmetric(final T uutX, final T uutY)
170   // CHECKSTYLE:ON
171   {
172     assumeThatValueIsNotNull(uutX);
173     assumeThatValueIsNotNull(uutY);
174 
175     final TestAtom<T> xToY = new TestAtom<T>("X", uutX, uutY);
176     final TestAtom<T> yToX = new TestAtom<T>("Y", uutY, uutX);
177 
178     if (noExceptionThrown(xToY, yToX))
179     {
180       assertThat(xToY.value == -yToX.value, is(true));
181     }
182     else if (xToY.e != null)
183     {
184       assertThat("Only X threw an exception.", yToX.e, is(not(equalTo(null))));
185     }
186     else
187     {
188       Assert.fail("Only Y threw an exception.");
189     }
190   }
191 
192   /**
193    * Checks the transitive property of the {@link Comparable#compareTo(Object)}
194    * method.
195    * <p>
196    * <blockquote>
197    * <p>
198    * The implementor must also ensure that the relation is transitive:
199    * <tt>(x.compareTo(y)&gt;0 &amp;&amp; y.compareTo(z)&gt;0)</tt> implies
200    * <tt>x.compareTo(z)&gt;0</tt>.
201    * </p>
202    * <p>
203    * <p>
204    * Finally, the implementor must ensure that <tt>x.compareTo(y)==0</tt>
205    * implies that <tt>sgn(x.compareTo(z)) == sgn(y.compareTo(z))</tt>, for all
206    * <tt>z</tt>.
207    * </p>
208    * </blockquote>
209    *
210    * @param uutX the unit under test to test for transitivity.
211    * @param uutY the unit under test to test for transitivity.
212    * @param uutZ the unit under test to test for transitivity.
213    * @see Comparable#compareTo(Object)
214    */
215   @Theory
216   public final void compareToIsTransitive(final T uutX, final T uutY,
217       final T uutZ)
218   {
219     assumeThatValueIsNotNull(uutX);
220     assumeThatValueIsNotNull(uutY);
221 
222     final TestAtom<T> xToY = new TestAtom<T>("X", uutX, uutY);
223     final TestAtom<T> yToZ = new TestAtom<T>("Y", uutY, uutZ);
224     final TestAtom<T> xToZ = new TestAtom<T>("Z", uutX, uutZ);
225 
226     if (noExceptionThrown(xToY, yToZ, xToZ))
227     {
228       assumeThat(xToY.value > 0 && yToZ.value > 0, is(equalTo(true)));
229       assertThat(xToZ.value > 0, is(equalTo(true)));
230     }
231     else
232     {
233       assertThat(thrownMessage("X", yToZ, xToZ), xToY, is(not(equalTo(null))));
234       assertThat(thrownMessage("Y", xToY, xToZ), yToZ, is(not(equalTo(null))));
235       assertThat(thrownMessage("Z", xToY, yToZ), xToZ, is(not(equalTo(null))));
236     }
237   }
238 
239   /**
240    * Checks the optional consistent to equals property of the
241    * {@link Comparable#compareTo(Object)} method.
242    * <p>
243    * <blockquote>
244    * <p>
245    * It is strongly recommended, but <i>not</i> strictly required that
246    * <tt>(x.compareTo(y)==0) == (x.equals(y))</tt>. Generally speaking, any
247    * class that implements the <tt>Comparable</tt> interface and violates this
248    * condition should clearly indicate this fact. The recommended language is
249    * "Note: this class has a natural ordering that is inconsistent with equals."
250    * </p>
251    * </blockquote>
252    *
253    * @param uutX the unit under test to test for consistency with equals.
254    * @param uutY the unit under test to test for consistency with equals.
255    * @see Comparable#compareTo(Object)
256    */
257   @Theory
258   public final void compareToIsConsistentToEquals(final T uutX, final T uutY)
259   {
260     if (checkConsistentWithEquals())
261     {
262       assumeThatValueIsNotNull(uutX);
263 
264       final TestAtom<T> xToY = new TestAtom<T>("X", uutX, uutY);
265 
266       assumeThat(xToY.isExceptionThrown(), is(equalTo(false)));
267       assumeThat(xToY.value, is(equalTo(0)));
268 
269       assertThat(uutX.equals(uutY), is(equalTo(true)));
270     }
271   }
272 }