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