Java tutorial
/******************************************************************************* * Copyright (c) 2009, 2019 Mountainminds GmbH & Co. KG and Contributors * This program and the accompanying materials are made available under * the terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0 * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * Marc R. Hoffmann - initial API and implementation * *******************************************************************************/ package org.jacoco.core.analysis; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.jar.JarInputStream; import java.util.jar.Pack200; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.jacoco.core.data.ExecutionDataStore; import org.jacoco.core.internal.data.CRC64; import org.jacoco.core.test.TargetLoader; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; /** * Unit tests for {@link Analyzer}. */ public class AnalyzerTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); private Analyzer analyzer; private Map<String, IClassCoverage> classes; private ExecutionDataStore executionData; private class EmptyStructureVisitor implements ICoverageVisitor { public void visitCoverage(IClassCoverage coverage) { final String name = coverage.getName(); assertNull("Class already processed: " + name, classes.put(name, coverage)); } } @Before public void setup() { classes = new HashMap<String, IClassCoverage>(); executionData = new ExecutionDataStore(); analyzer = new Analyzer(executionData, new EmptyStructureVisitor()); } @Test public void should_ignore_module_info() throws Exception { final ClassWriter cw = new ClassWriter(0); cw.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, null); cw.visitModule("module", 0, null).visitEnd(); cw.visitEnd(); final byte[] bytes = cw.toByteArray(); analyzer.analyzeClass(bytes, ""); assertTrue(classes.isEmpty()); } @Test public void should_ignore_synthetic_classes() throws Exception { final ClassWriter cw = new ClassWriter(0); cw.visit(Opcodes.V1_5, Opcodes.ACC_SYNTHETIC, "Foo", null, "java/lang/Object", null); cw.visitEnd(); final byte[] bytes = cw.toByteArray(); analyzer.analyzeClass(bytes, ""); assertTrue(classes.isEmpty()); } @Test public void should_not_modify_class_bytes_to_support_next_version() throws Exception { final byte[] originalBytes = createClass(Opcodes.V13 + 1); final byte[] bytes = new byte[originalBytes.length]; System.arraycopy(originalBytes, 0, bytes, 0, originalBytes.length); final long expectedClassId = CRC64.classId(bytes); analyzer.analyzeClass(bytes, ""); assertArrayEquals(originalBytes, bytes); assertEquals(expectedClassId, classes.get("Foo").getId()); } private static byte[] createClass(final int version) { final ClassWriter cw = new ClassWriter(0); cw.visit(version, 0, "Foo", null, "java/lang/Object", null); cw.visitEnd(); return cw.toByteArray(); } /** * @see #analyzeAll_should_throw_exception_for_unsupported_class_file_version() */ @Test public void analyzeClass_should_throw_exception_for_unsupported_class_file_version() { final byte[] bytes = createClass(Opcodes.V14 + 1); try { analyzer.analyzeClass(bytes, "UnsupportedVersion"); fail("exception expected"); } catch (IOException e) { assertEquals("Error while analyzing UnsupportedVersion.", e.getMessage()); assertEquals("Unsupported class file major version 59", e.getCause().getMessage()); } } @Test public void testAnalyzeClassFromStream() throws IOException { analyzer.analyzeClass(TargetLoader.getClassData(AnalyzerTest.class), "Test"); assertClasses("org/jacoco/core/analysis/AnalyzerTest"); } @Test public void testAnalyzeClassFromByteArray() throws IOException { analyzer.analyzeClass(TargetLoader.getClassDataAsBytes(AnalyzerTest.class), "Test"); assertClasses("org/jacoco/core/analysis/AnalyzerTest"); assertFalse(classes.get("org/jacoco/core/analysis/AnalyzerTest").isNoMatch()); } @Test public void testAnalyzeClassIdMatch() throws IOException { final byte[] bytes = TargetLoader.getClassDataAsBytes(AnalyzerTest.class); executionData.get(Long.valueOf(CRC64.classId(bytes)), "org/jacoco/core/analysis/AnalyzerTest", 200); analyzer.analyzeClass(bytes, "Test"); assertFalse(classes.get("org/jacoco/core/analysis/AnalyzerTest").isNoMatch()); } @Test public void testAnalyzeClassNoIdMatch() throws IOException { executionData.get(Long.valueOf(0), "org/jacoco/core/analysis/AnalyzerTest", 200); analyzer.analyzeClass(TargetLoader.getClassDataAsBytes(AnalyzerTest.class), "Test"); assertTrue(classes.get("org/jacoco/core/analysis/AnalyzerTest").isNoMatch()); } @Test public void testAnalyzeClass_Broken() throws IOException { final byte[] brokenclass = TargetLoader.getClassDataAsBytes(AnalyzerTest.class); brokenclass[10] = 0x23; try { analyzer.analyzeClass(brokenclass, "Broken.class"); fail("expected exception"); } catch (IOException e) { assertEquals("Error while analyzing Broken.class.", e.getMessage()); } } private static class BrokenInputStream extends InputStream { @Override public int read() throws IOException { throw new IOException(); } } /** * Triggers exception in {@link Analyzer#analyzeClass(InputStream, String)}. */ @Test public void testAnalyzeClass_BrokenStream() throws IOException { try { analyzer.analyzeClass(new BrokenInputStream(), "BrokenStream"); fail("exception expected"); } catch (IOException e) { assertEquals("Error while analyzing BrokenStream.", e.getMessage()); } } /** * @see #analyzeClass_should_throw_exception_for_unsupported_class_file_version() */ @Test public void analyzeAll_should_throw_exception_for_unsupported_class_file_version() { final byte[] bytes = createClass(Opcodes.V14 + 1); try { analyzer.analyzeAll(new ByteArrayInputStream(bytes), "UnsupportedVersion"); fail("exception expected"); } catch (IOException e) { assertEquals("Error while analyzing UnsupportedVersion.", e.getMessage()); assertEquals("Unsupported class file major version 59", e.getCause().getMessage()); } } @Test public void testAnalyzeAll_Class() throws IOException { final int count = analyzer.analyzeAll(TargetLoader.getClassData(AnalyzerTest.class), "Test"); assertEquals(1, count); assertClasses("org/jacoco/core/analysis/AnalyzerTest"); } @Test public void testAnalyzeAll_Zip() throws IOException { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final ZipOutputStream zip = new ZipOutputStream(buffer); zip.putNextEntry(new ZipEntry("org/jacoco/core/analysis/AnalyzerTest.class")); zip.write(TargetLoader.getClassDataAsBytes(AnalyzerTest.class)); zip.finish(); final int count = analyzer.analyzeAll(new ByteArrayInputStream(buffer.toByteArray()), "Test"); assertEquals(1, count); assertClasses("org/jacoco/core/analysis/AnalyzerTest"); } @Test public void testAnalyzeAll_EmptyZipEntry() throws IOException { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final ZipOutputStream zip = new ZipOutputStream(buffer); zip.putNextEntry(new ZipEntry("empty.txt")); zip.finish(); final int count = analyzer.analyzeAll(new ByteArrayInputStream(buffer.toByteArray()), "Test"); assertEquals(0, count); } /** * Triggers exception in * {@link Analyzer#analyzeAll(java.io.InputStream, String)}. */ @Test public void testAnalyzeAll_Broken() throws IOException { try { analyzer.analyzeAll(new BrokenInputStream(), "Test"); fail("expected exception"); } catch (IOException e) { assertEquals("Error while analyzing Test.", e.getMessage()); } } /** * Triggers exception in * {@link Analyzer#analyzeGzip(java.io.InputStream, String)}. */ @Test public void testAnalyzeAll_BrokenGZ() { final byte[] buffer = new byte[] { 0x1f, (byte) 0x8b, 0x00, 0x00 }; try { analyzer.analyzeAll(new ByteArrayInputStream(buffer), "Test.gz"); fail("expected exception"); } catch (IOException e) { assertEquals("Error while analyzing Test.gz.", e.getMessage()); } } @Test public void testAnalyzeAll_Pack200() throws IOException { final ByteArrayOutputStream zipbuffer = new ByteArrayOutputStream(); final ZipOutputStream zip = new ZipOutputStream(zipbuffer); zip.putNextEntry(new ZipEntry("org/jacoco/core/analysis/AnalyzerTest.class")); zip.write(TargetLoader.getClassDataAsBytes(AnalyzerTest.class)); zip.finish(); final ByteArrayOutputStream pack200buffer = new ByteArrayOutputStream(); GZIPOutputStream gzipOutput = new GZIPOutputStream(pack200buffer); Pack200.newPacker().pack(new JarInputStream(new ByteArrayInputStream(zipbuffer.toByteArray())), gzipOutput); gzipOutput.finish(); final int count = analyzer.analyzeAll(new ByteArrayInputStream(pack200buffer.toByteArray()), "Test"); assertEquals(1, count); assertClasses("org/jacoco/core/analysis/AnalyzerTest"); } /** * Triggers exception in * {@link Analyzer#analyzePack200(java.io.InputStream, String)}. */ @Test public void testAnalyzeAll_BrokenPack200() { final byte[] buffer = new byte[] { (byte) 0xca, (byte) 0xfe, (byte) 0xd0, 0x0d }; try { analyzer.analyzeAll(new ByteArrayInputStream(buffer), "Test.pack200"); fail("expected exception"); } catch (IOException e) { assertEquals("Error while analyzing Test.pack200.", e.getMessage()); } } @Test public void testAnalyzeAll_Empty() throws IOException { final int count = analyzer.analyzeAll(new ByteArrayInputStream(new byte[0]), "Test"); assertEquals(0, count); assertEquals(Collections.emptyMap(), classes); } @Test public void testAnalyzeAll_Folder() throws IOException { createClassfile("bin1", AnalyzerTest.class); final int count = analyzer.analyzeAll(folder.getRoot()); assertEquals(1, count); assertClasses("org/jacoco/core/analysis/AnalyzerTest"); } @Test public void testAnalyzeAll_Path() throws IOException { createClassfile("bin1", Analyzer.class); createClassfile("bin2", AnalyzerTest.class); String path = "bin1" + File.pathSeparator + "bin2"; final int count = analyzer.analyzeAll(path, folder.getRoot()); assertEquals(2, count); assertClasses("org/jacoco/core/analysis/Analyzer", "org/jacoco/core/analysis/AnalyzerTest"); } /** * Triggers exception in * {@link Analyzer#nextEntry(java.util.zip.ZipInputStream, String)}. */ @Test public void testAnalyzeAll_BrokenZip() { final byte[] buffer = new byte[30]; buffer[0] = 0x50; buffer[1] = 0x4b; buffer[2] = 0x03; buffer[3] = 0x04; Arrays.fill(buffer, 4, buffer.length, (byte) 0x42); try { analyzer.analyzeAll(new ByteArrayInputStream(buffer), "Test.zip"); fail("expected exception"); } catch (IOException e) { assertEquals("Error while analyzing Test.zip.", e.getMessage()); } } /** * With JDK 5 triggers exception in * {@link Analyzer#nextEntry(ZipInputStream, String)}, i.e. message will * contain only "broken.zip". * * With JDK > 5 triggers exception in * {@link Analyzer#analyzeAll(java.io.InputStream, String)}, i.e. message * will contain only "broken.zip@brokenentry.txt". */ @Test public void testAnalyzeAll_BrokenZipEntry() throws IOException { File file = new File(folder.getRoot(), "broken.zip"); OutputStream out = new FileOutputStream(file); ZipOutputStream zip = new ZipOutputStream(out); zip.putNextEntry(new ZipEntry("brokenentry.txt")); out.write(0x23); // Unexpected data here zip.close(); try { analyzer.analyzeAll(file); fail("expected exception"); } catch (IOException e) { assertTrue(e.getMessage().startsWith("Error while analyzing")); assertTrue(e.getMessage().contains("broken.zip")); } } /** * Triggers exception in * {@link Analyzer#analyzeClass(java.io.InputStream, String)}. */ @Test public void testAnalyzeAll_BrokenClassFileInZip() throws IOException { final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); final ZipOutputStream zip = new ZipOutputStream(buffer); zip.putNextEntry(new ZipEntry("org/jacoco/core/analysis/AnalyzerTest.class")); final byte[] brokenclass = TargetLoader.getClassDataAsBytes(AnalyzerTest.class); brokenclass[10] = 0x23; zip.write(brokenclass); zip.finish(); try { analyzer.analyzeAll(new ByteArrayInputStream(buffer.toByteArray()), "test.zip"); fail("expected exception"); } catch (IOException e) { assertEquals("Error while analyzing test.zip@org/jacoco/core/analysis/AnalyzerTest.class.", e.getMessage()); } } private void createClassfile(final String dir, final Class<?> source) throws IOException { File file = new File(folder.getRoot(), dir); file.mkdirs(); file = new File(file, "some.class"); OutputStream out = new FileOutputStream(file); out.write(TargetLoader.getClassDataAsBytes(source)); out.close(); } private void assertClasses(String... classNames) { assertEquals(new HashSet<String>(Arrays.asList(classNames)), classes.keySet()); } }