1package com.xtremelabs.robolectric.bytecode;
2
3import com.xtremelabs.robolectric.Robolectric;
4import com.xtremelabs.robolectric.WithoutTestDefaultsRunner;
5import com.xtremelabs.robolectric.annotation.EnableStrictI18n;
6import com.xtremelabs.robolectric.internal.Implementation;
7import com.xtremelabs.robolectric.internal.Implements;
8import com.xtremelabs.robolectric.internal.Instrument;
9import com.xtremelabs.robolectric.internal.RealObject;
10import org.junit.Before;
11import org.junit.Ignore;
12import org.junit.Test;
13import org.junit.runner.RunWith;
14
15import java.io.IOException;
16import java.io.PrintWriter;
17import java.io.StringWriter;
18
19import static org.hamcrest.CoreMatchers.instanceOf;
20import static org.hamcrest.CoreMatchers.not;
21import static org.hamcrest.core.StringContains.containsString;
22import static org.junit.Assert.*;
23
24@RunWith(WithoutTestDefaultsRunner.class)
25public class ShadowWranglerTest {
26    private String name;
27
28    @Before
29    public void setUp() throws Exception {
30        name = "context";
31    }
32
33    @Test
34    public void testConstructorInvocation_WithDefaultConstructorAndNoConstructorDelegateOnShadowClass() throws Exception {
35        Robolectric.bindShadowClass(ShadowFoo_WithDefaultConstructorAndNoConstructorDelegate.class);
36
37        Foo foo = new Foo(name);
38        assertEquals(ShadowFoo_WithDefaultConstructorAndNoConstructorDelegate.class, Robolectric.shadowOf_(foo).getClass());
39    }
40
41    @Test
42    public void testConstructorInvocation() throws Exception {
43        Robolectric.bindShadowClass(ShadowFoo.class);
44
45        Foo foo = new Foo(name);
46        assertSame(name, shadowOf(foo).name);
47        assertSame(foo, shadowOf(foo).realFooCtor);
48    }
49
50    @Test
51    public void testRealObjectAnnotatedFieldsAreSetBeforeConstructorIsCalled() throws Exception {
52        Robolectric.bindShadowClass(ShadowFoo.class);
53
54        Foo foo = new Foo(name);
55        assertSame(name, shadowOf(foo).name);
56        assertSame(foo, shadowOf(foo).realFooField);
57
58        assertSame(foo, shadowOf(foo).realFooInConstructor);
59        assertSame(foo, shadowOf(foo).realFooInParentConstructor);
60    }
61
62    @Test
63    public void testMethodDelegation() throws Exception {
64        Robolectric.bindShadowClass(ShadowFoo.class);
65
66        Foo foo = new Foo(name);
67        assertSame(name, foo.getName());
68    }
69
70    @Test
71    public void testEqualsMethodDelegation() throws Exception {
72        Robolectric.bindShadowClass(WithEquals.class);
73
74        Foo foo1 = new Foo(name);
75        Foo foo2 = new Foo(name);
76        assertEquals(foo1, foo2);
77    }
78
79    @Test
80    public void testHashCodeMethodDelegation() throws Exception {
81        Robolectric.bindShadowClass(WithEquals.class);
82
83        Foo foo = new Foo(name);
84        assertEquals(42, foo.hashCode());
85    }
86
87    @Test
88    public void testToStringMethodDelegation() throws Exception {
89        Robolectric.bindShadowClass(WithToString.class);
90
91        Foo foo = new Foo(name);
92        assertEquals("the expected string", foo.toString());
93    }
94
95    @Test
96    public void testShadowSelectionSearchesSuperclasses() throws Exception {
97        Robolectric.bindShadowClass(ShadowFoo.class);
98
99        TextFoo textFoo = new TextFoo(name);
100        assertEquals(ShadowFoo.class, Robolectric.shadowOf_(textFoo).getClass());
101    }
102
103    @Test
104    public void shouldUseMostSpecificShadow() throws Exception {
105        Robolectric.bindShadowClass(ShadowFoo.class);
106        Robolectric.bindShadowClass(ShadowTextFoo.class);
107
108        TextFoo textFoo = new TextFoo(name);
109        assertThat(shadowOf(textFoo), instanceOf(ShadowTextFoo.class));
110    }
111
112    @Test
113    public void testPrimitiveArrays() throws Exception {
114        Class<?> objArrayClass = ShadowWrangler.loadClass("java.lang.Object[]", getClass().getClassLoader());
115        assertTrue(objArrayClass.isArray());
116        assertEquals(Object.class, objArrayClass.getComponentType());
117
118        Class<?> intArrayClass = ShadowWrangler.loadClass("int[]", getClass().getClassLoader());
119        assertTrue(intArrayClass.isArray());
120        assertEquals(Integer.TYPE, intArrayClass.getComponentType());
121    }
122
123    @Test
124    public void shouldRemoveNoiseFromStackTraces() throws Exception {
125        Robolectric.bindShadowClass(ExceptionThrowingShadowFoo.class);
126        Foo foo = new Foo(null);
127
128        Exception e = null;
129        try {
130            foo.getName();
131        } catch (Exception e1) {
132            e = e1;
133        }
134
135        assertNotNull(e);
136        assertEquals(IOException.class, e.getClass());
137        assertEquals("fake exception", e.getMessage());
138        StringWriter stringWriter = new StringWriter();
139        e.printStackTrace(new PrintWriter(stringWriter));
140        String stackTrace = stringWriter.getBuffer().toString();
141
142        assertThat(stackTrace, containsString("fake exception"));
143        assertThat(stackTrace, containsString(ExceptionThrowingShadowFoo.class.getName() + ".getName("));
144        assertThat(stackTrace, containsString(Foo.class.getName() + ".getName("));
145        assertThat(stackTrace, containsString(ShadowWranglerTest.class.getName() + ".shouldRemoveNoiseFromStackTraces"));
146
147        assertThat(stackTrace, not(containsString("sun.reflect")));
148        assertThat(stackTrace, not(containsString("java.lang.reflect")));
149        assertThat(stackTrace, not(containsString(ShadowWrangler.class.getName() + ".")));
150        assertThat(stackTrace, not(containsString(RobolectricInternals.class.getName() + ".")));
151    }
152
153    @Test(expected=RuntimeException.class)
154    @EnableStrictI18n
155    public void shouldThrowExceptionOnI18nStrictMode() {
156    	Robolectric.bindShadowClass(ShadowFooI18n.class);
157    	Foo foo = new Foo(null);
158    	foo.getName();
159    }
160
161    private ShadowFoo shadowOf(Foo foo) {
162        return (ShadowFoo) Robolectric.shadowOf_(foo);
163    }
164
165    private ShadowTextFoo shadowOf(TextFoo foo) {
166        return (ShadowTextFoo) Robolectric.shadowOf_(foo);
167    }
168
169    @Implements(Foo.class)
170    public static class WithEquals {
171        @Override
172        public boolean equals(Object o) {
173            return true;
174        }
175
176
177        @Override
178        public int hashCode() {
179            return 42;
180        }
181
182    }
183
184    @Implements(Foo.class)
185    public static class WithToString {
186        @Override
187        public String toString() {
188            return "the expected string";
189        }
190    }
191
192    @Implements(TextFoo.class)
193    public static class ShadowTextFoo {
194    }
195
196    @Instrument
197    public static class TextFoo extends Foo {
198        public TextFoo(String s) {
199            super(s);
200        }
201    }
202
203    @Implements(Foo.class)
204    public static class ShadowFooI18n {
205    	String name;
206
207        public void __constructor__(String name) {
208           this.name = name;
209        }
210
211    	@Implementation(i18nSafe=false)
212    	public String getName() {
213    		return name;
214    	}
215    }
216
217    @Implements(Foo.class)
218    public static class ShadowFooParent {
219        @RealObject
220        private Foo realFoo;
221        Foo realFooInParentConstructor;
222
223        public void __constructor__(String name) {
224            realFooInParentConstructor = realFoo;
225        }
226    }
227
228    @Implements(Foo.class)
229    public static class ShadowFoo_WithDefaultConstructorAndNoConstructorDelegate {
230    }
231
232    @Implements(Foo.class)
233    public static class ExceptionThrowingShadowFoo {
234        @SuppressWarnings({"UnusedDeclaration"})
235        public String getName() throws IOException {
236            throw new IOException("fake exception");
237        }
238    }
239}
240