View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.collections4;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertTrue;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.ObjectInputStream;
31  import java.io.ObjectOutputStream;
32  import java.io.OutputStream;
33  import java.io.Serializable;
34  
35  import org.junit.jupiter.api.Test;
36  
37  /**
38   * Abstract test class for {@link Object} methods and contracts.
39   * <p>
40   * To use, simply extend this class, and implement
41   * the {@link #makeObject()} method.
42   * <p>
43   * If your {@link Object} fails one of these tests by design,
44   * you may still use this base set of cases.  Simply override the
45   * test case (method) your {@link Object} fails.
46   */
47  public abstract class AbstractObjectTest extends BulkTest {
48  
49      /** Current major release for Collections */
50      public static final int COLLECTIONS_MAJOR_VERSION = 4;
51  
52      /**
53       * JUnit constructor.
54       *
55       * @param testName  the test class name
56       */
57      public AbstractObjectTest(final String testName) {
58          super(testName);
59      }
60  
61      protected String getCanonicalEmptyCollectionName(final Object object) {
62          final StringBuilder retval = new StringBuilder();
63          retval.append(TEST_DATA_PATH);
64          String colName = object.getClass().getName();
65          colName = colName.substring(colName.lastIndexOf(".") + 1);
66          retval.append(colName);
67          retval.append(".emptyCollection.version");
68          retval.append(getCompatibilityVersion());
69          retval.append(".obj");
70          return retval.toString();
71      }
72  
73      protected String getCanonicalFullCollectionName(final Object object) {
74          final StringBuilder retval = new StringBuilder();
75          retval.append(TEST_DATA_PATH);
76          String colName = object.getClass().getName();
77          colName = colName.substring(colName.lastIndexOf(".") + 1);
78          retval.append(colName);
79          retval.append(".fullCollection.version");
80          retval.append(getCompatibilityVersion());
81          retval.append(".obj");
82          return retval.toString();
83      }
84  
85      // protected implementation
86      /**
87       * Gets the version of Collections that this object tries to
88       * maintain serialization compatibility with. Defaults to 4, due to
89       * the package change to collections4 introduced in version 4.
90       *
91       * This constant makes it possible for TestMap (and other subclasses,
92       * if necessary) to automatically check SCM for a versionX copy of a
93       * Serialized object, so we can make sure that compatibility is maintained.
94       * See, for example, TestMap.getCanonicalFullMapName(Map map).
95       * Subclasses can override this variable, indicating compatibility
96       * with earlier Collections versions.
97       *
98       * @return The version, or {@code null} if this object shouldn't be
99       * tested for compatibility with previous versions.
100      */
101     public String getCompatibilityVersion() {
102         return "4";
103     }
104 
105     /**
106      * Returns true to indicate that the collection supports equals() comparisons.
107      * This implementation returns true;
108      */
109     public boolean isEqualsCheckable() {
110         return true;
111     }
112 
113     /**
114      * Is serialization testing supported.
115      * Default is true.
116      */
117     public boolean isTestSerialization() {
118         return true;
119     }
120 
121     /**
122      * Implement this method to return the object to test.
123      *
124      * @return the object to test
125      */
126     public abstract Object makeObject();
127 
128     /**
129      * Read a Serialized or Externalized Object from bytes.
130      * Useful for verifying serialization in memory.
131      *
132      * @param b byte array containing a serialized Object
133      * @return Object contained in the bytes
134      * @throws IOException
135      * @throws ClassNotFoundException
136      */
137     protected Object readExternalFormFromBytes(final byte[] b) throws IOException, ClassNotFoundException {
138         final ByteArrayInputStream stream = new ByteArrayInputStream(b);
139         return readExternalFormFromStream(stream);
140     }
141 
142     /**
143      * Reads a Serialized or Externalized Object from disk.
144      * Useful for creating compatibility tests between
145      * different SCM versions of the same class
146      *
147      * @param path path to the serialized Object
148      * @return the Object at the given path
149      * @throws IOException
150      * @throws ClassNotFoundException
151      */
152     protected Object readExternalFormFromDisk(final String path) throws IOException, ClassNotFoundException {
153         try (FileInputStream stream = new FileInputStream(path)) {
154             return readExternalFormFromStream(stream);
155         }
156     }
157 
158     // private implementation
159     private Object readExternalFormFromStream(final InputStream stream) throws IOException, ClassNotFoundException {
160         final ObjectInputStream oStream = new ObjectInputStream(stream);
161         return oStream.readObject();
162     }
163 
164     protected Object serializeDeserialize(final Object obj) throws Exception {
165         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
166         final ObjectOutputStream out = new ObjectOutputStream(buffer);
167         out.writeObject(obj);
168         out.close();
169 
170         final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buffer.toByteArray()));
171         final Object dest = in.readObject();
172         in.close();
173 
174         return dest;
175     }
176 
177     protected boolean skipSerializedCanonicalTests() {
178         return Boolean.getBoolean("org.apache.commons.collections:with-clover");
179     }
180 
181     /**
182      * Override this method if a subclass is testing an object
183      * that cannot serialize an "empty" Collection.
184      * (e.g. Comparators have no contents)
185      *
186      * @return true
187      */
188     public boolean supportsEmptyCollections() {
189         return true;
190     }
191 
192     /**
193      * Override this method if a subclass is testing an object
194      * that cannot serialize a "full" Collection.
195      * (e.g. Comparators have no contents)
196      *
197      * @return true
198      */
199     public boolean supportsFullCollections() {
200         return true;
201     }
202 
203     /**
204      * Tests serialization by comparing against a previously stored version in SCM.
205      * If the test object is serializable, confirm that a canonical form exists.
206      */
207     @Test
208     public void testCanonicalEmptyCollectionExists() {
209         if (supportsEmptyCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) {
210             final Object object = makeObject();
211             if (object instanceof Serializable) {
212                 final String name = getCanonicalEmptyCollectionName(object);
213                 assertTrue(
214                         new File(name).exists(),
215                         "Canonical empty collection (" + name + ") is not in SCM");
216             }
217         }
218     }
219 
220     /**
221      * Tests serialization by comparing against a previously stored version in SCM.
222      * If the test object is serializable, confirm that a canonical form exists.
223      */
224     @Test
225     public void testCanonicalFullCollectionExists() {
226         if (supportsFullCollections() && isTestSerialization() && !skipSerializedCanonicalTests()) {
227             final Object object = makeObject();
228             if (object instanceof Serializable) {
229                 final String name = getCanonicalFullCollectionName(object);
230                 assertTrue(
231                         new File(name).exists(),
232                         "Canonical full collection (" + name + ") is not in SCM");
233             }
234         }
235     }
236 
237     @Test
238     public void testEqualsNull() {
239         final Object obj = makeObject();
240         assertFalse(obj.equals(null)); // make sure this doesn't throw NPE either
241     }
242 
243     @Test
244     public void testObjectEqualsSelf() {
245         final Object obj = makeObject();
246         assertEquals(obj, obj, "A Object should equal itself");
247     }
248 
249     @Test
250     public void testObjectHashCodeEqualsContract() {
251         final Object obj1 = makeObject();
252         if (obj1.equals(obj1)) {
253             assertEquals(
254                     obj1.hashCode(), obj1.hashCode(),
255                     "[1] When two objects are equal, their hashCodes should be also.");
256         }
257         final Object obj2 = makeObject();
258         if (obj1.equals(obj2)) {
259             assertEquals(
260                     obj1.hashCode(), obj2.hashCode(),
261                     "[2] When two objects are equal, their hashCodes should be also.");
262             assertEquals(obj2, obj1, "When obj1.equals(obj2) is true, then obj2.equals(obj1) should also be true");
263         }
264     }
265 
266     @Test
267     public void testObjectHashCodeEqualsSelfHashCode() {
268         final Object obj = makeObject();
269         assertEquals(obj.hashCode(), obj.hashCode(), "hashCode should be repeatable");
270     }
271 
272     @Test
273     public void testSerializeDeserializeThenCompare() throws Exception {
274         final Object obj = makeObject();
275         if (obj instanceof Serializable && isTestSerialization()) {
276             final Object dest = serializeDeserialize(obj);
277             if (isEqualsCheckable()) {
278                 assertEquals(obj, dest, "obj != deserialize(serialize(obj))");
279             }
280         }
281     }
282 
283     /**
284      * Sanity check method, makes sure that any Serializable
285      * class can be serialized and de-serialized in memory,
286      * using the handy makeObject() method
287      *
288      * @throws IOException
289      * @throws ClassNotFoundException
290      */
291     @Test
292     public void testSimpleSerialization() throws Exception {
293         final Object o = makeObject();
294         if (o instanceof Serializable && isTestSerialization()) {
295             final byte[] object = writeExternalFormToBytes((Serializable) o);
296             readExternalFormFromBytes(object);
297         }
298     }
299 
300     /**
301      * Converts a Serializable or Externalizable object to
302      * bytes.  Useful for in-memory tests of serialization
303      *
304      * @param o Object to convert to bytes
305      * @return serialized form of the Object
306      * @throws IOException
307      */
308     protected byte[] writeExternalFormToBytes(final Serializable o) throws IOException {
309         final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
310         writeExternalFormToStream(o, byteStream);
311         return byteStream.toByteArray();
312     }
313 
314     /**
315      * Writes a Serializable or Externalizable object as
316      * a file at the given path.  NOT USEFUL as part
317      * of a unit test; this is just a utility method
318      * for creating disk-based objects in SCM that can become
319      * the basis for compatibility tests using
320      * readExternalFormFromDisk(String path)
321      *
322      * @param o Object to serialize
323      * @param path path to write the serialized Object
324      * @throws IOException
325      */
326     protected void writeExternalFormToDisk(final Serializable o, final String path) throws IOException {
327         try (FileOutputStream fileStream = new FileOutputStream(path)) {
328             writeExternalFormToStream(o, fileStream);
329         }
330     }
331 
332     private void writeExternalFormToStream(final Serializable o, final OutputStream stream) throws IOException {
333         final ObjectOutputStream oStream = new ObjectOutputStream(stream);
334         oStream.writeObject(o);
335     }
336 
337 }