blob: 9c5d6a06bb29b81610303b5e70fb97f4181455fb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.rng.examples.stress;
import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.core.source32.IntProvider;
import org.apache.commons.rng.core.source32.RandomIntSource;
import org.apache.commons.rng.core.source64.RandomLongSource;
import org.apache.commons.rng.simple.RandomSource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.util.function.BiConsumer;
import java.util.function.UnaryOperator;
/**
* Tests for {@link RngDataOutput}.
*/
class RngDataOutputTest {
/**
* A factory for creating RngDataOutput objects.
*/
interface RngDataOutputFactory {
/**
* Create a new instance.
*
* @param out Output stream.
* @param size Number of values to write.
* @param byteOrder Byte order.
* @return the data output
*/
RngDataOutput create(OutputStream out, int size, ByteOrder byteOrder);
}
@Test
void testIntBigEndian() throws IOException {
assertRngOutput(RandomSource.PCG_MCG_XSH_RS_32,
UnaryOperator.identity(),
RngDataOutputTest::writeInt,
RngDataOutput::ofInt, ByteOrder.BIG_ENDIAN);
}
@Test
void testIntLittleEndian() throws IOException {
assertRngOutput(RandomSource.PCG_MCG_XSH_RS_32,
RNGUtils::createReverseBytesProvider,
RngDataOutputTest::writeInt,
RngDataOutput::ofInt, ByteOrder.LITTLE_ENDIAN);
}
@Test
void testLongBigEndian() throws IOException {
assertRngOutput(RandomSource.SPLIT_MIX_64,
UnaryOperator.identity(),
RngDataOutputTest::writeLong,
RngDataOutput::ofLong, ByteOrder.BIG_ENDIAN);
}
@Test
void testLongLittleEndian() throws IOException {
assertRngOutput(RandomSource.SPLIT_MIX_64,
RNGUtils::createReverseBytesProvider,
RngDataOutputTest::writeLong,
RngDataOutput::ofLong, ByteOrder.LITTLE_ENDIAN);
}
@Test
void testLongAsHLIntBigEndian() throws IOException {
assertRngOutput(RandomSource.SPLIT_MIX_64,
// Convert to an int provider so it is detected as requiring double the
// length output.
rng -> new HiLoCachingIntProvider(rng),
RngDataOutputTest::writeInt,
RngDataOutput::ofLongAsHLInt, ByteOrder.BIG_ENDIAN);
}
@Test
void testLongAsHLIntLittleEndian() throws IOException {
assertRngOutput(RandomSource.SPLIT_MIX_64,
// Convert SplitMix64 to an int provider so it is detected as requiring double the
// length output. Then reverse the bytes.
rng -> new HiLoCachingIntProvider(rng) {
@Override
public int next() {
return Integer.reverseBytes(super.next());
}
},
RngDataOutputTest::writeInt,
RngDataOutput::ofLongAsHLInt, ByteOrder.LITTLE_ENDIAN);
}
@Test
void testLongAsLHIntBigEndian() throws IOException {
assertRngOutput(RandomSource.SPLIT_MIX_64,
// Convert to an int provider so it is detected as requiring double the
// length output.
rng -> new LoHiCachingIntProvider(rng),
RngDataOutputTest::writeInt,
RngDataOutput::ofLongAsLHInt, ByteOrder.BIG_ENDIAN);
}
@Test
void testLongAsLHIntLittleEndian() throws IOException {
assertRngOutput(RandomSource.SPLIT_MIX_64,
// Convert to an int provider so it is detected as requiring double the
// length output. Then reverse the bytes.
rng -> new LoHiCachingIntProvider(rng) {
@Override
public int next() {
return Integer.reverseBytes(super.next());
}
},
RngDataOutputTest::writeInt,
RngDataOutput::ofLongAsLHInt, ByteOrder.LITTLE_ENDIAN);
}
private static void writeInt(DataOutputStream sink, UniformRandomProvider rng) {
try {
sink.writeInt(rng.nextInt());
} catch (IOException e) {
Assertions.fail();
}
}
private static void writeLong(DataOutputStream sink, UniformRandomProvider rng) {
try {
sink.writeLong(rng.nextLong());
} catch (IOException e) {
Assertions.fail();
}
}
/**
* Assert the byte output from the source is the same. Creates two instances of the same
* RNG with the same seed. The first is converted to a new RNG using the {@code rngConverter}.
* This is used to output raw bytes using a {@link DataOutputStream} via the specified
* {@code pipe}. The second is used to output raw bytes via the {@link RngDataOutput} class
* created using the {@code factory}.
*
* <p>The random source should output native {@code int} or {@code long} values.
*
* @param source Random source.
* @param rngConverter Converter for the raw RNG.
* @param pipe Pipe to send data from the RNG to the DataOutputStream.
* @param factory Factory for the RngDataOutput.
* @param byteOrder Byte order for the RngDataOutput.
* @throws IOException Signals that an I/O exception has occurred.
*/
private static void assertRngOutput(RandomSource source,
UnaryOperator<UniformRandomProvider> rngConverter,
BiConsumer<DataOutputStream, UniformRandomProvider> pipe,
RngDataOutputFactory factory,
ByteOrder byteOrder) throws IOException {
final long seed = RandomSource.createLong();
UniformRandomProvider rng1 = source.create(seed);
UniformRandomProvider rng2 = source.create(seed);
final int size = 37;
for (int repeats = 1; repeats <= 2; repeats++) {
byte[] expected = createBytes(rng1, size, repeats, rngConverter, pipe);
byte[] actual = writeRngOutput(rng2, size, repeats, byteOrder, factory);
Assertions.assertArrayEquals(expected, actual);
}
}
/**
* Convert the RNG and then creates bytes from the RNG using the pipe.
*
* @param rng RNG.
* @param size The number of values to send to the pipe.
* @param repeats The number of repeat iterations.
* @param rngConverter Converter for the raw RNG.
* @param pipe Pipe to send data from the RNG to the DataOutputStream.
* @return the bytes
* @throws IOException Signals that an I/O exception has occurred.
*/
private static byte[] createBytes(UniformRandomProvider rng, int size, int repeats,
UnaryOperator<UniformRandomProvider> rngConverter,
BiConsumer<DataOutputStream, UniformRandomProvider> pipe) throws IOException {
UniformRandomProvider rng2 = rngConverter.apply(rng);
// If the factory converts to an IntProvider then output twice the size
if (rng instanceof RandomLongSource && rng2 instanceof RandomIntSource) {
size *= 2;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (DataOutputStream sink = new DataOutputStream(out)) {
for (int j = 0; j < repeats; j++) {
for (int i = 0; i < size; i++) {
pipe.accept(sink, rng2);
}
}
}
return out.toByteArray();
}
/**
* Write the RNG to the RngDataOutput built by the factory.
*
* @param rng RNG.
* @param size The number of values to send to the RngDataOutput.
* @param repeats The number of repeat iterations.
* @param byteOrder Byte order for the RngDataOutput.
* @param factory Factory for the RngDataOutput.
* @return the bytes
* @throws IOException Signals that an I/O exception has occurred.
*/
private static byte[] writeRngOutput(UniformRandomProvider rng, int size, int repeats,
ByteOrder byteOrder, RngDataOutputFactory factory) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try (RngDataOutput sink = factory.create(out, size, byteOrder)) {
for (int j = 0; j < repeats; j++) {
sink.write(rng);
}
}
return out.toByteArray();
}
private static class LoHiCachingIntProvider extends IntProvider {
private long source = -1;
private final UniformRandomProvider rng;
LoHiCachingIntProvider(UniformRandomProvider rng) {
this.rng = rng;
}
@Override
public int next() {
long next = source;
if (next < 0) {
// refill
next = rng.nextLong();
// store hi
source = next >>> 32;
// extract low
return (int) next;
}
final int v = (int) next;
// reset
source = -1;
return v;
}
}
private static class HiLoCachingIntProvider extends IntProvider {
private long source = -1;
private final UniformRandomProvider rng;
HiLoCachingIntProvider(UniformRandomProvider rng) {
this.rng = rng;
}
@Override
public int next() {
long next = source;
if (next < 0) {
// refill
next = rng.nextLong();
// store low
source = next & 0xffff_ffffL;
// extract hi
return (int) (next >>> 32);
}
final int v = (int) next;
// reset
source = -1;
return v;
}
}
}