Add location to IOException when unable to read input during analysis (#400)
diff --git a/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java b/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java
index bef1079..89ed34b 100644
--- a/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java
+++ b/org.jacoco.core.test/src/org/jacoco/core/analysis/AnalyzerTest.java
@@ -15,11 +15,13 @@
 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;
@@ -39,7 +41,6 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 
 /**
@@ -50,9 +51,6 @@
 	@Rule
 	public TemporaryFolder folder = new TemporaryFolder();
 
-	@Rule
-	public ExpectedException expected = ExpectedException.none();
-
 	private Analyzer analyzer;
 
 	private Map<String, IClassCoverage> classes;
@@ -114,13 +112,15 @@
 
 	@Test
 	public void testAnalyzeClass_Broken() throws IOException {
-		expected.expect(IOException.class);
-		expected.expectMessage("Error while analyzing Broken.class.");
-
 		final byte[] brokenclass = TargetLoader
 				.getClassDataAsBytes(AnalyzerTest.class);
 		brokenclass[10] = 0x23;
-		analyzer.analyzeClass(brokenclass, "Broken.class");
+		try {
+			analyzer.analyzeClass(brokenclass, "Broken.class");
+			fail("expected exception");
+		} catch (IOException e) {
+			assertEquals("Error while analyzing Broken.class.", e.getMessage());
+		}
 	}
 
 	@Test
@@ -156,6 +156,40 @@
 		assertEquals(0, count);
 	}
 
+	/**
+	 * Triggers exception in
+	 * {@link Analyzer#analyzeAll(java.io.InputStream, String)}.
+	 */
+	@Test
+	public void testAnalyzeAll_Broken() throws IOException {
+		try {
+			analyzer.analyzeAll(new InputStream() {
+				@Override
+				public int read() throws IOException {
+					throw new IOException();
+				}
+			}, "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();
@@ -178,6 +212,23 @@
 		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(
@@ -205,22 +256,58 @@
 				"org/jacoco/core/analysis/AnalyzerTest");
 	}
 
-	@Test(expected = IOException.class)
-	public void testAnalyzeAll_BrokenZip() throws IOException {
+	/**
+	 * 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();
-		analyzer.analyzeAll(file);
+		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 {
-		expected.expect(IOException.class);
-		expected.expectMessage("Error while analyzing test.zip@org/jacoco/core/analysis/AnalyzerTest.class.");
-
 		final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
 		final ZipOutputStream zip = new ZipOutputStream(buffer);
 		zip.putNextEntry(new ZipEntry(
@@ -231,8 +318,15 @@
 		zip.write(brokenclass);
 		zip.finish();
 
-		analyzer.analyzeAll(new ByteArrayInputStream(buffer.toByteArray()),
-				"test.zip");
+		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)
diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java b/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
index 5fd859e..ac86128 100644
--- a/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
+++ b/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java
@@ -149,7 +149,7 @@
 	}
 
 	private IOException analyzerError(final String location,
-			final RuntimeException cause) {
+			final Exception cause) {
 		final IOException ex = new IOException(String.format(
 				"Error while analyzing %s.", location));
 		ex.initCause(cause);
@@ -172,7 +172,12 @@
 	 */
 	public int analyzeAll(final InputStream input, final String location)
 			throws IOException {
-		final ContentTypeDetector detector = new ContentTypeDetector(input);
+		final ContentTypeDetector detector;
+		try {
+			detector = new ContentTypeDetector(input);
+		} catch (IOException e) {
+			throw analyzerError(location, e);
+		}
 		switch (detector.getType()) {
 		case ContentTypeDetector.CLASSFILE:
 			analyzeClass(detector.getInputStream(), location);
@@ -233,7 +238,8 @@
 	public int analyzeAll(final String path, final File basedir)
 			throws IOException {
 		int count = 0;
-		final StringTokenizer st = new StringTokenizer(path, File.pathSeparator);
+		final StringTokenizer st = new StringTokenizer(path,
+				File.pathSeparator);
 		while (st.hasMoreTokens()) {
 			count += analyzeAll(new File(basedir, st.nextToken()));
 		}
@@ -245,20 +251,41 @@
 		final ZipInputStream zip = new ZipInputStream(input);
 		ZipEntry entry;
 		int count = 0;
-		while ((entry = zip.getNextEntry()) != null) {
+		while ((entry = nextEntry(zip, location)) != null) {
 			count += analyzeAll(zip, location + "@" + entry.getName());
 		}
 		return count;
 	}
 
+	private ZipEntry nextEntry(ZipInputStream input, String location)
+			throws IOException {
+		try {
+			return input.getNextEntry();
+		} catch (IOException e) {
+			throw analyzerError(location, e);
+		}
+	}
+
 	private int analyzeGzip(final InputStream input, final String location)
 			throws IOException {
-		return analyzeAll(new GZIPInputStream(input), location);
+		GZIPInputStream gzipInputStream;
+		try {
+			gzipInputStream = new GZIPInputStream(input);
+		} catch (IOException e) {
+			throw analyzerError(location, e);
+		}
+		return analyzeAll(gzipInputStream, location);
 	}
 
 	private int analyzePack200(final InputStream input, final String location)
 			throws IOException {
-		return analyzeAll(Pack200Streams.unpack(input), location);
+		InputStream unpackedInput;
+		try {
+			unpackedInput = Pack200Streams.unpack(input);
+		} catch (IOException e) {
+			throw analyzerError(location, e);
+		}
+		return analyzeAll(unpackedInput, location);
 	}
 
 }
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index 71a1354..60d4a63 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -43,6 +43,9 @@
   <li>Empty probe arrays are not written to execution data files any more. This
       reduces exec file size significantly for per-test data dumps.
       (GitHub <a href="https://github.com/jacoco/jacoco/issues/387">#387</a>).</li>
+  <li>More information about context is provided when unable to read input during
+      analysis.
+      (GitHub <a href="https://github.com/jacoco/jacoco/issues/400">#400</a>).</li>
   <li>Require at least Maven 3.0 for build of JaCoCo.</li>
 </ul>