Home | History | Annotate | Download | only in instr
      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