1 /******************************************************************************* 2 * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors 3 * All rights reserved. This program and the accompanying materials 4 * are made available under the terms of the Eclipse Public License v1.0 5 * which accompanies this distribution, and is available at 6 * http://www.eclipse.org/legal/epl-v10.html 7 * 8 * Contributors: 9 * Marc R. Hoffmann - initial API and implementation 10 * 11 *******************************************************************************/ 12 package org.jacoco.core.instr; 13 14 import static org.junit.Assert.assertEquals; 15 import static org.junit.Assert.assertNull; 16 import static org.junit.Assert.fail; 17 18 import java.io.ByteArrayInputStream; 19 import java.io.ByteArrayOutputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.io.ObjectInputStream; 23 import java.io.ObjectOutputStream; 24 import java.io.OutputStream; 25 import java.io.Serializable; 26 import java.util.Arrays; 27 import java.util.jar.JarInputStream; 28 import java.util.jar.JarOutputStream; 29 import java.util.jar.Pack200; 30 import java.util.zip.GZIPInputStream; 31 import java.util.zip.GZIPOutputStream; 32 import java.util.zip.ZipEntry; 33 import java.util.zip.ZipInputStream; 34 import java.util.zip.ZipOutputStream; 35 36 import org.jacoco.core.analysis.AnalyzerTest; 37 import org.jacoco.core.runtime.RuntimeData; 38 import org.jacoco.core.runtime.SystemPropertiesRuntime; 39 import org.jacoco.core.test.TargetLoader; 40 import org.junit.After; 41 import org.junit.Before; 42 import org.junit.Test; 43 44 /** 45 * Unit tests for {@link Instrumenter}. 46 */ 47 public class InstrumenterTest { 48 49 // no serialVersionUID to enforce calculation 50 @SuppressWarnings("serial") 51 public static class SerializationTarget implements Serializable { 52 53 private final String text; 54 55 private final int nr; 56 57 public SerializationTarget(final String text, final int nr) { 58 this.text = text; 59 this.nr = nr; 60 } 61 62 @Override 63 public String toString() { 64 return text + nr; 65 } 66 67 } 68 69 private SystemPropertiesRuntime runtime; 70 71 private Instrumenter instrumenter; 72 73 @Before 74 public void setup() throws Exception { 75 runtime = new SystemPropertiesRuntime(); 76 instrumenter = new Instrumenter(runtime); 77 runtime.startup(new RuntimeData()); 78 } 79 80 @After 81 public void teardown() { 82 runtime.shutdown(); 83 } 84 85 @Test 86 public void testInstrumentClass() throws Exception { 87 byte[] bytes = instrumenter.instrument( 88 TargetLoader.getClassDataAsBytes(InstrumenterTest.class), 89 "Test"); 90 TargetLoader loader = new TargetLoader(); 91 Class<?> clazz = loader.add(InstrumenterTest.class, bytes); 92 assertEquals("org.jacoco.core.instr.InstrumenterTest", clazz.getName()); 93 } 94 95 /** 96 * Triggers exception in {@link Instrumenter#instrument(byte[], String)}. 97 */ 98 @Test 99 public void testInstrumentBrokenClass1() throws IOException { 100 final byte[] brokenclass = TargetLoader 101 .getClassDataAsBytes(AnalyzerTest.class); 102 brokenclass[10] = 0x23; 103 try { 104 instrumenter.instrument(brokenclass, "Broken.class"); 105 fail(); 106 } catch (IOException e) { 107 assertEquals("Error while instrumenting Broken.class.", 108 e.getMessage()); 109 } 110 } 111 112 private static class BrokenInputStream extends InputStream { 113 @Override 114 public int read() throws IOException { 115 throw new IOException(); 116 } 117 } 118 119 /** 120 * Triggers exception in 121 * {@link Instrumenter#instrument(InputStream, String)}. 122 */ 123 @Test 124 public void testInstrumentBrokenStream() { 125 try { 126 instrumenter.instrument(new BrokenInputStream(), "BrokenStream"); 127 fail("exception expected"); 128 } catch (IOException e) { 129 assertEquals("Error while instrumenting BrokenStream.", 130 e.getMessage()); 131 } 132 } 133 134 /** 135 * Triggers exception in 136 * {@link Instrumenter#instrument(InputStream, OutputStream, String)}. 137 */ 138 @Test 139 public void testInstrumentBrokenStream2() { 140 try { 141 instrumenter.instrument(new BrokenInputStream(), 142 new ByteArrayOutputStream(), "BrokenStream"); 143 fail("exception expected"); 144 } catch (IOException e) { 145 assertEquals("Error while instrumenting BrokenStream.", 146 e.getMessage()); 147 } 148 } 149 150 @Test 151 public void testSerialization() throws Exception { 152 // Create instrumented instance: 153 byte[] bytes = instrumenter.instrument( 154 TargetLoader.getClassData(SerializationTarget.class), "Test"); 155 TargetLoader loader = new TargetLoader(); 156 Object obj1 = loader.add(SerializationTarget.class, bytes) 157 .getConstructor(String.class, Integer.TYPE) 158 .newInstance("Hello", Integer.valueOf(42)); 159 160 // Serialize instrumented instance: 161 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 162 new ObjectOutputStream(buffer).writeObject(obj1); 163 164 // Deserialize with original class definition: 165 Object obj2 = new ObjectInputStream(new ByteArrayInputStream( 166 buffer.toByteArray())).readObject(); 167 assertEquals("Hello42", obj2.toString()); 168 } 169 170 @Test 171 public void testInstrumentAll_Class() throws IOException { 172 InputStream in = TargetLoader.getClassData(getClass()); 173 OutputStream out = new ByteArrayOutputStream(); 174 175 int count = instrumenter.instrumentAll(in, out, "Test"); 176 177 assertEquals(1, count); 178 } 179 180 @Test 181 public void testInstrumentAll_Zip() throws IOException { 182 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 183 ZipOutputStream zipout = new ZipOutputStream(buffer); 184 zipout.putNextEntry(new ZipEntry("Test.class")); 185 zipout.write(TargetLoader.getClassDataAsBytes(getClass())); 186 zipout.finish(); 187 ByteArrayOutputStream out = new ByteArrayOutputStream(); 188 189 int count = instrumenter.instrumentAll( 190 new ByteArrayInputStream(buffer.toByteArray()), out, "Test"); 191 192 assertEquals(1, count); 193 ZipInputStream zipin = new ZipInputStream(new ByteArrayInputStream( 194 out.toByteArray())); 195 assertEquals("Test.class", zipin.getNextEntry().getName()); 196 assertNull(zipin.getNextEntry()); 197 } 198 199 /** 200 * Triggers exception in 201 * {@link org.jacoco.core.internal.ContentTypeDetector#ContentTypeDetector(InputStream)}. 202 */ 203 @Test 204 public void testInstrumentAll_Broken() { 205 try { 206 instrumenter.instrumentAll(new BrokenInputStream(), 207 new ByteArrayOutputStream(), "Broken"); 208 fail("exception expected"); 209 } catch (IOException e) { 210 assertEquals("Error while instrumenting Broken.", e.getMessage()); 211 } 212 } 213 214 /** 215 * Triggers exception in 216 * {@link Instrumenter#copy(InputStream, OutputStream)}. 217 */ 218 @Test 219 public void testInstrumentAll_Broken2() { 220 final InputStream inputStream = new InputStream() { 221 private int count; 222 223 @Override 224 public int read() throws IOException { 225 count++; 226 if (count > 4) { 227 throw new IOException(); 228 } 229 return 0; 230 } 231 }; 232 233 try { 234 instrumenter.instrumentAll(inputStream, new ByteArrayOutputStream(), 235 "Broken"); 236 } catch (IOException e) { 237 assertEquals("Error while instrumenting Broken.", e.getMessage()); 238 } 239 } 240 241 /** 242 * Triggers exception in 243 * {@link Instrumenter#nextEntry(ZipInputStream, String)}. 244 */ 245 @Test 246 public void testInstrumentAll_BrokenZip() { 247 final byte[] buffer = new byte[30]; 248 buffer[0] = 0x50; 249 buffer[1] = 0x4b; 250 buffer[2] = 0x03; 251 buffer[3] = 0x04; 252 Arrays.fill(buffer, 4, buffer.length, (byte) 0x42); 253 254 try { 255 instrumenter.instrumentAll(new ByteArrayInputStream(buffer), 256 new ByteArrayOutputStream(), "Test.zip"); 257 fail("exception expected"); 258 } catch (IOException e) { 259 assertEquals("Error while instrumenting Test.zip.", e.getMessage()); 260 } 261 } 262 263 /** 264 * With JDK <= 6 triggers exception in 265 * {@link Instrumenter#copy(InputStream, OutputStream)}. 266 * 267 * With JDK > 6 triggers exception in 268 * {@link org.jacoco.core.internal.ContentTypeDetector#ContentTypeDetector(InputStream)}. 269 */ 270 @Test 271 public void testInstrumentAll_BrokenZipEntry() throws IOException { 272 ByteArrayOutputStream out = new ByteArrayOutputStream(); 273 ZipOutputStream zip = new ZipOutputStream(out); 274 zip.putNextEntry(new ZipEntry("brokenentry.txt")); 275 out.write(0x23); // Unexpected data here 276 zip.close(); 277 278 try { 279 instrumenter.instrumentAll( 280 new ByteArrayInputStream(out.toByteArray()), 281 new ByteArrayOutputStream(), "broken.zip"); 282 fail("exception expected"); 283 } catch (IOException e) { 284 assertEquals( 285 "Error while instrumenting broken.zip (at) brokenentry.txt.", 286 e.getMessage()); 287 } 288 } 289 290 @Test 291 public void testInstrumentAll_BrokenClassFileInZip() throws IOException { 292 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 293 ZipOutputStream zipout = new ZipOutputStream(buffer); 294 zipout.putNextEntry(new ZipEntry("Test.class")); 295 final byte[] brokenclass = TargetLoader.getClassDataAsBytes(getClass()); 296 brokenclass[10] = 0x23; 297 zipout.write(brokenclass); 298 zipout.finish(); 299 ByteArrayOutputStream out = new ByteArrayOutputStream(); 300 301 try { 302 instrumenter.instrumentAll( 303 new ByteArrayInputStream(buffer.toByteArray()), out, 304 "test.zip"); 305 fail(); 306 } catch (IOException e) { 307 assertEquals("Error while instrumenting test.zip (at) Test.class.", 308 e.getMessage()); 309 } 310 } 311 312 /** 313 * Triggers exception in 314 * {@link Instrumenter#instrumentGzip(InputStream, OutputStream, String)}. 315 */ 316 @Test 317 public void testInstrumentAll_BrokenGZ() { 318 final byte[] buffer = new byte[] { 0x1f, (byte) 0x8b, 0x00, 0x00 }; 319 320 try { 321 instrumenter.instrumentAll(new ByteArrayInputStream(buffer), 322 new ByteArrayOutputStream(), "Test.gz"); 323 fail("exception expected"); 324 } catch (IOException e) { 325 assertEquals("Error while instrumenting Test.gz.", e.getMessage()); 326 } 327 } 328 329 @Test 330 public void testInstrumentAll_Pack200() throws IOException { 331 ByteArrayOutputStream jarbuffer = new ByteArrayOutputStream(); 332 ZipOutputStream zipout = new ZipOutputStream(jarbuffer); 333 zipout.putNextEntry(new ZipEntry("Test.class")); 334 zipout.write(TargetLoader.getClassDataAsBytes(getClass())); 335 zipout.finish(); 336 337 ByteArrayOutputStream pack200buffer = new ByteArrayOutputStream(); 338 GZIPOutputStream gzipOutput = new GZIPOutputStream(pack200buffer); 339 Pack200.newPacker().pack( 340 new JarInputStream(new ByteArrayInputStream( 341 jarbuffer.toByteArray())), gzipOutput); 342 gzipOutput.finish(); 343 344 ByteArrayOutputStream out = new ByteArrayOutputStream(); 345 int count = instrumenter.instrumentAll(new ByteArrayInputStream( 346 pack200buffer.toByteArray()), out, "Test"); 347 348 jarbuffer.reset(); 349 Pack200.newUnpacker() 350 .unpack(new GZIPInputStream(new ByteArrayInputStream( 351 out.toByteArray())), new JarOutputStream(jarbuffer)); 352 353 assertEquals(1, count); 354 ZipInputStream zipin = new ZipInputStream(new ByteArrayInputStream( 355 jarbuffer.toByteArray())); 356 assertEquals("Test.class", zipin.getNextEntry().getName()); 357 assertNull(zipin.getNextEntry()); 358 } 359 360 /** 361 * Triggers exception in 362 * {@link Instrumenter#instrumentPack200(InputStream, OutputStream, String)}. 363 */ 364 @Test 365 public void testInstrumentAll_BrokenPack200() { 366 final byte[] buffer = new byte[] { (byte) 0xca, (byte) 0xfe, 367 (byte) 0xd0, 0x0d }; 368 369 try { 370 instrumenter.instrumentAll(new ByteArrayInputStream(buffer), 371 new ByteArrayOutputStream(), "Test.pack200"); 372 } catch (IOException e) { 373 assertEquals("Error while instrumenting Test.pack200.", 374 e.getMessage()); 375 } 376 } 377 378 @Test 379 public void testInstrumentAll_Other() throws IOException { 380 InputStream in = new ByteArrayInputStream("text".getBytes()); 381 ByteArrayOutputStream out = new ByteArrayOutputStream(); 382 383 int count = instrumenter.instrumentAll(in, out, "Test"); 384 385 assertEquals(0, count); 386 assertEquals("text", new String(out.toByteArray())); 387 } 388 389 @Test 390 public void testInstrumentAll_RemoveSignatures() throws IOException { 391 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 392 ZipOutputStream zipout = new ZipOutputStream(buffer); 393 zipout.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); 394 zipout.putNextEntry(new ZipEntry("META-INF/ALIAS.SF")); 395 zipout.finish(); 396 ByteArrayOutputStream out = new ByteArrayOutputStream(); 397 398 int count = instrumenter.instrumentAll( 399 new ByteArrayInputStream(buffer.toByteArray()), out, "Test"); 400 401 assertEquals(0, count); 402 ZipInputStream zipin = new ZipInputStream(new ByteArrayInputStream( 403 out.toByteArray())); 404 assertEquals("META-INF/MANIFEST.MF", zipin.getNextEntry().getName()); 405 assertNull(zipin.getNextEntry()); 406 } 407 408 @Test 409 public void testInstrumentAll_KeepSignatures() throws IOException { 410 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 411 ZipOutputStream zipout = new ZipOutputStream(buffer); 412 zipout.putNextEntry(new ZipEntry("META-INF/ALIAS.SF")); 413 zipout.finish(); 414 ByteArrayOutputStream out = new ByteArrayOutputStream(); 415 416 instrumenter.setRemoveSignatures(false); 417 int count = instrumenter.instrumentAll( 418 new ByteArrayInputStream(buffer.toByteArray()), out, "Test"); 419 420 assertEquals(0, count); 421 ZipInputStream zipin = new ZipInputStream(new ByteArrayInputStream( 422 out.toByteArray())); 423 assertEquals("META-INF/ALIAS.SF", zipin.getNextEntry().getName()); 424 assertNull(zipin.getNextEntry()); 425 } 426 427 } 428