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.jupiter.api.Assertions;
25  import org.junit.jupiter.api.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  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      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      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      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      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      void testLongAsHLIntBigEndian() throws IOException {
88          assertRngOutput(RandomSource.SPLIT_MIX_64,
89              // Convert to an int provider so it is detected as requiring double the
90              // length output.
91              rng -> new HiLoCachingIntProvider(rng),
92              RngDataOutputTest::writeInt,
93              RngDataOutput::ofLongAsHLInt, ByteOrder.BIG_ENDIAN);
94      }
95  
96      @Test
97      void testLongAsHLIntLittleEndian() throws IOException {
98          assertRngOutput(RandomSource.SPLIT_MIX_64,
99              // Convert SplitMix64 to an int provider so it is detected as requiring double the
100             // length output. Then reverse the bytes.
101             rng -> new HiLoCachingIntProvider(rng) {
102                 @Override
103                 public int next() {
104                     return Integer.reverseBytes(super.next());
105                 }
106             },
107             RngDataOutputTest::writeInt,
108             RngDataOutput::ofLongAsHLInt, ByteOrder.LITTLE_ENDIAN);
109     }
110 
111 
112     @Test
113     void testLongAsLHIntBigEndian() throws IOException {
114         assertRngOutput(RandomSource.SPLIT_MIX_64,
115             // Convert to an int provider so it is detected as requiring double the
116             // length output.
117             rng -> new LoHiCachingIntProvider(rng),
118             RngDataOutputTest::writeInt,
119             RngDataOutput::ofLongAsLHInt, ByteOrder.BIG_ENDIAN);
120     }
121 
122     @Test
123     void testLongAsLHIntLittleEndian() throws IOException {
124         assertRngOutput(RandomSource.SPLIT_MIX_64,
125             // Convert to an int provider so it is detected as requiring double the
126             // length output. Then reverse the bytes.
127             rng -> new LoHiCachingIntProvider(rng) {
128                 @Override
129                 public int next() {
130                     return Integer.reverseBytes(super.next());
131                 }
132             },
133             RngDataOutputTest::writeInt,
134             RngDataOutput::ofLongAsLHInt, ByteOrder.LITTLE_ENDIAN);
135     }
136 
137     private static void writeInt(DataOutputStream sink, UniformRandomProvider rng) {
138         try {
139             sink.writeInt(rng.nextInt());
140         } catch (IOException e) {
141             Assertions.fail();
142         }
143     }
144 
145     private static void writeLong(DataOutputStream sink, UniformRandomProvider rng) {
146         try {
147             sink.writeLong(rng.nextLong());
148         } catch (IOException e) {
149             Assertions.fail();
150         }
151     }
152 
153     /**
154      * Assert the byte output from the source is the same. Creates two instances of the same
155      * RNG with the same seed. The first is converted to a new RNG using the {@code rngConverter}.
156      * This is used to output raw bytes using a {@link DataOutputStream} via the specified
157      * {@code pipe}. The second is used to output raw bytes via the {@link RngDataOutput} class
158      * created using the {@code factory}.
159      *
160      * <p>The random source should output native {@code int} or {@code long} values.
161      *
162      * @param source Random source.
163      * @param rngConverter Converter for the raw RNG.
164      * @param pipe Pipe to send data from the RNG to the DataOutputStream.
165      * @param factory Factory for the RngDataOutput.
166      * @param byteOrder Byte order for the RngDataOutput.
167      * @throws IOException Signals that an I/O exception has occurred.
168      */
169     private static void assertRngOutput(RandomSource source,
170         UnaryOperator<UniformRandomProvider> rngConverter,
171         BiConsumer<DataOutputStream, UniformRandomProvider> pipe,
172         RngDataOutputFactory factory,
173         ByteOrder byteOrder) throws IOException {
174         final long seed = RandomSource.createLong();
175         UniformRandomProvider rng1 = source.create(seed);
176         UniformRandomProvider rng2 = source.create(seed);
177         final int size = 37;
178         for (int repeats = 1; repeats <= 2; repeats++) {
179             byte[] expected = createBytes(rng1, size, repeats, rngConverter, pipe);
180             byte[] actual = writeRngOutput(rng2, size, repeats, byteOrder, factory);
181             Assertions.assertArrayEquals(expected, actual);
182         }
183     }
184 
185     /**
186      * Convert the RNG and then creates bytes from the RNG using the pipe.
187      *
188      * @param rng RNG.
189      * @param size The number of values to send to the pipe.
190      * @param repeats The number of repeat iterations.
191      * @param rngConverter Converter for the raw RNG.
192      * @param pipe Pipe to send data from the RNG to the DataOutputStream.
193      * @return the bytes
194      * @throws IOException Signals that an I/O exception has occurred.
195      */
196     private static byte[] createBytes(UniformRandomProvider rng, int size, int repeats,
197         UnaryOperator<UniformRandomProvider> rngConverter,
198         BiConsumer<DataOutputStream, UniformRandomProvider> pipe) throws IOException {
199         UniformRandomProvider rng2 = rngConverter.apply(rng);
200         // If the factory converts to an IntProvider then output twice the size
201         if (rng instanceof RandomLongSource && rng2 instanceof RandomIntSource) {
202             size *= 2;
203         }
204         ByteArrayOutputStream out = new ByteArrayOutputStream();
205         try (DataOutputStream sink = new DataOutputStream(out)) {
206             for (int j = 0; j < repeats; j++) {
207                 for (int i = 0; i < size; i++) {
208                     pipe.accept(sink, rng2);
209                 }
210             }
211         }
212         return out.toByteArray();
213     }
214 
215     /**
216      * Write the RNG to the RngDataOutput built by the factory.
217      *
218      * @param rng RNG.
219      * @param size The number of values to send to the RngDataOutput.
220      * @param repeats The number of repeat iterations.
221      * @param byteOrder Byte order for the RngDataOutput.
222      * @param factory Factory for the RngDataOutput.
223      * @return the bytes
224      * @throws IOException Signals that an I/O exception has occurred.
225      */
226     private static byte[] writeRngOutput(UniformRandomProvider rng, int size, int repeats,
227         ByteOrder byteOrder, RngDataOutputFactory factory) throws IOException {
228         ByteArrayOutputStream out = new ByteArrayOutputStream();
229         try (RngDataOutput sink = factory.create(out, size, byteOrder)) {
230             for (int j = 0; j < repeats; j++) {
231                 sink.write(rng);
232             }
233         }
234         return out.toByteArray();
235     }
236 
237     private static class LoHiCachingIntProvider extends IntProvider {
238         private long source = -1;
239         private final UniformRandomProvider rng;
240 
241         LoHiCachingIntProvider(UniformRandomProvider rng) {
242             this.rng = rng;
243         }
244 
245         @Override
246         public int next() {
247             long next = source;
248             if (next < 0) {
249                 // refill
250                 next = rng.nextLong();
251                 // store hi
252                 source = next >>> 32;
253                 // extract low
254                 return (int) next;
255             }
256             final int v = (int) next;
257             // reset
258             source = -1;
259             return v;
260         }
261     }
262 
263     private static class HiLoCachingIntProvider extends IntProvider {
264         private long source = -1;
265         private final UniformRandomProvider rng;
266 
267         HiLoCachingIntProvider(UniformRandomProvider rng) {
268             this.rng = rng;
269         }
270 
271         @Override
272         public int next() {
273             long next = source;
274             if (next < 0) {
275                 // refill
276                 next = rng.nextLong();
277                 // store low
278                 source = next & 0xffff_ffffL;
279                 // extract hi
280                 return (int) (next >>> 32);
281             }
282             final int v = (int) next;
283             // reset
284             source = -1;
285             return v;
286         }
287     }
288 }