/*******************************************************************************
* Copyright (c) 2009, 2017 Mountainminds GmbH & Co. KG and Contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Marc R. Hoffmann - initial API and implementation
*
*******************************************************************************/
package org.jacoco.core.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;
import org.junit.Before;
import org.junit.Test;
/**
* Unit tests for {@link ExecutionDataReader} and {@link ExecutionDataWriter}.
* The tests don't care about the written binary format, they just verify
* symmetry.
*/
public class ExecutionDataReaderWriterTest {
protected ByteArrayOutputStream buffer;
private ExecutionDataWriter writer;
private ExecutionDataStore store;
private SessionInfo sessionInfo;
private Random random;
@Before
public void setup() throws IOException {
buffer = new ByteArrayOutputStream();
writer = createWriter(buffer);
store = new ExecutionDataStore();
random = new Random(5);
}
@Test
public void testEmpty() throws IOException {
final ExecutionDataReader reader = createReader();
reader.setSessionInfoVisitor(new ISessionInfoVisitor() {
public void visitSessionInfo(final SessionInfo info) {
fail("No data expected.");
}
});
reader.setExecutionDataVisitor(new IExecutionDataVisitor() {
public void visitClassExecution(final ExecutionData data) {
fail("No data expected.");
}
});
assertFalse(reader.read());
}
@Test
public void testFlush() throws IOException {
final boolean[] flushCalled = new boolean[] { false };
final OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
}
@Override
public void flush() throws IOException {
flushCalled[0] = true;
}
};
new ExecutionDataWriter(out).flush();
assertTrue(flushCalled[0]);
}
@Test
public void testCustomBlocks() throws IOException {
buffer.write(0x55);
buffer.write(0x66);
final ExecutionDataReader reader = new ExecutionDataReader(
new ByteArrayInputStream(buffer.toByteArray())) {
@Override
protected boolean readBlock(byte blocktype) throws IOException {
switch (blocktype) {
case 0x55:
return true;
case 0x66:
return false;
}
return super.readBlock(blocktype);
}
};
assertTrue(reader.read());
}
@Test
public void testGetFileHeader() {
byte[] header = ExecutionDataWriter.getFileHeader();
assertEquals(5, header.length);
assertEquals(0x01, 0xFF & header[0]);
assertEquals(0xC0, 0xFF & header[1]);
assertEquals(0xC0, 0xFF & header[2]);
final char version = ExecutionDataWriter.FORMAT_VERSION;
assertEquals(version >> 8, 0xFF & header[3]);
assertEquals(version & 0xFF, 0xFF & header[4]);
}
@Test
public void testMultipleHeaders() throws IOException {
new ExecutionDataWriter(buffer);
new ExecutionDataWriter(buffer);
new ExecutionDataWriter(buffer);
assertFalse(createReader().read());
}
@Test(expected = IOException.class)
public void testInvalidMagicNumber() throws IOException {
buffer = new ByteArrayOutputStream();
buffer.write(ExecutionDataWriter.BLOCK_HEADER);
buffer.write(0x12);
buffer.write(0x34);
createReader().read();
}
@Test(expected = IncompatibleExecDataVersionException.class)
public void testInvalidVersion() throws IOException {
buffer = new ByteArrayOutputStream();
buffer.write(ExecutionDataWriter.BLOCK_HEADER);
buffer.write(0xC0);
buffer.write(0xC0);
final char version = (char) (ExecutionDataWriter.FORMAT_VERSION - 1);
buffer.write(version >> 8);
buffer.write(version & 0xFF);
createReader().read();
}
@Test(expected = IOException.class)
public void testMissingHeader() throws IOException {
buffer.reset();
writer.visitClassExecution(new ExecutionData(Long.MIN_VALUE, "Sample",
createData(8)));
createReaderWithVisitors().read();
}
@Test(expected = IOException.class)
public void testUnknownBlock() throws IOException {
buffer.write(0xff);
createReader().read();
}
@Test(expected = EOFException.class)
public void testTruncatedFile() throws IOException {
writer.visitClassExecution(new ExecutionData(Long.MIN_VALUE, "Sample",
createData(8)));
final byte[] content = buffer.toByteArray();
buffer.reset();
buffer.write(content, 0, content.length - 1);
createReaderWithVisitors().read();
}
@Test
public void testEmptyFile() throws IOException {
buffer.reset();
createReader().read();
}
// === Session Info ===
@Test(expected = IOException.class)
public void testNoSessionInfoVisitor() throws IOException {
writer.visitSessionInfo(new SessionInfo("x", 0, 1));
createReader().read();
}
@Test
public void testSessionInfo() throws IOException {
writer.visitSessionInfo(new SessionInfo("TestSession",
2837123124567891234L, 3444234223498879234L));
assertFalse(createReaderWithVisitors().read());
assertNotNull(sessionInfo);
assertEquals("TestSession", sessionInfo.getId());
assertEquals(2837123124567891234L, sessionInfo.getStartTimeStamp());
assertEquals(3444234223498879234L, sessionInfo.getDumpTimeStamp());
}
@Test(expected = RuntimeException.class)
public void testSessionInfoIOException() throws IOException {
final boolean[] broken = new boolean[1];
final ExecutionDataWriter writer = createWriter(new OutputStream() {
@Override
public void write(int b) throws IOException {
if (broken[0]) {
throw new IOException();
}
}
});
broken[0] = true;
writer.visitSessionInfo(new SessionInfo("X", 0, 0));
}
// === Execution Data ===
@Test(expected = IOException.class)
public void testNoExecutionDataVisitor() throws IOException {
writer.visitClassExecution(new ExecutionData(Long.MIN_VALUE, "Sample",
createData(8)));
createReader().read();
}
@Test
public void testMinClassId() throws IOException {
final boolean[] data = createData(8);
writer.visitClassExecution(new ExecutionData(Long.MIN_VALUE, "Sample",
data));
assertFalse(createReaderWithVisitors().read());
assertArrayEquals(data, store.get(Long.MIN_VALUE).getProbes());
}
@Test
public void testMaxClassId() throws IOException {
final boolean[] data = createData(8);
writer.visitClassExecution(new ExecutionData(Long.MAX_VALUE, "Sample",
data));
assertFalse(createReaderWithVisitors().read());
assertArrayEquals(data, store.get(Long.MAX_VALUE).getProbes());
}
@Test
public void testEmptyClass() throws IOException {
final boolean[] data = createData(0);
writer.visitClassExecution(new ExecutionData(3, "Sample", data));
assertFalse(createReaderWithVisitors().read());
assertTrue(store.getContents().isEmpty());
}
@Test
public void testNoHitClass() throws IOException {
final boolean[] data = new boolean[] { false, false, false };
writer.visitClassExecution(new ExecutionData(3, "Sample", data));
assertFalse(createReaderWithVisitors().read());
assertTrue(store.getContents().isEmpty());
}
@Test
public void testOneClass() throws IOException {
final boolean[] data = createData(15);
writer.visitClassExecution(new ExecutionData(3, "Sample", data));
assertFalse(createReaderWithVisitors().read());
assertArrayEquals(data, store.get(3).getProbes());
}
@Test
public void testTwoClasses() throws IOException {
final boolean[] data1 = createData(15);
final boolean[] data2 = createData(185);
writer.visitClassExecution(new ExecutionData(333, "Sample", data1));
writer.visitClassExecution(new ExecutionData(-45, "Sample", data2));
assertFalse(createReaderWithVisitors().read());
assertArrayEquals(data1, store.get(333).getProbes());
assertArrayEquals(data2, store.get(-45).getProbes());
}
@Test
public void testBigClass() throws IOException {
final boolean[] data = createData(3599);
writer.visitClassExecution(new ExecutionData(123, "Sample", data));
assertFalse(createReaderWithVisitors().read());
assertArrayEquals(data, store.get(123).getProbes());
}
@Test(expected = RuntimeException.class)
public void testExecutionDataIOException() throws IOException {
final boolean[] broken = new boolean[1];
final ExecutionDataWriter writer = createWriter(new OutputStream() {
@Override
public void write(int b) throws IOException {
if (broken[0]) {
throw new IOException();
}
}
});
broken[0] = true;
writer.visitClassExecution(new ExecutionData(3, "Sample", createData(1)));
}
private ExecutionDataReader createReaderWithVisitors() throws IOException {
final ExecutionDataReader reader = createReader();
reader.setExecutionDataVisitor(store);
reader.setSessionInfoVisitor(new ISessionInfoVisitor() {
public void visitSessionInfo(SessionInfo info) {
sessionInfo = info;
}
});
return reader;
}
private boolean[] createData(final int probeCount) {
final boolean[] data = new boolean[probeCount];
for (int j = 0; j < data.length; j++) {
data[j] = random.nextBoolean();
}
return data;
}
private void assertArrayEquals(final boolean[] expected,
final boolean[] actual) {
assertTrue(Arrays.equals(expected, actual));
}
protected ExecutionDataWriter createWriter(OutputStream out)
throws IOException {
return new ExecutionDataWriter(out);
}
protected ExecutionDataReader createReader() throws IOException {
return new ExecutionDataReader(new ByteArrayInputStream(
buffer.toByteArray()));
}
}