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