content: package io.activej.serializer.stream;
  
  import io.activej.serializer.stream.StreamCodecs.SubtypeStreamCodec;
  import org.junit.Test;
  import org.junit.experimental.theories.DataPoints;
  import org.junit.experimental.theories.FromDataPoints;
  import org.junit.experimental.theories.Theories;
  import org.junit.experimental.theories.Theory;
  import org.junit.runner.RunWith;
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.util.*;
  import java.util.concurrent.ThreadLocalRandom;
  
  import static org.junit.Assert.*;
  import static org.junit.Assume.assumeTrue;
  
  @RunWith(Theories.class)
  public class StreamCodecsTest {
  
  	@DataPoints("bufferSizes")
  	public static int[] BUFFER_SIZES = new int[]{1, 2, 3, 5, 10, 100, StreamOutput.DEFAULT_BUFFER_SIZE, 2 * StreamOutput.DEFAULT_BUFFER_SIZE};
  
  	@DataPoints("containerSizes")
  	public static int[] CONTAINER_SIZES = new int[]{0, 1, 2, 3, 5, 10, 1_000, 1_000_000};
  
  	@Theory
  	public void ofString(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<String> codec = StreamCodecs.ofString();
  		String string = "test";
  		String result = doTest(codec, string, readBufferSize, writeBufferSize);
  
  		assertEquals(string, result);
  	}
  
  	@Theory
  	public void ofArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<String[]> codec = StreamCodecs.ofArray(StreamCodecs.ofString(), String[]::new);
  		String[] strings = new String[arraySize];
  		for (int i = 0; i < arraySize; i++) {
  			strings[i] = String.valueOf(i);
  		}
  		String[] result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(strings, result);
  	}
  
  	@Theory
  	public void ofArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize > 100) && writeBufferSize > 100);
  
  		StreamCodec<String[]> codec = StreamCodecs.ofArray(StreamCodecs.ofString(), String[]::new);
  		String[] strings = new String[arraySize];
  		for (int i = 0; i < arraySize; i++) {
  			strings[i] = String.valueOf(i);
  		}
  		String[] result = doTestWithAdditionalData(codec, strings, readBufferSize, writeBufferSize, 100, 100);
  
  		assertArrayEquals(strings, result);
  	}
  
  	@Theory
  	public void ofHeterogeneousArray(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<Object[]> codec = StreamCodecs.ofArray(value -> switch (value % 3) {
  			case 0 -> StreamCodecs.ofInt();
  			case 1 -> StreamCodecs.ofString();
  			case 2 -> StreamCodecs.ofBoolean();
  			default -> throw new AssertionError();
  		}, Object[]::new);
  		Object[] objects = {0, "x", true, 12, "y", false, 34, "11241"};
  		Object[] result = doTest(codec, objects, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(objects, result);
  	}
  
  
  	@Theory
  	public void ofFixedLengthArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<String[]> codec = StreamCodecs.ofFixedLengthArray(StreamCodecs.ofString(), () -> new String[arraySize], arraySize);
  		String[] strings = new String[arraySize];
  		for (int i = 0; i < arraySize; i++) {
  			strings[i] = String.valueOf(i);
  		}
  		String[] result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(strings, result);
  	}
  
  	@Theory
  	public void ofFixedLengthArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize > 100) && writeBufferSize > 100);
  
  		StreamCodec<String[]> codec = StreamCodecs.ofFixedLengthArray(StreamCodecs.ofString(), () -> new String[arraySize], arraySize);
  		String[] strings = new String[arraySize];
  		for (int i = 0; i < arraySize; i++) {
  			strings[i] = String.valueOf(i);
  		}
  		String[] result = doTestWithAdditionalData(codec, strings, readBufferSize, writeBufferSize, 100, 100);
  
  		assertArrayEquals(strings, result);
  	}
  
  	@Theory
  	public void ofFixedLengthHeterogeneousArray(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		Object[] objects = {0, "x", true, 12, "y", false, 34, "11241"};
  
  		int length = objects.length;
  		StreamCodec<Object[]> codec = StreamCodecs.ofFixedLengthArray(value -> switch (value % 3) {
  			case 0 -> StreamCodecs.ofInt();
  			case 1 -> StreamCodecs.ofString();
  			case 2 -> StreamCodecs.ofBoolean();
  			default -> throw new AssertionError();
  		}, () -> new Object[length], length);
  		Object[] result = doTest(codec, objects, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(objects, result);
  	}
  
  	@Theory
  	public void ofList(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int listSize
  	) {
  		assumeTrue((listSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<List<String>> codec = StreamCodecs.ofList(StreamCodecs.ofString());
  		List<String> strings = new ArrayList<>(listSize);
  		for (int i = 0; i < listSize; i++) {
  			strings.add(String.valueOf(i));
  		}
  		List<String> result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertEquals(strings, result);
  	}
  
  	@Theory
  	public void ofHeterogeneousList(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<List<Object>> codec = StreamCodecs.ofList(value -> switch (value % 3) {
  			case 0 -> StreamCodecs.ofInt();
  			case 1 -> StreamCodecs.ofString();
  			case 2 -> StreamCodecs.ofBoolean();
  			default -> throw new AssertionError();
  		});
  		List<Object> objects = List.of(0, "x", true, 12, "y", false, 34, "11241");
  		List<Object> result = doTest(codec, objects, readBufferSize, writeBufferSize);
  
  		assertEquals(objects, result);
  	}
  
  	@Theory
  	public void ofSubtypeBuilder(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<Number> codec = SubtypeStreamCodec.<Number>builder()
  			.withSubtype(Integer.class, StreamCodecs.ofInt())
  			.withSubtype(Long.class, StreamCodecs.ofLong())
  			.withSubtype(Float.class, StreamCodecs.ofFloat())
  			.withSubtype(Byte.class, StreamCodecs.ofByte())
  			.build();
  
  		byte b = Byte.MAX_VALUE;
  		byte bResult = (byte) doTest(codec, b, readBufferSize, writeBufferSize);
  		assertEquals(b, bResult);
  
  		int i = Integer.MIN_VALUE;
  		int iResult = (int) doTest(codec, i, readBufferSize, writeBufferSize);
  		assertEquals(i, iResult);
  
  		long l = Long.MAX_VALUE;
  		long lResult = (long) doTest(codec, l, readBufferSize, writeBufferSize);
  		assertEquals(l, lResult);
  
  		float f = Float.MIN_VALUE;
  		float fResult = (float) doTest(codec, f, readBufferSize, writeBufferSize);
  		assertEquals(f, fResult, 1e-10);
  	}
  
  	@Theory
  	public void ofSubtypeMap(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		LinkedHashMap<Class<? extends Number>, StreamCodec<? extends Number>> map = new LinkedHashMap<>();
  		map.put(Integer.class, StreamCodecs.ofInt());
  		map.put(Long.class, StreamCodecs.ofLong());
  		map.put(Float.class, StreamCodecs.ofFloat());
  		map.put(Byte.class, StreamCodecs.ofByte());
  
  		StreamCodec<Number> codec = StreamCodecs.ofSubtype(map);
  
  		byte b = Byte.MAX_VALUE;
  		byte bResult = (byte) doTest(codec, b, readBufferSize, writeBufferSize);
  		assertEquals(b, bResult);
  
  		int i = Integer.MIN_VALUE;
  		int iResult = (int) doTest(codec, i, readBufferSize, writeBufferSize);
  		assertEquals(i, iResult);
  
  		long l = Long.MAX_VALUE;
  		long lResult = (long) doTest(codec, l, readBufferSize, writeBufferSize);
  		assertEquals(l, lResult);
  
  		float f = Float.MIN_VALUE;
  		float fResult = (float) doTest(codec, f, readBufferSize, writeBufferSize);
  		assertEquals(f, fResult, 1e-10);
  	}
  
  	@Theory
  	public void ofVarIntArrayList(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<List<int[]>> codec = StreamCodecs.ofList(StreamCodecs.ofVarIntArray());
  		List<int[]> expected = List.of(
  			new int[]{-1, -2, -3},
  			new int[]{1, 2, 3},
  			new int[]{-1, 0, 1},
  			new int[]{Integer.MIN_VALUE, Integer.MAX_VALUE}
  		);
  		List<int[]> result = doTest(codec, expected, readBufferSize, writeBufferSize);
  
  		assertEquals(expected.size(), result.size());
  
  		for (int i = 0; i < expected.size(); i++) {
  			assertArrayEquals(expected.get(i), result.get(i));
  		}
  	}
  
  	@Theory
  	public void ofVarLongArrayList(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<List<long[]>> codec = StreamCodecs.ofList(StreamCodecs.ofVarLongArray());
  		List<long[]> expected = List.of(
  			new long[]{-1, -2, -3},
  			new long[]{1, 2, 3},
  			new long[]{-1, 0, 1},
  			new long[]{Long.MIN_VALUE, Long.MAX_VALUE}
  		);
  		List<long[]> result = doTest(codec, expected, readBufferSize, writeBufferSize);
  
  		assertEquals(expected.size(), result.size());
  
  		for (int i = 0; i < expected.size(); i++) {
  			assertArrayEquals(expected.get(i), result.get(i));
  		}
  	}
  
  	@Theory
  	public void ofIntArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofIntArray();
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTest(codec, ints, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofIntArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize > 100) && writeBufferSize > 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofIntArray();
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTestWithAdditionalData(codec, ints, readBufferSize, writeBufferSize, 4, 4);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofByteArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<byte[]> codec = StreamCodecs.ofByteArray();
  		byte[] bytes = new byte[arraySize];
  		Random random = ThreadLocalRandom.current();
  		random.nextBytes(bytes);
  
  		byte[] result = doTest(codec, bytes, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(bytes, result);
  	}
  
  	@Theory
  	public void ofByteArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<byte[]> codec = StreamCodecs.ofByteArray();
  		byte[] bytes = new byte[arraySize];
  		Random random = ThreadLocalRandom.current();
  		random.nextBytes(bytes);
  
  		byte[] result = doTestWithAdditionalData(codec, bytes, readBufferSize, writeBufferSize, 4, 4);
  
  		assertArrayEquals(bytes, result);
  	}
  
  	@Theory
  	public void ofVarIntArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofVarIntArray();
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTest(codec, ints, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofVarIntArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofVarIntArray();
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTestWithAdditionalData(codec, ints, readBufferSize, writeBufferSize, 5, 5);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofVarLongArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<long[]> codec = StreamCodecs.ofVarLongArray();
  		long[] longs = new long[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < longs.length; i++) {
  			longs[i] = random.nextLong();
  		}
  
  		long[] result = doTest(codec, longs, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(longs, result);
  	}
  
  	@Theory
  	public void ofVarLongArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<long[]> codec = StreamCodecs.ofVarLongArray();
  		long[] longs = new long[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < longs.length; i++) {
  			longs[i] = random.nextLong();
  		}
  
  		long[] result = doTestWithAdditionalData(codec, longs, readBufferSize, writeBufferSize, 10, 10);
  
  		assertArrayEquals(longs, result);
  	}
  
  	@Theory
  	public void ofFixedLengthVarIntArrayList(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<List<int[]>> codec = StreamCodecs.ofList(StreamCodecs.ofFixedLengthVarIntArray(3));
  		List<int[]> expected = List.of(
  			new int[]{-1, -2, -3},
  			new int[]{1, 2, 3},
  			new int[]{-1, 0, 1},
  			new int[]{Integer.MIN_VALUE, 0, Integer.MAX_VALUE}
  		);
  		List<int[]> result = doTest(codec, expected, readBufferSize, writeBufferSize);
  
  		assertEquals(expected.size(), result.size());
  
  		for (int i = 0; i < expected.size(); i++) {
  			assertArrayEquals(expected.get(i), result.get(i));
  		}
  	}
  
  	@Theory
  	public void ofFixedLengthVarLongArrayList(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize) {
  		StreamCodec<List<long[]>> codec = StreamCodecs.ofList(StreamCodecs.ofFixedLengthVarLongArray(3));
  		List<long[]> expected = List.of(
  			new long[]{-1, -2, -3},
  			new long[]{1, 2, 3},
  			new long[]{-1, 0, 1},
  			new long[]{Long.MIN_VALUE, 0, Long.MAX_VALUE}
  		);
  		List<long[]> result = doTest(codec, expected, readBufferSize, writeBufferSize);
  
  		assertEquals(expected.size(), result.size());
  
  		for (int i = 0; i < expected.size(); i++) {
  			assertArrayEquals(expected.get(i), result.get(i));
  		}
  	}
  
  	@Theory
  	public void ofFixedLengthIntArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofFixedLengthIntArray(arraySize);
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTest(codec, ints, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofFixedLengthIntArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize > 100) && writeBufferSize > 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofFixedLengthIntArray(arraySize);
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTestWithAdditionalData(codec, ints, readBufferSize, writeBufferSize, 4, 4);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofFixedLengthByteArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<byte[]> codec = StreamCodecs.ofFixedLengthByteArray(arraySize);
  		byte[] bytes = new byte[arraySize];
  		Random random = ThreadLocalRandom.current();
  		random.nextBytes(bytes);
  
  		byte[] result = doTest(codec, bytes, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(bytes, result);
  	}
  
  	@Theory
  	public void ofFixedLengthByteArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<byte[]> codec = StreamCodecs.ofFixedLengthByteArray(arraySize);
  		byte[] bytes = new byte[arraySize];
  		Random random = ThreadLocalRandom.current();
  		random.nextBytes(bytes);
  
  		byte[] result = doTestWithAdditionalData(codec, bytes, readBufferSize, writeBufferSize, 4, 4);
  
  		assertArrayEquals(bytes, result);
  	}
  
  	@Theory
  	public void ofFixedLengthVarIntArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofFixedLengthVarIntArray(arraySize);
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTest(codec, ints, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofFixedLengthVarIntArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<int[]> codec = StreamCodecs.ofFixedLengthVarIntArray(arraySize);
  		int[] ints = new int[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < ints.length; i++) {
  			ints[i] = random.nextInt();
  		}
  
  		int[] result = doTestWithAdditionalData(codec, ints, readBufferSize, writeBufferSize, 5, 5);
  
  		assertArrayEquals(ints, result);
  	}
  
  	@Theory
  	public void ofFixedLengthVarLongArray(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<long[]> codec = StreamCodecs.ofFixedLengthVarLongArray(arraySize);
  		long[] longs = new long[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < longs.length; i++) {
  			longs[i] = random.nextLong();
  		}
  
  		long[] result = doTest(codec, longs, readBufferSize, writeBufferSize);
  
  		assertArrayEquals(longs, result);
  	}
  
  	@Theory
  	public void ofFixedLengthVarLongArrayWithAdditionalData(
  		@FromDataPoints("bufferSizes") int readBufferSize,
  		@FromDataPoints("bufferSizes") int writeBufferSize,
  		@FromDataPoints("containerSizes") int arraySize
  	) {
  		assumeTrue((arraySize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<long[]> codec = StreamCodecs.ofFixedLengthVarLongArray(arraySize);
  		long[] longs = new long[arraySize];
  		Random random = ThreadLocalRandom.current();
  		for (int i = 0; i < longs.length; i++) {
  			longs[i] = random.nextLong();
  		}
  
  		long[] result = doTestWithAdditionalData(codec, longs, readBufferSize, writeBufferSize, 10, 10);
  
  		assertArrayEquals(longs, result);
  	}
  
  	private <T> T doTest(StreamCodec<T> codec, T value, int readBufferSize, int writeBufferSize) {
  		ByteArrayOutputStream baos = new ByteArrayOutputStream();
  		try (StreamOutput output = StreamOutput.create(baos, writeBufferSize)) {
  			codec.encode(output, value);
  		} catch (IOException e) {
  			throw new AssertionError(e);
  		}
  
  		ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
  		try (StreamInput input = StreamInput.create(bais, readBufferSize)) {
  			return codec.decode(input);
  		} catch (IOException e) {
  			throw new AssertionError(e);
  		}
  	}
  
  	private <T> T doTestWithAdditionalData(
  		StreamCodec<T> codec, T value, int readBufferSize, int writeBufferSize, int additionalDataBefore,
  		int additionalDataAfter
  	) {
  		StreamCodec<T> codecWithAdditionalData = new StreamCodecWithAdditionalData<>(codec, additionalDataBefore, additionalDataAfter);
  		return doTest(codecWithAdditionalData, value, readBufferSize, writeBufferSize);
  	}
  
  	private static class StreamCodecWithAdditionalData<T> implements StreamCodec<T> {
  		private final StreamCodec<T> codec;
  		private final int additionalDataBefore;
  		private final int additionalDataAfter;
  
  		public StreamCodecWithAdditionalData(StreamCodec<T> codec, int additionalDataBefore, int additionalDataAfter) {
  			this.codec = codec;
  			this.additionalDataBefore = additionalDataBefore;
  			this.additionalDataAfter = additionalDataAfter;
  		}
  
  		@Override
  		public void encode(StreamOutput output, T item) throws IOException {
  			for (int i = 0; i < additionalDataBefore; i++) {
  				output.writeByte((byte) 1);
  			}
  			codec.encode(output, item);
  			for (int i = 0; i < additionalDataAfter; i++) {
  				output.writeByte((byte) 1);
  			}
  		}
  
  		@Override
  		public T decode(StreamInput input) throws IOException {
  			for (int i = 0; i < additionalDataBefore; i++) {
  				byte b = input.readByte();
  				assertEquals(1, b);
  			}
  			T decoded = codec.decode(input);
  			for (int i = 0; i < additionalDataAfter; i++) {
  				byte b = input.readByte();
  				assertEquals(1, b);
  			}
  			return decoded;
  		}
  	}
  
  	@Test
  	public void testReference() throws IOException {
  		StreamCodec<String> codec = StreamCodecs.reference(StreamCodecs.ofString());
  		ByteArrayOutputStream os = new ByteArrayOutputStream();
  		try (StreamOutput output = StreamOutput.create(os)) {
  			String s1 = "s1";
  			String s2 = "s2";
  			String s3 = "s3";
  			codec.encode(output, s1);
  			codec.encode(output, s2);
  			codec.encode(output, s1);
  			codec.encode(output, s3);
  			codec.encode(output, s2);
  		}
  
  		codec = StreamCodecs.reference(StreamCodecs.ofString());
  		byte[] buf = os.toByteArray();
  		try (StreamInput input = StreamInput.create(new ByteArrayInputStream(buf))) {
  			String s1 = codec.decode(input);
  			assertEquals("s1", s1);
  			String s2 = codec.decode(input);
  			assertEquals("s2", s2);
  			assertSame(s1, codec.decode(input));
  			String s3 = codec.decode(input);
  			assertEquals("s3", s3);
  			assertSame(s2, codec.decode(input));
  		}
  	}
  
  	@Theory
  	public void ofArrayList(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int listSize) {
  		assumeTrue((listSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<ArrayList<String>> codec = StreamCodecs.ofArrayList(StreamCodecs.ofString());
  		ArrayList<String> strings = new ArrayList<>(listSize);
  		for (int i = 0; i < listSize; i++) {
  			strings.add(String.valueOf(i));
  		}
  		List<String> result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertEquals(strings, result);
  	}
  
  	@Theory
  	public void ofLinkedList(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int listSize) {
  		assumeTrue((listSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<LinkedList<String>> codec = StreamCodecs.ofLinkedList(StreamCodecs.ofString());
  		LinkedList<String> strings = new LinkedList<>();
  		for (int i = 0; i < listSize; i++) {
  			strings.add(String.valueOf(i));
  		}
  		List<String> result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertEquals(strings, result);
  	}
  
  	@Theory
  	public void ofSet(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int setSize) {
  		assumeTrue((setSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<Set<String>> codec = StreamCodecs.ofSet(StreamCodecs.ofString());
  		Set<String> strings = new HashSet<>();
  		for (int i = 0; i < setSize; i++) {
  			strings.add(String.valueOf(i));
  		}
  		Set<String> result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertEquals(strings, result);
  	}
  
  	@Theory
  	public void ofHashSet(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int setSize) {
  		assumeTrue((setSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<HashSet<String>> codec = StreamCodecs.ofHashSet(StreamCodecs.ofString());
  		HashSet<String> strings = new HashSet<>();
  		for (int i = 0; i < setSize; i++) {
  			strings.add(String.valueOf(i));
  		}
  		Set<String> result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertEquals(strings, result);
  	}
  
  	@Theory
  	public void ofLinkedHashSet(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int setSize) {
  		assumeTrue((setSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<LinkedHashSet<String>> codec = StreamCodecs.ofLinkedHashSet(StreamCodecs.ofString());
  		LinkedHashSet<String> strings = new LinkedHashSet<>();
  		for (int i = 0; i < setSize; i++) {
  			strings.add(String.valueOf(i));
  		}
  		Set<String> result = doTest(codec, strings, readBufferSize, writeBufferSize);
  
  		assertEquals(strings, result);
  	}
  
  	@Theory
  	public void ofMap(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int mapSize) {
  		assumeTrue((mapSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<Map<String, Integer>> codec = StreamCodecs.ofMap(StreamCodecs.ofString(), StreamCodecs.ofInt());
  		Map<String, Integer> map = new HashMap<>(mapSize);
  		for (int i = 0; i < mapSize; i++) {
  			map.put(String.valueOf(i), i);
  		}
  		Map<String, Integer> result = doTest(codec, map, readBufferSize, writeBufferSize);
  
  		assertEquals(map, result);
  	}
  
  	@Theory
  	public void ofHashMap(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int mapSize) {
  		assumeTrue((mapSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<HashMap<String, Integer>> codec = StreamCodecs.ofHashMap(StreamCodecs.ofString(), StreamCodecs.ofInt());
  		HashMap<String, Integer> map = new HashMap<>(mapSize);
  		for (int i = 0; i < mapSize; i++) {
  			map.put(String.valueOf(i), i);
  		}
  		Map<String, Integer> result = doTest(codec, map, readBufferSize, writeBufferSize);
  
  		assertEquals(map, result);
  	}
  
  	@Theory
  	public void ofLinkedHashMap(@FromDataPoints("bufferSizes") int readBufferSize, @FromDataPoints("bufferSizes") int writeBufferSize,
  			@FromDataPoints("containerSizes") int mapSize) {
  		assumeTrue((mapSize != 1_000_000 || readBufferSize >= 100) && writeBufferSize >= 100);
  
  		StreamCodec<LinkedHashMap<String, Integer>> codec = StreamCodecs.ofLinkedHashMap(StreamCodecs.ofString(), StreamCodecs.ofInt());
  		LinkedHashMap<String, Integer> map = new LinkedHashMap<>(mapSize);
  		for (int i = 0; i < mapSize; i++) {
  			map.put(String.valueOf(i), i);
  		}
  		Map<String, Integer> result = doTest(codec, map, readBufferSize, writeBufferSize);
  
  		assertEquals(map, result);
  	}
  
  
  }
  