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