1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package libcore.java.lang;
18
19import java.io.PrintWriter;
20import java.io.StringWriter;
21import java.util.Arrays;
22import junit.framework.TestCase;
23import libcore.util.SerializationTester;
24
25public class ThrowableTest extends TestCase {
26    private static class NoStackTraceException extends Exception {
27        @Override
28        public synchronized Throwable fillInStackTrace() {
29            return null;
30        }
31    }
32    public void testNullStackTrace() {
33        try {
34            throw new NoStackTraceException();
35        } catch (NoStackTraceException ex) {
36            // We used to throw NullPointerException when printing an exception with no stack trace.
37            ex.printStackTrace(new PrintWriter(new StringWriter()));
38        }
39    }
40
41    public void testNonWritableStackTrace() {
42        try {
43            // The 4th argument, writableStackTrace, is false...
44            throw new SuppressionsThrowable("hi", null, true, false);
45        } catch (Throwable th) {
46            assertEquals("hi", th.getMessage());
47
48            // We see an empty stack trace.
49            assertEquals(0, th.getStackTrace().length);
50
51            // setStackTrace is a no-op.
52            th.setStackTrace(new StackTraceElement[] { new StackTraceElement("c", "m", "f", -2) });
53            assertEquals(0, th.getStackTrace().length);
54
55            // fillInStackTrace is a no-op.
56            th.fillInStackTrace();
57            assertEquals(0, th.getStackTrace().length);
58
59            // It's still possible to print an exception with writableStackTrace == false.
60            th.printStackTrace(new PrintWriter(new StringWriter()));
61        }
62    }
63
64    private static class SuppressionsThrowable extends Throwable {
65        private static final long serialVersionUID = 202649043897209143L;
66
67        public SuppressionsThrowable(String detailMessage, Throwable throwable,
68                boolean enableSuppression, boolean writableStackTrace) {
69            super(detailMessage, throwable, enableSuppression, writableStackTrace);
70        }
71    }
72
73    public void testAddSuppressed() {
74        Throwable throwable = new Throwable();
75        assertSuppressed(throwable);
76        Throwable suppressedA = new Throwable();
77        throwable.addSuppressed(suppressedA);
78        assertSuppressed(throwable, suppressedA);
79        Throwable suppressedB = new Throwable();
80        throwable.addSuppressed(suppressedB);
81        assertSuppressed(throwable, suppressedA, suppressedB);
82    }
83
84    public void testAddDuplicateSuppressed() {
85        Throwable throwable = new Throwable();
86        Throwable suppressedA = new Throwable();
87        throwable.addSuppressed(suppressedA);
88        throwable.addSuppressed(suppressedA);
89        throwable.addSuppressed(suppressedA);
90        assertSuppressed(throwable, suppressedA, suppressedA, suppressedA);
91    }
92
93    public void testGetSuppressedReturnsCopy() {
94        Throwable throwable = new Throwable();
95        Throwable suppressedA = new Throwable();
96        Throwable suppressedB = new Throwable();
97        throwable.addSuppressed(suppressedA);
98        throwable.addSuppressed(suppressedB);
99        Throwable[] mutable = throwable.getSuppressed();
100        mutable[0] = null;
101        mutable[1] = null;
102        assertSuppressed(throwable, suppressedA, suppressedB);
103    }
104
105    public void testAddSuppressedWithSuppressionDisabled() {
106        Throwable throwable = new SuppressionsThrowable("foo", null, false, true);
107        assertSuppressed(throwable);
108        throwable.addSuppressed(new Throwable());
109        assertSuppressed(throwable);
110        throwable.addSuppressed(new Throwable());
111        assertSuppressed(throwable);
112    }
113
114    public void testAddSuppressedSelf() {
115        Throwable throwable = new Throwable();
116        try {
117            throwable.addSuppressed(throwable);
118            fail();
119        } catch (IllegalArgumentException expected) {
120        }
121    }
122
123    public void testAddSuppressedNull() {
124        Throwable throwable = new Throwable();
125        try {
126            throwable.addSuppressed(null);
127            fail();
128        } catch (NullPointerException expected) {
129        }
130    }
131
132    public void testPrintStackTraceWithCause() {
133        Throwable throwable = newThrowable("Throwable", "A", "B");
134        throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
135
136        assertEquals("java.lang.Throwable: Throwable\n"
137                + "\tat ClassB.doB(ClassB.java:1)\n"
138                + "\tat ClassA.doA(ClassA.java:0)\n"
139                + "Caused by: java.lang.Throwable: Cause\n"
140                + "\tat ClassD.doD(ClassD.java:3)\n"
141                + "\tat ClassC.doC(ClassC.java:2)\n"
142                + "\t... 2 more\n", printStackTraceToString(throwable));
143    }
144
145    public void testPrintStackTraceWithCauseAndSuppressed() {
146        Throwable throwable = newThrowable("Throwable", "A", "B");
147        throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
148        throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "E", "F"));
149        throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "G", "H"));
150
151        assertEquals("java.lang.Throwable: Throwable\n"
152                + "\tat ClassB.doB(ClassB.java:1)\n"
153                + "\tat ClassA.doA(ClassA.java:0)\n"
154                + "\tSuppressed: java.lang.Throwable: Suppressed\n"
155                + "\t\tat ClassF.doF(ClassF.java:3)\n"
156                + "\t\tat ClassE.doE(ClassE.java:2)\n"
157                + "\t\t... 2 more\n"
158                + "\tSuppressed: java.lang.Throwable: Suppressed\n"
159                + "\t\tat ClassH.doH(ClassH.java:3)\n"
160                + "\t\tat ClassG.doG(ClassG.java:2)\n"
161                + "\t\t... 2 more\n"
162                + "Caused by: java.lang.Throwable: Cause\n"
163                + "\tat ClassD.doD(ClassD.java:3)\n"
164                + "\tat ClassC.doC(ClassC.java:2)\n"
165                + "\t... 2 more\n", printStackTraceToString(throwable));
166    }
167
168    public void testPrintStackTraceWithEverything() {
169        Throwable throwable = newThrowable("Throwable", "A", "B");
170        Throwable cause = newThrowable("Cause", "A", "B", "C", "D");
171        Throwable suppressed = newThrowable("Suppressed", "A", "B", "E", "F");
172
173        throwable.addSuppressed(suppressed);
174        suppressed.addSuppressed(newThrowable("Suppressed/Suppressed", "A", "B", "E", "G"));
175        suppressed.initCause(newThrowable("Suppressed/Cause", "A", "B", "E", "H"));
176
177        throwable.initCause(cause);
178        cause.addSuppressed(newThrowable("Cause/Suppressed", "A", "B", "C", "I"));
179        cause.initCause(newThrowable("Cause/Cause", "A", "B", "C", "J"));
180
181        assertEquals("java.lang.Throwable: Throwable\n"
182                + "\tat ClassB.doB(ClassB.java:1)\n"
183                + "\tat ClassA.doA(ClassA.java:0)\n"
184                + "\tSuppressed: java.lang.Throwable: Suppressed\n"
185                + "\t\tat ClassF.doF(ClassF.java:3)\n"
186                + "\t\tat ClassE.doE(ClassE.java:2)\n"
187                + "\t\t... 2 more\n"
188                + "\t\tSuppressed: java.lang.Throwable: Suppressed/Suppressed\n"
189                + "\t\t\tat ClassG.doG(ClassG.java:3)\n"
190                + "\t\t\t... 3 more\n"
191                + "\tCaused by: java.lang.Throwable: Suppressed/Cause\n"
192                + "\t\tat ClassH.doH(ClassH.java:3)\n"
193                + "\t\t... 3 more\n"
194                + "Caused by: java.lang.Throwable: Cause\n"
195                + "\tat ClassD.doD(ClassD.java:3)\n"
196                + "\tat ClassC.doC(ClassC.java:2)\n"
197                + "\t... 2 more\n"
198                + "\tSuppressed: java.lang.Throwable: Cause/Suppressed\n"
199                + "\t\tat ClassI.doI(ClassI.java:3)\n"
200                + "\t\t... 3 more\n"
201                + "Caused by: java.lang.Throwable: Cause/Cause\n"
202                + "\tat ClassJ.doJ(ClassJ.java:3)\n"
203                + "\t... 3 more\n", printStackTraceToString(throwable));
204    }
205
206    public void testSetStackTraceWithNullElement() {
207        Throwable throwable = new Throwable();
208        try {
209            throwable.setStackTrace(new StackTraceElement[]{ null });
210            fail();
211        } catch (NullPointerException expected) {
212        }
213    }
214
215    public void testCauseSerialization() {
216        String s = "aced0005737200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300034c0"
217                + "00563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4"
218                + "d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636"
219                + "574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b78707371007e0"
220                + "00071007e000574000543617573657572001e5b4c6a6176612e6c616e672e537461636b547261636"
221                + "5456c656d656e743b02462a3c3cfd22390200007870000000047372001b6a6176612e6c616e672e5"
222                + "37461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d62657"
223                + "24c000e6465636c6172696e67436c61737371007e00024c000866696c654e616d6571007e00024c0"
224                + "00a6d6574686f644e616d6571007e0002787000000003740006436c6173734474000b436c6173734"
225                + "42e6a617661740003646f447371007e000900000002740006436c6173734374000b436c617373432"
226                + "e6a617661740003646f437371007e000900000001740006436c6173734274000b436c617373422e6"
227                + "a617661740003646f427371007e000900000000740006436c6173734174000b436c617373412e6a6"
228                + "17661740003646f41787400095468726f7761626c657571007e0007000000027371007e000900000"
229                + "001740006436c6173734274000b436c617373422e6a617661740003646f427371007e00090000000"
230                + "0740006436c6173734174000b436c617373412e6a617661740003646f4178";
231        Throwable throwable = newThrowable("Throwable", "A", "B");
232        throwable.initCause(newThrowable("Cause", "A", "B", "C", "D"));
233        assertSerialized(throwable, s);
234    }
235
236    public void testSuppressedSerialization() {
237        String s = "aced0005737200136a6176612e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c0"
238                + "00563617573657400154c6a6176612f6c616e672f5468726f7761626c653b4c000d64657461696c4"
239                + "d6573736167657400124c6a6176612f6c616e672f537472696e673b5b000a737461636b547261636"
240                + "574001e5b4c6a6176612f6c616e672f537461636b5472616365456c656d656e743b4c00147375707"
241                + "0726573736564457863657074696f6e737400104c6a6176612f7574696c2f4c6973743b787071007"
242                + "e000574000a53657269616c697a65647572001e5b4c6a6176612e6c616e672e537461636b5472616"
243                + "365456c656d656e743b02462a3c3cfd22390200007870000000027372001b6a6176612e6c616e672"
244                + "e537461636b5472616365456c656d656e746109c59a2636dd8502000449000a6c696e654e756d626"
245                + "5724c000e6465636c6172696e67436c61737371007e00024c000866696c654e616d6571007e00024"
246                + "c000a6d6574686f644e616d6571007e0002787000000001740006436c6173734274000b436c61737"
247                + "3422e6a617661740003646f427371007e000900000000740006436c6173734174000b436c6173734"
248                + "12e6a617661740003646f41737200136a6176612e7574696c2e41727261794c6973747881d21d99c"
249                + "7619d03000149000473697a657870000000017704000000017371007e000071007e001474000a537"
250                + "570707265737365647571007e0007000000047371007e000900000003740006436c6173734474000"
251                + "b436c617373442e6a617661740003646f447371007e000900000002740006436c6173734374000b4"
252                + "36c617373432e6a617661740003646f437371007e000900000001740006436c6173734274000b436"
253                + "c617373422e6a617661740003646f427371007e000900000000740006436c6173734174000b436c6"
254                + "17373412e6a617661740003646f41737200266a6176612e7574696c2e436f6c6c656374696f6e732"
255                + "4556e6d6f6469666961626c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00047"
256                + "872002c6a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6"
257                + "c6c656374696f6e19420080cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6"
258                + "c656374696f6e3b78707371007e0012000000007704000000007871007e002b787878";
259        Throwable throwable = newThrowable("Serialized", "A", "B");
260        throwable.addSuppressed(newThrowable("Suppressed", "A", "B", "C", "D"));
261        assertSerialized(throwable, s);
262    }
263
264    public void testDisableSuppressionSerialization() {
265        String s = "aced0005737200356c6962636f72652e6a6176612e6c616e672e5468726f7761626c65546573742"
266                + "45375707072657373696f6e735468726f7761626c6502cff43b5390d137020000787200136a61766"
267                + "12e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6"
268                + "176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6"
269                + "176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c6"
270                + "16e672f537461636b5472616365456c656d656e743b4c00147375707072657373656445786365707"
271                + "4696f6e737400104c6a6176612f7574696c2f4c6973743b787070740003666f6f7572001e5b4c6a6"
272                + "176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd223902000078700"
273                + "00000007078";
274        Throwable throwable = new SuppressionsThrowable("foo", null, false, true);
275        throwable.setStackTrace(new StackTraceElement[0]);
276        new SerializationTester<Throwable>(throwable, s) {
277            @Override protected boolean equals(Throwable a, Throwable b) {
278                return printStackTraceToString(a).equals(printStackTraceToString(b));
279            }
280            @Override protected void verify(Throwable deserialized) {
281                // the suppressed exception is silently discarded
282                deserialized.addSuppressed(newThrowable("Suppressed"));
283                assertSuppressed(deserialized);
284            }
285        }.test();
286    }
287
288    public void testEnableSuppressionSerialization() {
289        String s = "aced0005737200356c6962636f72652e6a6176612e6c616e672e5468726f7761626c65546573742"
290                + "45375707072657373696f6e735468726f7761626c6502cff43b5390d137020000787200136a61766"
291                + "12e6c616e672e5468726f7761626c65d5c635273977b8cb0300044c000563617573657400154c6a6"
292                + "176612f6c616e672f5468726f7761626c653b4c000d64657461696c4d6573736167657400124c6a6"
293                + "176612f6c616e672f537472696e673b5b000a737461636b547261636574001e5b4c6a6176612f6c6"
294                + "16e672f537461636b5472616365456c656d656e743b4c00147375707072657373656445786365707"
295                + "4696f6e737400104c6a6176612f7574696c2f4c6973743b787070740003666f6f7572001e5b4c6a6"
296                + "176612e6c616e672e537461636b5472616365456c656d656e743b02462a3c3cfd223902000078700"
297                + "0000000737200266a6176612e7574696c2e436f6c6c656374696f6e7324556e6d6f6469666961626"
298                + "c654c697374fc0f2531b5ec8e100200014c00046c69737471007e00057872002c6a6176612e75746"
299                + "96c2e436f6c6c656374696f6e7324556e6d6f6469666961626c65436f6c6c656374696f6e1942008"
300                + "0cb5ef71e0200014c0001637400164c6a6176612f7574696c2f436f6c6c656374696f6e3b7870737"
301                + "200136a6176612e7574696c2e41727261794c6973747881d21d99c7619d03000149000473697a657"
302                + "870000000007704000000007871007e000f78";
303        Throwable throwable = new SuppressionsThrowable("foo", null, true, true);
304        throwable.setStackTrace(new StackTraceElement[0]);
305        new SerializationTester<Throwable>(throwable, s) {
306            @Override protected boolean equals(Throwable a, Throwable b) {
307                return printStackTraceToString(a).equals(printStackTraceToString(b));
308            }
309            @Override protected void verify(Throwable deserialized) {
310                // the suppressed exception is permitted
311                Throwable suppressed = newThrowable("Suppressed");
312                deserialized.addSuppressed(suppressed);
313                assertSuppressed(deserialized, suppressed);
314            }
315        }.test();
316    }
317
318    private void assertSerialized(final Throwable throwable, String golden) {
319        new SerializationTester<Throwable>(throwable, golden) {
320            @Override protected boolean equals(Throwable a, Throwable b) {
321                return printStackTraceToString(a).equals(printStackTraceToString(b));
322            }
323        }.test();
324    }
325
326    private Throwable newThrowable(String message, String... stackTraceElements) {
327        StackTraceElement[] array = new StackTraceElement[stackTraceElements.length];
328        for (int i = 0; i < stackTraceElements.length; i++) {
329            String s = stackTraceElements[i];
330            array[stackTraceElements.length - 1 - i]
331                    = new StackTraceElement("Class" + s, "do" + s, "Class" + s + ".java", i);
332        }
333        Throwable result = new Throwable(message);
334        result.setStackTrace(array);
335        return result;
336    }
337
338    private String printStackTraceToString(Throwable throwable) {
339        StringWriter writer = new StringWriter();
340        throwable.printStackTrace(new PrintWriter(writer));
341        return writer.toString();
342    }
343
344    private void assertSuppressed(Throwable throwable, Throwable... expectedSuppressed) {
345        assertEquals(Arrays.asList(throwable.getSuppressed()), Arrays.asList(expectedSuppressed));
346    }
347}
348