1/*******************************************************************************
2 * Copyright (c) 2009, 2015 Mountainminds GmbH & Co. KG and Contributors
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Marc R. Hoffmann - initial API and implementation
10 *
11 *******************************************************************************/
12package org.jacoco.core.data;
13
14import static org.junit.Assert.assertEquals;
15import static org.junit.Assert.assertFalse;
16import static org.junit.Assert.assertNotNull;
17import static org.junit.Assert.assertTrue;
18import static org.junit.Assert.fail;
19
20import java.io.ByteArrayInputStream;
21import java.io.ByteArrayOutputStream;
22import java.io.IOException;
23import java.io.OutputStream;
24import java.util.Arrays;
25import java.util.Random;
26
27import org.junit.Before;
28import org.junit.Test;
29
30/**
31 * Unit tests for {@link ExecutionDataReader} and {@link ExecutionDataWriter}.
32 * The tests don't care about the written binary format, they just verify
33 * symmetry.
34 */
35public class ExecutionDataReaderWriterTest {
36
37	protected ByteArrayOutputStream buffer;
38
39	private ExecutionDataWriter writer;
40
41	private ExecutionDataStore store;
42
43	private SessionInfo sessionInfo;
44
45	private Random random;
46
47	@Before
48	public void setup() throws IOException {
49		buffer = new ByteArrayOutputStream();
50		writer = createWriter(buffer);
51		store = new ExecutionDataStore();
52		random = new Random(5);
53	}
54
55	@Test
56	public void testEmpty() throws IOException {
57		final ExecutionDataReader reader = createReader();
58		reader.setSessionInfoVisitor(new ISessionInfoVisitor() {
59			public void visitSessionInfo(final SessionInfo info) {
60				fail("No data expected.");
61			}
62		});
63		reader.setExecutionDataVisitor(new IExecutionDataVisitor() {
64			public void visitClassExecution(final ExecutionData data) {
65				fail("No data expected.");
66			}
67		});
68		assertFalse(reader.read());
69	}
70
71	@Test
72	public void testFlush() throws IOException {
73		final boolean[] flushCalled = new boolean[] { false };
74		final OutputStream out = new OutputStream() {
75			@Override
76			public void write(int b) throws IOException {
77			}
78
79			@Override
80			public void flush() throws IOException {
81				flushCalled[0] = true;
82			}
83		};
84		new ExecutionDataWriter(out).flush();
85		assertTrue(flushCalled[0]);
86	}
87
88	@Test
89	public void testCustomBlocks() throws IOException {
90		buffer.write(-22);
91		buffer.write(-33);
92		final ExecutionDataReader reader = new ExecutionDataReader(
93				new ByteArrayInputStream(buffer.toByteArray())) {
94
95			@Override
96			protected boolean readBlock(byte blocktype) throws IOException {
97				switch (blocktype) {
98				case -22:
99					return true;
100				case -33:
101					return false;
102				}
103				return super.readBlock(blocktype);
104			}
105		};
106		assertTrue(reader.read());
107	}
108
109	@Test
110	public void testGetFileHeader() {
111		byte[] header = ExecutionDataWriter.getFileHeader();
112		assertEquals(5, header.length);
113		assertEquals(0x01, 0xFF & header[0]);
114		assertEquals(0xC0, 0xFF & header[1]);
115		assertEquals(0xC0, 0xFF & header[2]);
116		final char version = ExecutionDataWriter.FORMAT_VERSION;
117		assertEquals(version >> 8, 0xFF & header[3]);
118		assertEquals(version & 0xFF, 0xFF & header[4]);
119	}
120
121	@Test
122	public void testMultipleHeaders() throws IOException {
123		new ExecutionDataWriter(buffer);
124		new ExecutionDataWriter(buffer);
125		new ExecutionDataWriter(buffer);
126		assertFalse(createReader().read());
127	}
128
129	@Test(expected = IOException.class)
130	public void testInvalidMagicNumber() throws IOException {
131		buffer = new ByteArrayOutputStream();
132		buffer.write(ExecutionDataWriter.BLOCK_HEADER);
133		buffer.write(0x12);
134		buffer.write(0x34);
135		createReader().read();
136	}
137
138	@Test(expected = IOException.class)
139	public void testInvalidHeaderVersion() throws IOException {
140		buffer = new ByteArrayOutputStream();
141		buffer.write(ExecutionDataWriter.BLOCK_HEADER);
142		buffer.write(0xC0);
143		buffer.write(0xC0);
144		final char version = ExecutionDataWriter.FORMAT_VERSION - 1;
145		buffer.write(version >> 8);
146		buffer.write(version & 0xFF);
147		createReader().read();
148	}
149
150	@Test(expected = IOException.class)
151	public void testMissingHeader() throws IOException {
152		buffer.reset();
153		writer.visitClassExecution(new ExecutionData(Long.MIN_VALUE, "Sample",
154				createData(0)));
155		createReaderWithVisitors().read();
156	}
157
158	@Test(expected = IOException.class)
159	public void testUnknownBlock() throws IOException {
160		buffer.write(0xff);
161		createReader().read();
162	}
163
164	@Test
165	public void testEmptyFile() throws IOException {
166		buffer = new ByteArrayOutputStream();
167		createReader().read();
168	}
169
170	// === Session Info ===
171
172	@Test(expected = IOException.class)
173	public void testNoSessionInfoVisitor() throws IOException {
174		writer.visitSessionInfo(new SessionInfo("x", 0, 1));
175		createReader().read();
176	}
177
178	@Test
179	public void testSessionInfo() throws IOException {
180		writer.visitSessionInfo(new SessionInfo("TestSession",
181				2837123124567891234L, 3444234223498879234L));
182		assertFalse(createReaderWithVisitors().read());
183		assertNotNull(sessionInfo);
184		assertEquals("TestSession", sessionInfo.getId());
185		assertEquals(2837123124567891234L, sessionInfo.getStartTimeStamp());
186		assertEquals(3444234223498879234L, sessionInfo.getDumpTimeStamp());
187	}
188
189	@Test(expected = RuntimeException.class)
190	public void testSessionInfoIOException() throws IOException {
191		final boolean[] broken = new boolean[1];
192		final ExecutionDataWriter writer = createWriter(new OutputStream() {
193			@Override
194			public void write(int b) throws IOException {
195				if (broken[0]) {
196					throw new IOException();
197				}
198			}
199		});
200		broken[0] = true;
201		writer.visitSessionInfo(new SessionInfo("X", 0, 0));
202	}
203
204	// === Execution Data ===
205
206	@Test(expected = IOException.class)
207	public void testNoExecutionDataVisitor() throws IOException {
208		writer.visitClassExecution(new ExecutionData(Long.MIN_VALUE, "Sample",
209				createData(0)));
210		createReader().read();
211	}
212
213	@Test
214	public void testMinClassId() throws IOException {
215		final boolean[] data = createData(0);
216		writer.visitClassExecution(new ExecutionData(Long.MIN_VALUE, "Sample",
217				data));
218		assertFalse(createReaderWithVisitors().read());
219		assertArrayEquals(data, store.get(Long.MIN_VALUE).getProbes());
220	}
221
222	@Test
223	public void testMaxClassId() throws IOException {
224		final boolean[] data = createData(0);
225		writer.visitClassExecution(new ExecutionData(Long.MAX_VALUE, "Sample",
226				data));
227		assertFalse(createReaderWithVisitors().read());
228		assertArrayEquals(data, store.get(Long.MAX_VALUE).getProbes());
229	}
230
231	@Test
232	public void testEmptyClass() throws IOException {
233		final boolean[] data = createData(0);
234		writer.visitClassExecution(new ExecutionData(3, "Sample", data));
235		assertFalse(createReaderWithVisitors().read());
236		assertArrayEquals(data, store.get(3).getProbes());
237	}
238
239	@Test
240	public void testOneClass() throws IOException {
241		final boolean[] data = createData(5);
242		writer.visitClassExecution(new ExecutionData(3, "Sample", data));
243		assertFalse(createReaderWithVisitors().read());
244		assertArrayEquals(data, store.get(3).getProbes());
245	}
246
247	@Test
248	public void testTwoClasses() throws IOException {
249		final boolean[] data1 = createData(5);
250		final boolean[] data2 = createData(7);
251		writer.visitClassExecution(new ExecutionData(333, "Sample", data1));
252		writer.visitClassExecution(new ExecutionData(-45, "Sample", data2));
253		assertFalse(createReaderWithVisitors().read());
254		assertArrayEquals(data1, store.get(333).getProbes());
255		assertArrayEquals(data2, store.get(-45).getProbes());
256	}
257
258	@Test
259	public void testBigClass() throws IOException {
260		final boolean[] data = createData(117);
261		writer.visitClassExecution(new ExecutionData(123, "Sample", data));
262		assertFalse(createReaderWithVisitors().read());
263		assertArrayEquals(data, store.get(123).getProbes());
264	}
265
266	@Test(expected = RuntimeException.class)
267	public void testExecutionDataIOException() throws IOException {
268		final boolean[] broken = new boolean[1];
269		final ExecutionDataWriter writer = createWriter(new OutputStream() {
270			@Override
271			public void write(int b) throws IOException {
272				if (broken[0]) {
273					throw new IOException();
274				}
275			}
276		});
277		broken[0] = true;
278		writer.visitClassExecution(new ExecutionData(3, "Sample", createData(1)));
279	}
280
281	private ExecutionDataReader createReaderWithVisitors() throws IOException {
282		final ExecutionDataReader reader = createReader();
283		reader.setExecutionDataVisitor(store);
284		reader.setSessionInfoVisitor(new ISessionInfoVisitor() {
285			public void visitSessionInfo(SessionInfo info) {
286				sessionInfo = info;
287			}
288		});
289		return reader;
290	}
291
292	private boolean[] createData(final int probeCount) {
293		final boolean[] data = new boolean[random.nextInt(probeCount + 1)];
294		for (int j = 0; j < data.length; j++) {
295			data[j] = random.nextBoolean();
296		}
297		return data;
298	}
299
300	private void assertArrayEquals(final boolean[] expected,
301			final boolean[] actual) {
302		assertTrue(Arrays.equals(expected, actual));
303	}
304
305	protected ExecutionDataWriter createWriter(OutputStream out)
306			throws IOException {
307		return new ExecutionDataWriter(out);
308	}
309
310	protected ExecutionDataReader createReader() throws IOException {
311		return new ExecutionDataReader(new ByteArrayInputStream(
312				buffer.toByteArray()));
313	}
314
315}
316