1/*
2 * Copyright 2010 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package com.google.android.testing.mocking;
17
18import javassist.CannotCompileException;
19import javassist.ClassPool;
20import javassist.CtClass;
21import javassist.CtMethod;
22import javassist.NotFoundException;
23
24import junit.framework.TestCase;
25
26import java.io.ByteArrayInputStream;
27import java.io.IOException;
28import java.lang.reflect.Method;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.HashMap;
32import java.util.List;
33import java.util.Map;
34
35
36/**
37 * Various tests that verify that different types of Classes are handled
38 * correctly.
39 *
40 * @author swoodward@google.com (Stephen Woodward)
41 */
42public class ClassTypeTests extends TestCase {
43  private AndroidMockGenerator androidMockGenerator = new AndroidMockGenerator();
44
45  private AndroidMockGenerator getAndroidMockGenerator() {
46    return androidMockGenerator;
47  }
48
49  private void assertAllMethodNames(List<String> expectedNames,
50      Map<String, List<String>> expectedMethods, List<GeneratedClassFile> classes)
51      throws IOException {
52    for (GeneratedClassFile clazz : classes) {
53      assertTrue(expectedNames.contains(clazz.getClassName()));
54      assertUnorderedContentsSame(expectedMethods.get(clazz.getClassName()), getMethodNames(clazz));
55    }
56  }
57
58  private <T> void assertUnorderedContentsSame(Iterable<T> expected, Iterable<T> actual) {
59    List<T> missingItems = new ArrayList<T>();
60    List<T> extraItems = new ArrayList<T>();
61    for (T item : expected) {
62      missingItems.add(item);
63    }
64    for (T item : actual) {
65      missingItems.remove(item);
66      extraItems.add(item);
67    }
68    for (T item : expected) {
69      extraItems.remove(item);
70    }
71    if (missingItems.size() + extraItems.size() != 0) {
72      String errorMessage =
73          "Contents were different. Missing: " + Arrays.toString(missingItems.toArray())
74              + " Extra: " + Arrays.toString(extraItems.toArray());
75      fail(errorMessage);
76    }
77  }
78
79  private List<String> getExpectedNames(Class<?> clazz) {
80    return new ArrayList<String>(Arrays.asList(new String[] {
81        "genmocks." + clazz.getCanonicalName() + "DelegateInterface",
82        "genmocks." + clazz.getCanonicalName() + "DelegateSubclass"}));
83  }
84
85  private Iterable<String> getMethodNames(GeneratedClassFile clazz) throws IOException {
86    ByteArrayInputStream classInputStream = new ByteArrayInputStream(clazz.getContents());
87    CtClass ctClass;
88    try {
89      ctClass = ClassPool.getDefault().getCtClass(clazz.getClassName());
90      if (ctClass.isFrozen()) {
91        ctClass.defrost();
92      }
93    } catch (NotFoundException e) {
94      // That's ok, we're just defrosting any classes that affect us that were created
95      // by other tests.  NotFoundException implies the class is not frozen.
96    }
97    ctClass = ClassPool.getDefault().makeClass(classInputStream);
98    return getMethodNames(ctClass.getDeclaredMethods());
99  }
100
101  private List<String> getMethodNames(CtMethod[] methods) {
102    List<String> methodNames = new ArrayList<String>();
103    for (CtMethod method : methods) {
104      methodNames.add(method.getName());
105    }
106    return methodNames;
107  }
108
109  private List<String> getMethodNames(Method[] methods, String[] exclusions) {
110    List<String> methodNames = new ArrayList<String>();
111    for (Method method : methods) {
112      if (!Arrays.asList(exclusions).contains(method.getName())) {
113        methodNames.add(method.getName());
114      }
115    }
116    return methodNames;
117  }
118
119  private Map<String, List<String>> getExpectedMethodsMap(List<String> expectedNames,
120      Class<?> clazz) {
121    return getExpectedMethodsMap(expectedNames, clazz, new String[0]);
122  }
123
124  private Map<String, List<String>> getExpectedMethodsMap(List<String> expectedNames,
125      Class<?> clazz, String[] exclusions) {
126    Map<String, List<String>> expectedMethods = new HashMap<String, List<String>>();
127    expectedMethods.put(expectedNames.get(0), new ArrayList<String>(Arrays.asList(new String[] {
128        "finalize", "clone"})));
129    expectedMethods.put(expectedNames.get(1), new ArrayList<String>(Arrays.asList(new String[] {
130        "finalize", "clone", "setDelegate___AndroidMock", "getDelegate___AndroidMock"})));
131    expectedMethods.get(expectedNames.get(0)).addAll(
132        getMethodNames(clazz.getDeclaredMethods(), exclusions));
133    expectedMethods.get(expectedNames.get(1)).addAll(
134        getMethodNames(clazz.getDeclaredMethods(), exclusions));
135    return expectedMethods;
136  }
137
138  public void testClassIsDuplicate() throws ClassNotFoundException, IOException,
139      CannotCompileException {
140    List<GeneratedClassFile> classList =
141        getAndroidMockGenerator().createMocksForClass(Object.class);
142    List<GeneratedClassFile> secondClassList =
143        getAndroidMockGenerator().createMocksForClass(Object.class);
144    assertEquals(classList, secondClassList);
145  }
146
147  public void testClassHasDelegateMethods() throws ClassNotFoundException, IOException,
148      CannotCompileException {
149    List<String> expectedNames = getExpectedNames(ClassHasDelegateMethods.class);
150    Map<String, List<String>> expectedMethods =
151        getExpectedMethodsMap(expectedNames, ClassHasDelegateMethods.class,
152            new String[] {"getDelegate___AndroidMock"});
153    // This use case doesn't fit our util in any nice way, so just tweak it.
154    expectedMethods.get(
155        "genmocks.com.google.android.testing.mocking.ClassHasDelegateMethodsDelegateInterface")
156        .add("getDelegate___AndroidMock");
157
158    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
159    List<GeneratedClassFile> classes =
160        mockGenerator.createMocksForClass(ClassHasDelegateMethods.class);
161    assertEquals(2, classes.size());
162    assertAllMethodNames(expectedNames, expectedMethods, classes);
163  }
164
165  public void testClassHasFinalMethods() throws ClassNotFoundException, IOException,
166      CannotCompileException {
167    List<String> expectedNames = getExpectedNames(ClassHasFinalMethods.class);
168    Map<String, List<String>> expectedMethods =
169        getExpectedMethodsMap(expectedNames, ClassHasFinalMethods.class, new String[] {"foo",
170            "foobar"});
171
172    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
173    List<GeneratedClassFile> classes =
174        mockGenerator.createMocksForClass(ClassHasFinalMethods.class);
175    assertEquals(2, classes.size());
176    assertAllMethodNames(expectedNames, expectedMethods, classes);
177  }
178
179  public void testClassHasNoDefaultConstructor() throws ClassNotFoundException, IOException,
180      CannotCompileException {
181    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
182    List<GeneratedClassFile> classes =
183        mockGenerator.createMocksForClass(ClassHasNoDefaultConstructor.class);
184    assertEquals(2, classes.size());
185  }
186
187  public void testClassHasNoPublicConstructors() throws ClassNotFoundException, IOException,
188      CannotCompileException {
189    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
190    List<GeneratedClassFile> classes =
191        mockGenerator.createMocksForClass(ClassHasNoPublicConstructors.class);
192    assertEquals(0, classes.size());
193  }
194
195  public void testClassHasOverloadedMethods() throws ClassNotFoundException, IOException,
196      CannotCompileException {
197    List<String> expectedNames = getExpectedNames(ClassHasOverloadedMethods.class);
198    Map<String, List<String>> expectedMethods =
199        getExpectedMethodsMap(expectedNames, ClassHasOverloadedMethods.class);
200
201    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
202    List<GeneratedClassFile> classes =
203        mockGenerator.createMocksForClass(ClassHasOverloadedMethods.class);
204    assertEquals(2, classes.size());
205    assertAllMethodNames(expectedNames, expectedMethods, classes);
206  }
207
208  public void testClassHasStaticMethods() throws ClassNotFoundException, IOException,
209      CannotCompileException {
210    List<String> expectedNames = getExpectedNames(ClassHasStaticMethods.class);
211    Map<String, List<String>> expectedMethods =
212        getExpectedMethodsMap(expectedNames, ClassHasStaticMethods.class,
213            new String[] {"staticFoo"});
214
215    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
216    List<GeneratedClassFile> classes =
217        mockGenerator.createMocksForClass(ClassHasStaticMethods.class);
218    assertEquals(2, classes.size());
219    assertAllMethodNames(expectedNames, expectedMethods, classes);
220  }
221
222  public void testClassIsAnnotation() throws ClassNotFoundException, IOException,
223      CannotCompileException {
224    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
225    List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsAnnotation.class);
226    assertEquals(0, classes.size());
227  }
228
229  public void testClassIsEnum() throws ClassNotFoundException, IOException, CannotCompileException {
230    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
231    List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsEnum.class);
232    assertEquals(0, classes.size());
233  }
234
235  public void testClassIsFinal() throws ClassNotFoundException, IOException,
236      CannotCompileException {
237    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
238    List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsFinal.class);
239    assertEquals(0, classes.size());
240  }
241
242  public void testClassIsInterface() throws ClassNotFoundException, IOException,
243      CannotCompileException {
244    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
245    List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(ClassIsInterface.class);
246    assertEquals(0, classes.size());
247  }
248
249  public void testClassIsArray() throws ClassNotFoundException, IOException,
250      CannotCompileException {
251    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
252    List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Object[].class);
253    assertEquals(0, classes.size());
254  }
255
256  public void testClassIsNormal() throws ClassNotFoundException, IOException,
257      CannotCompileException {
258    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
259    List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Object.class);
260    assertEquals(2, classes.size());
261  }
262
263  public void testClassIsPrimitive() throws ClassNotFoundException, IOException,
264      CannotCompileException {
265    AndroidMockGenerator mockGenerator = getAndroidMockGenerator();
266    List<GeneratedClassFile> classes = mockGenerator.createMocksForClass(Integer.TYPE);
267    assertEquals(0, classes.size());
268  }
269}
270