001    // Copyright 2010, 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.ioc.test;
016    
017    import java.lang.reflect.Field;
018    import java.util.ArrayList;
019    import java.util.Arrays;
020    import java.util.List;
021    
022    import org.testng.Assert;
023    
024    /**
025     * Extra assertions on top of the standard set, packaged as a base class for easy referencing in tests. Also,
026     * utilities for instantiation objects and setting and reading private fields of those objects.
027     * <p>
028     * This class was originally in the tapestry-ioc module as was moved to tapestry-test; the package name was not changed
029     * to ensure backwards compatibility.
030     * 
031     * @since 5.2.0
032     */
033    public class TestUtils extends Assert
034    {
035    
036        /**
037         * Invoked from code that should not be reachable. For example, place a call to unreachable() after invoking a
038         * method that is expected to throw an exception.
039         */
040        public static void unreachable()
041        {
042            fail("This code should not be reachable.");
043        }
044    
045        /**
046         * Asserts that the message property of the throwable contains each of the provided substrings.
047         * 
048         * @param t
049         *            throwable to check
050         * @param substrings
051         *            some number of expected substrings
052         */
053        public static void assertMessageContains(Throwable t, String... substrings)
054        {
055            String message = t.getMessage();
056    
057            for (String substring : substrings)
058                assertTrue(message.contains(substring), String.format("String '%s' not found in '%s'.", substring, message));
059        }
060    
061        /**
062         * Compares two lists for equality; first all the elements are individually compared for equality (if the lists are
063         * of unequal length, only elements up to the shorter length are compared). Then the length of the lists are
064         * compared. This generally gives
065         * 
066         * @param <T>
067         *            type of objects to compare
068         * @param actual
069         *            actual values to check
070         * @param expected
071         *            expected values
072         */
073        public static <T> void assertListsEquals(List<T> actual, List<T> expected)
074        {
075            int count = Math.min(actual.size(), expected.size());
076    
077            try
078            {
079                for (int i = 0; i < count; i++)
080                {
081                    assertEquals(actual.get(i), expected.get(i), String.format("Element #%d.", i));
082                }
083    
084                assertEquals(actual.size(), expected.size(), "List size.");
085            }
086            catch (AssertionError ae)
087            {
088                showLists(actual, expected);
089    
090                throw ae;
091            }
092        }
093    
094        protected static <T> void showLists(List<T> actual, List<T> expected)
095        {
096            List<String> actualStrings = toStrings(actual);
097            List<String> expectedStrings = toStrings(expected);
098    
099            String format = String
100                    .format("%%3d: [%%-%ds] [%%-%ds]\n", maxLength(actualStrings), maxLength(expectedStrings));
101    
102            int count = Math.max(actual.size(), expected.size());
103    
104            System.out.flush();
105            System.err.flush();
106    
107            System.err.println("List results differ (actual  vs. expected):");
108    
109            for (int i = 0; i < count; i++)
110            {
111                System.err.printf(format, i, get(actualStrings, i), get(expectedStrings, i));
112            }
113        }
114    
115        private static String get(List<String> list, int index)
116        {
117            if (index < list.size())
118                return list.get(index);
119    
120            return "";
121        }
122    
123        private static int maxLength(List<String> list)
124        {
125            int result = 0;
126    
127            for (String s : list)
128            {
129                result = Math.max(result, s.length());
130            }
131    
132            return result;
133        }
134    
135        private static <T> List<String> toStrings(List<T> list)
136        {
137            List<String> result = new ArrayList<String>();
138    
139            for (T t : list)
140            {
141                result.add(String.valueOf(t));
142            }
143    
144            return result;
145        }
146    
147        /**
148         * Convenience for {@link #assertListsEquals(List, List)}.
149         * 
150         * @param <T>
151         *            type of objects to compare
152         * @param actual
153         *            actual values to check
154         * @param expected
155         *            expected values
156         */
157        public static <T> void assertListsEquals(List<T> actual, T... expected)
158        {
159            assertListsEquals(actual, Arrays.asList(expected));
160        }
161    
162        /**
163         * Convenience for {@link #assertListsEquals(List, List)}.
164         * 
165         * @param <T>
166         *            type of objects to compare
167         * @param actual
168         *            actual values to check
169         * @param expected
170         *            expected values
171         */
172        public static <T> void assertArraysEqual(T[] actual, T... expected)
173        {
174            assertListsEquals(Arrays.asList(actual), expected);
175        }
176    
177        /**
178         * Initializes private fields (via reflection).
179         * 
180         * @param object
181         *            object to be updated
182         * @param fieldValues
183         *            string field names and corresponding field values
184         * @return the object
185         */
186        public static <T> T set(T object, Object... fieldValues)
187        {
188            assert object != null;
189            Class objectClass = object.getClass();
190    
191            for (int i = 0; i < fieldValues.length; i += 2)
192            {
193                String fieldName = (String) fieldValues[i];
194                Object fieldValue = fieldValues[i + 1];
195    
196                try
197                {
198                    Field field = findField(objectClass, fieldName);
199    
200                    field.setAccessible(true);
201    
202                    field.set(object, fieldValue);
203                }
204                catch (Exception ex)
205                {
206                    throw new RuntimeException(String.format("Unable to set field '%s' of %s to %s: %s", fieldName, object,
207                            fieldValue, toMessage(ex)), ex);
208                }
209            }
210    
211            return object;
212        }
213    
214        /**
215         * Reads the content of a private field.
216         * 
217         * @param object
218         *            to read the private field from
219         * @param fieldName
220         *            name of field to read
221         * @return value stored in the field
222         * @since 5.1.0.5
223         */
224        public static Object get(Object object, String fieldName)
225        {
226            assert object != null;
227    
228            try
229            {
230                Field field = findField(object.getClass(), fieldName);
231    
232                field.setAccessible(true);
233    
234                return field.get(object);
235            }
236            catch (Exception ex)
237            {
238                throw new RuntimeException(String.format("Unable to read field '%s' of %s: %s", fieldName, object,
239                        toMessage(ex)), ex);
240            }
241        }
242    
243        private static String toMessage(Throwable exception)
244        {
245            String message = exception.getMessage();
246    
247            if (message != null)
248                return message;
249    
250            return exception.getClass().getName();
251        }
252    
253        private static Field findField(Class objectClass, String fieldName)
254        {
255    
256            Class cursor = objectClass;
257    
258            while (cursor != null)
259            {
260                try
261                {
262                    return cursor.getDeclaredField(fieldName);
263                }
264                catch (NoSuchFieldException ex)
265                {
266                    // Ignore.
267                }
268    
269                cursor = cursor.getSuperclass();
270            }
271    
272            throw new RuntimeException(String.format("Class %s does not contain a field named '%s'.",
273                    objectClass.getName(), fieldName));
274        }
275    
276        /**
277         * Creates a new instance of the object using its default constructor, and initializes it (via
278         * {@link #set(Object, Object[])}).
279         * 
280         * @param objectType
281         *            typeof object to instantiate
282         * @param fieldValues
283         *            string field names and corresponding field values
284         * @return the initialized instance
285         */
286        public static <T> T create(Class<T> objectType, Object... fieldValues)
287        {
288            T result = null;
289    
290            try
291            {
292                result = objectType.newInstance();
293            }
294            catch (Exception ex)
295            {
296                throw new RuntimeException(String.format("Unable to instantiate instance of %s: %s", objectType.getName(),
297                        toMessage(ex)), ex);
298            }
299    
300            return set(result, fieldValues);
301        }
302    
303    }