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