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
015package org.apache.tapestry5.ioc.test;
016
017import java.lang.reflect.Field;
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.List;
021
022import 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 */
033public 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}