View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.examples.stress;
18  
19  import org.apache.commons.rng.UniformRandomProvider;
20  import org.apache.commons.rng.core.source32.IntProvider;
21  import org.apache.commons.rng.core.source32.RandomIntSource;
22  import org.apache.commons.rng.core.source64.RandomLongSource;
23  import org.apache.commons.rng.simple.RandomSource;
24  import org.junit.Assert;
25  import org.junit.Test;
26  
27  import java.io.ByteArrayOutputStream;
28  import java.io.DataOutputStream;
29  import java.io.IOException;
30  import java.io.OutputStream;
31  import java.nio.ByteOrder;
32  import java.util.function.BiConsumer;
33  import java.util.function.UnaryOperator;
34  
35  /**
36   * Tests for {@link RngDataOutput}.
37   */
38  public class RngDataOutputTest {
39      /**
40       * A factory for creating RngDataOutput objects.
41       */
42      interface RngDataOutputFactory {
43          /**
44           * Create a new instance.
45           *
46           * @param out Output stream.
47           * @param size Number of values to write.
48           * @param byteOrder Byte order.
49           * @return the data output
50           */
51          RngDataOutput create(OutputStream out, int size, ByteOrder byteOrder);
52      }
53  
54      @Test
55      public void testIntBigEndian() throws IOException {
56          assertRngOutput(RandomSource.PCG_MCG_XSH_RS_32,
57              UnaryOperator.identity(),
58              RngDataOutputTest::writeInt,
59              RngDataOutput::ofInt, ByteOrder.BIG_ENDIAN);
60      }
61  
62      @Test
63      public void testIntLittleEndian() throws IOException {
64          assertRngOutput(RandomSource.PCG_MCG_XSH_RS_32,
65              RNGUtils::createReverseBytesProvider,
66              RngDataOutputTest::writeInt,
67              RngDataOutput::ofInt, ByteOrder.LITTLE_ENDIAN);
68      }
69  
70      @Test
71      public void testLongBigEndian() throws IOException {
72          assertRngOutput(RandomSource.SPLIT_MIX_64,
73              UnaryOperator.identity(),
74              RngDataOutputTest::writeLong,
75              RngDataOutput::ofLong, ByteOrder.BIG_ENDIAN);
76      }
77  
78      @Test
79      public void testLongLittleEndian() throws IOException {
80          assertRngOutput(RandomSource.SPLIT_MIX_64,
81              RNGUtils::createReverseBytesProvider,
82              RngDataOutputTest::writeLong,
83              RngDataOutput::ofLong, ByteOrder.LITTLE_ENDIAN);
84      }
85  
86      @Test
87      public void testLongAsIntBigEndian() throws IOException {
88          assertRngOutput(RandomSource.SPLIT_MIX_64,
89              // Convert SplitMix64 to an int provider so it is detected as requiring double the
90              // length output.
91              rng -> new IntProvider() {
92                  @Override
93                  public int next() {
94                      return rng.nextInt();
95                  }
96              },
97              RngDataOutputTest::writeInt,
98              RngDataOutput::ofLongAsInt, ByteOrder.BIG_ENDIAN);
99      }
100 
101     @Test
102     public void testLongAsIntLittleEndian() throws IOException {
103         assertRngOutput(RandomSource.SPLIT_MIX_64,
104             // Convert SplitMix64 to an int provider so it is detected as requiring double the
105             // length output. Then reverse the bytes.
106             rng -> new IntProvider() {
107                 @Override
108                 public int next() {
109                     return Integer.reverseBytes(rng.nextInt());
110                 }
111             },
112             RngDataOutputTest::writeInt,
113             RngDataOutput::ofLongAsInt, ByteOrder.LITTLE_ENDIAN);
114     }
115 
116     private static void writeInt(DataOutputStream sink, UniformRandomProvider rng) {
117         try {
118             sink.writeInt(rng.nextInt());
119         } catch (IOException e) {
120             Assert.fail();
121         }
122     }
123 
124     private static void writeLong(DataOutputStream sink, UniformRandomProvider rng) {
125         try {
126             sink.writeLong(rng.nextLong());
127         } catch (IOException e) {
128             Assert.fail();
129         }
130     }
131 
132     /**
133      * Assert the byte output from the source is the same. Creates two instances of the same
134      * RNG with the same seed. The first is converted to a new RNG using the {@code rngConverter}.
135      * This is used to output raw bytes using a {@link DataOutputStream} via the specified
136      * {@code pipe}. The second is used to output raw bytes via the {@link RngDataOutput} class
137      * created using the {@code factory}.
138      *
139      * <p>The random source should output native {@code int} or {@code long} values.
140      *
141      * @param source Random source.
142      * @param rngConverter Converter for the raw RNG.
143      * @param pipe Pipe to send data from the RNG to the DataOutputStream.
144      * @param factory Factory for the RngDataOutput.
145      * @param byteOrder Byte order for the RngDataOutput.
146      * @throws IOException Signals that an I/O exception has occurred.
147      */
148     private static void assertRngOutput(RandomSource source,
149         UnaryOperator<UniformRandomProvider> rngConverter,
150         BiConsumer<DataOutputStream, UniformRandomProvider> pipe,
151         RngDataOutputFactory factory,
152         ByteOrder byteOrder) throws IOException {
153         final long seed = RandomSource.createLong();
154         UniformRandomProvider rng1 = RandomSource.create(source, seed);
155         UniformRandomProvider rng2 = RandomSource.create(source, seed);
156         final int size = 37;
157         for (int repeats = 1; repeats <= 2; repeats++) {
158             byte[] expected = createBytes(rng1, size, repeats, rngConverter, pipe);
159             byte[] actual = writeRngOutput(rng2, size, repeats, byteOrder, factory);
160             Assert.assertArrayEquals(expected, actual);
161         }
162     }
163 
164     /**
165      * Convert the RNG and then creates bytes from the RNG using the pipe.
166      *
167      * @param rng RNG.
168      * @param size The number of values to send to the pipe.
169      * @param repeats The number of repeat iterations.
170      * @param rngConverter Converter for the raw RNG.
171      * @param pipe Pipe to send data from the RNG to the DataOutputStream.
172      * @return the bytes
173      * @throws IOException Signals that an I/O exception has occurred.
174      */
175     private static byte[] createBytes(UniformRandomProvider rng, int size, int repeats,
176         UnaryOperator<UniformRandomProvider> rngConverter,
177         BiConsumer<DataOutputStream, UniformRandomProvider> pipe) throws IOException {
178         UniformRandomProvider rng2 = rngConverter.apply(rng);
179         // If the factory converts to an IntProvider then output twice the size
180         if (rng instanceof RandomLongSource && rng2 instanceof RandomIntSource) {
181             size *= 2;
182         }
183         ByteArrayOutputStream out = new ByteArrayOutputStream();
184         try (DataOutputStream sink = new DataOutputStream(out)) {
185             for (int j = 0; j < repeats; j++) {
186                 for (int i = 0; i < size; i++) {
187                     pipe.accept(sink, rng2);
188                 }
189             }
190         }
191         return out.toByteArray();
192     }
193 
194     /**
195      * Write the RNG to the RngDataOutput built by the factory.
196      *
197      * @param rng RNG.
198      * @param size The number of values to send to the RngDataOutput.
199      * @param repeats The number of repeat iterations.
200      * @param byteOrder Byte order for the RngDataOutput.
201      * @param factory Factory for the RngDataOutput.
202      * @return the bytes
203      * @throws IOException Signals that an I/O exception has occurred.
204      */
205     private static byte[] writeRngOutput(UniformRandomProvider rng, int size, int repeats,
206         ByteOrder byteOrder, RngDataOutputFactory factory) throws IOException {
207         ByteArrayOutputStream out = new ByteArrayOutputStream();
208         try (RngDataOutput sink = factory.create(out, size, byteOrder)) {
209             for (int j = 0; j < repeats; j++) {
210                 sink.write(rng);
211             }
212         }
213         return out.toByteArray();
214     }
215 }