xref: /aosp_15_r20/external/jacoco/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java (revision 7e63c1270baf9bfa84f5b6aecf17bd0c1a75af94)
1 /*******************************************************************************
2  * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors
3  * This program and the accompanying materials are made available under
4  * the terms of the Eclipse Public License 2.0 which is available at
5  * http://www.eclipse.org/legal/epl-2.0
6  *
7  * SPDX-License-Identifier: EPL-2.0
8  *
9  * Contributors:
10  *    Marc R. Hoffmann - initial API and implementation
11  *
12  *******************************************************************************/
13 package org.jacoco.core.analysis;
14 
15 import static org.junit.Assert.assertArrayEquals;
16 import static org.junit.Assert.assertEquals;
17 import static org.junit.Assert.assertFalse;
18 import static org.junit.Assert.assertNull;
19 import static org.junit.Assert.assertTrue;
20 import static org.junit.Assert.fail;
21 
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.OutputStream;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Map;
34 import java.util.zip.GZIPOutputStream;
35 import java.util.zip.ZipEntry;
36 import java.util.zip.ZipInputStream;
37 import java.util.zip.ZipOutputStream;
38 
39 import org.jacoco.core.data.ExecutionDataStore;
40 import org.jacoco.core.internal.Pack200Streams;
41 import org.jacoco.core.internal.data.CRC64;
42 import org.jacoco.core.test.TargetLoader;
43 import org.junit.AssumptionViolatedException;
44 import org.junit.Before;
45 import org.junit.Rule;
46 import org.junit.Test;
47 import org.junit.rules.TemporaryFolder;
48 import org.objectweb.asm.ClassWriter;
49 import org.objectweb.asm.Opcodes;
50 
51 /**
52  * Unit tests for {@link Analyzer}.
53  */
54 public class AnalyzerTest {
55 
56 	@Rule
57 	public TemporaryFolder folder = new TemporaryFolder();
58 
59 	private Analyzer analyzer;
60 
61 	private Map<String, IClassCoverage> classes;
62 
63 	private ExecutionDataStore executionData;
64 
65 	private class EmptyStructureVisitor implements ICoverageVisitor {
66 
visitCoverage(IClassCoverage coverage)67 		public void visitCoverage(IClassCoverage coverage) {
68 			final String name = coverage.getName();
69 			assertNull("Class already processed: " + name,
70 					classes.put(name, coverage));
71 		}
72 	}
73 
74 	@Before
setup()75 	public void setup() {
76 		classes = new HashMap<String, IClassCoverage>();
77 		executionData = new ExecutionDataStore();
78 		analyzer = new Analyzer(executionData, new EmptyStructureVisitor());
79 	}
80 
81 	@Test
should_ignore_module_info()82 	public void should_ignore_module_info() throws Exception {
83 		final ClassWriter cw = new ClassWriter(0);
84 		cw.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null,
85 				null);
86 		cw.visitModule("module", 0, null).visitEnd();
87 		cw.visitEnd();
88 		final byte[] bytes = cw.toByteArray();
89 
90 		analyzer.analyzeClass(bytes, "");
91 
92 		assertTrue(classes.isEmpty());
93 	}
94 
95 	@Test
should_ignore_synthetic_classes()96 	public void should_ignore_synthetic_classes() throws Exception {
97 		final ClassWriter cw = new ClassWriter(0);
98 		cw.visit(Opcodes.V1_5, Opcodes.ACC_SYNTHETIC, "Foo", null,
99 				"java/lang/Object", null);
100 		cw.visitEnd();
101 		final byte[] bytes = cw.toByteArray();
102 
103 		analyzer.analyzeClass(bytes, "");
104 
105 		assertTrue(classes.isEmpty());
106 	}
107 
108 	@Test
should_not_modify_class_bytes_to_support_next_version()109 	public void should_not_modify_class_bytes_to_support_next_version()
110 			throws Exception {
111 		final byte[] originalBytes = createClass(Opcodes.V16);
112 		final byte[] bytes = new byte[originalBytes.length];
113 		System.arraycopy(originalBytes, 0, bytes, 0, originalBytes.length);
114 		final long expectedClassId = CRC64.classId(bytes);
115 
116 		analyzer.analyzeClass(bytes, "");
117 
118 		assertArrayEquals(originalBytes, bytes);
119 		assertEquals(expectedClassId, classes.get("Foo").getId());
120 	}
121 
createClass(final int version)122 	private static byte[] createClass(final int version) {
123 		final ClassWriter cw = new ClassWriter(0);
124 		cw.visit(version, 0, "Foo", null, "java/lang/Object", null);
125 		cw.visitEnd();
126 		return cw.toByteArray();
127 	}
128 
129 	/**
130 	 * @see #analyzeAll_should_throw_exception_for_unsupported_class_file_version()
131 	 */
132 	@Test
analyzeClass_should_throw_exception_for_unsupported_class_file_version()133 	public void analyzeClass_should_throw_exception_for_unsupported_class_file_version() {
134 		final byte[] bytes = createClass(Opcodes.V16 + 2);
135 		try {
136 			analyzer.analyzeClass(bytes, "UnsupportedVersion");
137 			fail("exception expected");
138 		} catch (IOException e) {
139 			assertEquals("Error while analyzing UnsupportedVersion.",
140 					e.getMessage());
141 			assertEquals("Unsupported class file major version 62",
142 					e.getCause().getMessage());
143 		}
144 	}
145 
146 	@Test
testAnalyzeClassFromStream()147 	public void testAnalyzeClassFromStream() throws IOException {
148 		analyzer.analyzeClass(TargetLoader.getClassData(AnalyzerTest.class),
149 				"Test");
150 		assertClasses("org/jacoco/core/analysis/AnalyzerTest");
151 	}
152 
153 	@Test
testAnalyzeClassFromByteArray()154 	public void testAnalyzeClassFromByteArray() throws IOException {
155 		analyzer.analyzeClass(
156 				TargetLoader.getClassDataAsBytes(AnalyzerTest.class), "Test");
157 		assertClasses("org/jacoco/core/analysis/AnalyzerTest");
158 		assertFalse(classes.get("org/jacoco/core/analysis/AnalyzerTest")
159 				.isNoMatch());
160 	}
161 
162 	@Test
testAnalyzeClassIdMatch()163 	public void testAnalyzeClassIdMatch() throws IOException {
164 		final byte[] bytes = TargetLoader
165 				.getClassDataAsBytes(AnalyzerTest.class);
166 		executionData.get(Long.valueOf(CRC64.classId(bytes)),
167 				"org/jacoco/core/analysis/AnalyzerTest", 200);
168 		analyzer.analyzeClass(bytes, "Test");
169 		assertFalse(classes.get("org/jacoco/core/analysis/AnalyzerTest")
170 				.isNoMatch());
171 	}
172 
173 	@Test
testAnalyzeClassNoIdMatch()174 	public void testAnalyzeClassNoIdMatch() throws IOException {
175 		executionData.get(Long.valueOf(0),
176 				"org/jacoco/core/analysis/AnalyzerTest", 200);
177 		analyzer.analyzeClass(
178 				TargetLoader.getClassDataAsBytes(AnalyzerTest.class), "Test");
179 		assertTrue(classes.get("org/jacoco/core/analysis/AnalyzerTest")
180 				.isNoMatch());
181 	}
182 
183 	@Test
testAnalyzeClass_Broken()184 	public void testAnalyzeClass_Broken() throws IOException {
185 		final byte[] brokenclass = TargetLoader
186 				.getClassDataAsBytes(AnalyzerTest.class);
187 		brokenclass[10] = 0x23;
188 		try {
189 			analyzer.analyzeClass(brokenclass, "Broken.class");
190 			fail("expected exception");
191 		} catch (IOException e) {
192 			assertEquals("Error while analyzing Broken.class.", e.getMessage());
193 		}
194 	}
195 
196 	private static class BrokenInputStream extends InputStream {
197 		@Override
read()198 		public int read() throws IOException {
199 			throw new IOException();
200 		}
201 	}
202 
203 	/**
204 	 * Triggers exception in {@link Analyzer#analyzeClass(InputStream, String)}.
205 	 */
206 	@Test
testAnalyzeClass_BrokenStream()207 	public void testAnalyzeClass_BrokenStream() throws IOException {
208 		try {
209 			analyzer.analyzeClass(new BrokenInputStream(), "BrokenStream");
210 			fail("exception expected");
211 		} catch (IOException e) {
212 			assertEquals("Error while analyzing BrokenStream.", e.getMessage());
213 		}
214 	}
215 
216 	/**
217 	 * @see #analyzeClass_should_throw_exception_for_unsupported_class_file_version()
218 	 */
219 	@Test
analyzeAll_should_throw_exception_for_unsupported_class_file_version()220 	public void analyzeAll_should_throw_exception_for_unsupported_class_file_version() {
221 		final byte[] bytes = createClass(Opcodes.V16 + 2);
222 		try {
223 			analyzer.analyzeAll(new ByteArrayInputStream(bytes),
224 					"UnsupportedVersion");
225 			fail("exception expected");
226 		} catch (IOException e) {
227 			assertEquals("Error while analyzing UnsupportedVersion.",
228 					e.getMessage());
229 			assertEquals("Unsupported class file major version 62",
230 					e.getCause().getMessage());
231 		}
232 	}
233 
234 	@Test
testAnalyzeAll_Class()235 	public void testAnalyzeAll_Class() throws IOException {
236 		final int count = analyzer.analyzeAll(
237 				TargetLoader.getClassData(AnalyzerTest.class), "Test");
238 		assertEquals(1, count);
239 		assertClasses("org/jacoco/core/analysis/AnalyzerTest");
240 	}
241 
242 	@Test
testAnalyzeAll_Zip()243 	public void testAnalyzeAll_Zip() throws IOException {
244 		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
245 		final ZipOutputStream zip = new ZipOutputStream(buffer);
246 		zip.putNextEntry(
247 				new ZipEntry("org/jacoco/core/analysis/AnalyzerTest.class"));
248 		zip.write(TargetLoader.getClassDataAsBytes(AnalyzerTest.class));
249 		zip.finish();
250 		final int count = analyzer.analyzeAll(
251 				new ByteArrayInputStream(buffer.toByteArray()), "Test");
252 		assertEquals(1, count);
253 		assertClasses("org/jacoco/core/analysis/AnalyzerTest");
254 	}
255 
256 	@Test
testAnalyzeAll_EmptyZipEntry()257 	public void testAnalyzeAll_EmptyZipEntry() throws IOException {
258 		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
259 		final ZipOutputStream zip = new ZipOutputStream(buffer);
260 		zip.putNextEntry(new ZipEntry("empty.txt"));
261 		zip.finish();
262 		final int count = analyzer.analyzeAll(
263 				new ByteArrayInputStream(buffer.toByteArray()), "Test");
264 		assertEquals(0, count);
265 	}
266 
267 	/**
268 	 * Triggers exception in
269 	 * {@link Analyzer#analyzeAll(java.io.InputStream, String)}.
270 	 */
271 	@Test
testAnalyzeAll_Broken()272 	public void testAnalyzeAll_Broken() throws IOException {
273 		try {
274 			analyzer.analyzeAll(new BrokenInputStream(), "Test");
275 			fail("expected exception");
276 		} catch (IOException e) {
277 			assertEquals("Error while analyzing Test.", e.getMessage());
278 		}
279 	}
280 
281 	/**
282 	 * Triggers exception in
283 	 * {@link Analyzer#analyzeGzip(java.io.InputStream, String)}.
284 	 */
285 	@Test
testAnalyzeAll_BrokenGZ()286 	public void testAnalyzeAll_BrokenGZ() {
287 		final byte[] buffer = new byte[] { 0x1f, (byte) 0x8b, 0x00, 0x00 };
288 		try {
289 			analyzer.analyzeAll(new ByteArrayInputStream(buffer), "Test.gz");
290 			fail("expected exception");
291 		} catch (IOException e) {
292 			assertEquals("Error while analyzing Test.gz.", e.getMessage());
293 		}
294 	}
295 
296 	@Test
testAnalyzeAll_Pack200()297 	public void testAnalyzeAll_Pack200() throws IOException {
298 		try {
299 			Class.forName("java.util.jar.Pack200");
300 		} catch (ClassNotFoundException e) {
301 			throw new AssumptionViolatedException(
302 					"this test requires JDK with Pack200");
303 		}
304 
305 		final ByteArrayOutputStream zipbuffer = new ByteArrayOutputStream();
306 		final ZipOutputStream zip = new ZipOutputStream(zipbuffer);
307 		zip.putNextEntry(
308 				new ZipEntry("org/jacoco/core/analysis/AnalyzerTest.class"));
309 		zip.write(TargetLoader.getClassDataAsBytes(AnalyzerTest.class));
310 		zip.finish();
311 
312 		final ByteArrayOutputStream pack200buffer = new ByteArrayOutputStream();
313 		GZIPOutputStream gzipOutput = new GZIPOutputStream(pack200buffer);
314 		Pack200Streams.pack(zipbuffer.toByteArray(), gzipOutput);
315 		gzipOutput.finish();
316 
317 		final int count = analyzer.analyzeAll(
318 				new ByteArrayInputStream(pack200buffer.toByteArray()), "Test");
319 		assertEquals(1, count);
320 		assertClasses("org/jacoco/core/analysis/AnalyzerTest");
321 	}
322 
323 	/**
324 	 * Triggers exception in
325 	 * {@link Analyzer#analyzePack200(java.io.InputStream, String)}.
326 	 */
327 	@Test
testAnalyzeAll_BrokenPack200()328 	public void testAnalyzeAll_BrokenPack200() {
329 		final byte[] buffer = new byte[] { (byte) 0xca, (byte) 0xfe,
330 				(byte) 0xd0, 0x0d };
331 		try {
332 			analyzer.analyzeAll(new ByteArrayInputStream(buffer),
333 					"Test.pack200");
334 			fail("expected exception");
335 		} catch (IOException e) {
336 			assertEquals("Error while analyzing Test.pack200.", e.getMessage());
337 		}
338 	}
339 
340 	@Test
testAnalyzeAll_Empty()341 	public void testAnalyzeAll_Empty() throws IOException {
342 		final int count = analyzer
343 				.analyzeAll(new ByteArrayInputStream(new byte[0]), "Test");
344 		assertEquals(0, count);
345 		assertEquals(Collections.emptyMap(), classes);
346 	}
347 
348 	@Test
testAnalyzeAll_Folder()349 	public void testAnalyzeAll_Folder() throws IOException {
350 		createClassfile("bin1", AnalyzerTest.class);
351 		final int count = analyzer.analyzeAll(folder.getRoot());
352 		assertEquals(1, count);
353 		assertClasses("org/jacoco/core/analysis/AnalyzerTest");
354 	}
355 
356 	@Test
testAnalyzeAll_Path()357 	public void testAnalyzeAll_Path() throws IOException {
358 		createClassfile("bin1", Analyzer.class);
359 		createClassfile("bin2", AnalyzerTest.class);
360 		String path = "bin1" + File.pathSeparator + "bin2";
361 		final int count = analyzer.analyzeAll(path, folder.getRoot());
362 		assertEquals(2, count);
363 		assertClasses("org/jacoco/core/analysis/Analyzer",
364 				"org/jacoco/core/analysis/AnalyzerTest");
365 	}
366 
367 	/**
368 	 * Triggers exception in
369 	 * {@link Analyzer#nextEntry(java.util.zip.ZipInputStream, String)}.
370 	 */
371 	@Test
testAnalyzeAll_BrokenZip()372 	public void testAnalyzeAll_BrokenZip() {
373 		final byte[] buffer = new byte[30];
374 		buffer[0] = 0x50;
375 		buffer[1] = 0x4b;
376 		buffer[2] = 0x03;
377 		buffer[3] = 0x04;
378 		Arrays.fill(buffer, 4, buffer.length, (byte) 0x42);
379 		try {
380 			analyzer.analyzeAll(new ByteArrayInputStream(buffer), "Test.zip");
381 			fail("expected exception");
382 		} catch (IOException e) {
383 			assertEquals("Error while analyzing Test.zip.", e.getMessage());
384 		}
385 	}
386 
387 	/**
388 	 * With JDK 5 triggers exception in
389 	 * {@link Analyzer#nextEntry(ZipInputStream, String)}, i.e. message will
390 	 * contain only "broken.zip".
391 	 *
392 	 * With JDK > 5 triggers exception in
393 	 * {@link Analyzer#analyzeAll(java.io.InputStream, String)}, i.e. message
394 	 * will contain only "[email protected]".
395 	 */
396 	@Test
testAnalyzeAll_BrokenZipEntry()397 	public void testAnalyzeAll_BrokenZipEntry() throws IOException {
398 		File file = new File(folder.getRoot(), "broken.zip");
399 		OutputStream out = new FileOutputStream(file);
400 		ZipOutputStream zip = new ZipOutputStream(out);
401 		zip.putNextEntry(new ZipEntry("brokenentry.txt"));
402 		out.write(0x23); // Unexpected data here
403 		zip.close();
404 		try {
405 			analyzer.analyzeAll(file);
406 			fail("expected exception");
407 		} catch (IOException e) {
408 			assertTrue(e.getMessage().startsWith("Error while analyzing"));
409 			assertTrue(e.getMessage().contains("broken.zip"));
410 		}
411 	}
412 
413 	/**
414 	 * Triggers exception in
415 	 * {@link Analyzer#analyzeClass(java.io.InputStream, String)}.
416 	 */
417 	@Test
testAnalyzeAll_BrokenClassFileInZip()418 	public void testAnalyzeAll_BrokenClassFileInZip() throws IOException {
419 		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
420 		final ZipOutputStream zip = new ZipOutputStream(buffer);
421 		zip.putNextEntry(
422 				new ZipEntry("org/jacoco/core/analysis/AnalyzerTest.class"));
423 		final byte[] brokenclass = TargetLoader
424 				.getClassDataAsBytes(AnalyzerTest.class);
425 		brokenclass[10] = 0x23;
426 		zip.write(brokenclass);
427 		zip.finish();
428 
429 		try {
430 			analyzer.analyzeAll(new ByteArrayInputStream(buffer.toByteArray()),
431 					"test.zip");
432 			fail("expected exception");
433 		} catch (IOException e) {
434 			assertEquals(
435 					"Error while analyzing test.zip@org/jacoco/core/analysis/AnalyzerTest.class.",
436 					e.getMessage());
437 		}
438 	}
439 
createClassfile(final String dir, final Class<?> source)440 	private void createClassfile(final String dir, final Class<?> source)
441 			throws IOException {
442 		File file = new File(folder.getRoot(), dir);
443 		file.mkdirs();
444 		file = new File(file, "some.class");
445 		OutputStream out = new FileOutputStream(file);
446 		out.write(TargetLoader.getClassDataAsBytes(source));
447 		out.close();
448 	}
449 
assertClasses(String... classNames)450 	private void assertClasses(String... classNames) {
451 		assertEquals(new HashSet<String>(Arrays.asList(classNames)),
452 				classes.keySet());
453 	}
454 
455 }
456