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  
22  import picocli.CommandLine.Command;
23  import picocli.CommandLine.Mixin;
24  import picocli.CommandLine.Option;
25  import picocli.CommandLine.Parameters;
26  
27  import java.io.BufferedWriter;
28  import java.io.File;
29  import java.io.IOException;
30  import java.io.Writer;
31  import java.nio.ByteOrder;
32  import java.nio.IntBuffer;
33  import java.nio.file.Files;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.concurrent.Callable;
37  import java.util.concurrent.ThreadLocalRandom;
38  
39  /**
40   * Specification for the "bridge" command.
41   *
42   * <p>This command tests that {@code int} values can be piped to a
43   * program that reads {@code int} values from its standard input. Only
44   * a limited number of {@code int} values will be written.</p>
45   */
46  @Command(name = "bridge",
47           description = {"Transfer test 32-bit data to a test application sub-process via standard input."})
48  class BridgeTestCommand implements Callable<Void> {
49      /** The standard options. */
50      @Mixin
51      private StandardOptions reusableOptions;
52  
53      /** The executable. */
54      @Parameters(index = "0",
55                  description = "The stress test executable.")
56      private File executable;
57  
58      /** The executable arguments. */
59      @Parameters(index = "1..*",
60                  description = "The arguments to pass to the executable.",
61                  paramLabel = "<argument>")
62      private List<String> executableArguments = new ArrayList<>();
63  
64      /** The file output prefix. */
65      @Option(names = {"--prefix"},
66              description = "Results file prefix (default: ${DEFAULT-VALUE}).")
67      private File fileOutputPrefix = new File("bridge");
68  
69      /** The output byte order of the binary data. */
70      @Option(names = {"-b", "--byte-order"},
71              description = {"Byte-order of the transferred data (default: ${DEFAULT-VALUE}).",
72                             "Valid values: BIG_ENDIAN, LITTLE_ENDIAN."})
73      private ByteOrder byteOrder = ByteOrder.nativeOrder();
74  
75      /**
76       * Validates the run command arguments and and runs the bridge test.
77       */
78      @Override
79      public Void call() {
80          LogUtils.setLogLevel(reusableOptions.logLevel);
81          ProcessUtils.checkExecutable(executable);
82          ProcessUtils.checkOutputDirectory(fileOutputPrefix);
83          runBridgeTest();
84          return null;
85      }
86  
87      /**
88       * Starts the executable process and writes {@code int} values to a data file and the stdin
89       * of the executable. Captures stdout of the executable to a file.
90       */
91      private void runBridgeTest() {
92          final List<String> command = ProcessUtils.buildSubProcessCommand(executable, executableArguments);
93  
94          try {
95              final File dataFile = new File(fileOutputPrefix + ".data");
96              final File outputFile = new File(fileOutputPrefix + ".out");
97              final File errorFile = new File(fileOutputPrefix + ".err");
98  
99              // Store int values
100             final IntBuffer buffer = IntBuffer.allocate(Integer.SIZE * 2);
101 
102             try (BufferedWriter textOutput = Files.newBufferedWriter(dataFile.toPath())) {
103                 // Write int data using a single bit in all possible positions
104                 int value = 1;
105                 for (int i = 0; i < Integer.SIZE; i++) {
106                     writeInt(textOutput, buffer, value);
107                     value <<= 1;
108                 }
109 
110                 // Write random int data
111                 while (buffer.remaining() != 0) {
112                     writeInt(textOutput, buffer, ThreadLocalRandom.current().nextInt());
113                 }
114             }
115 
116             // Pass the same values to the output application
117             buffer.flip();
118             final UniformRandomProvider rng = new IntProvider() {
119                 @Override
120                 public int next() {
121                     return buffer.get();
122                 }
123             };
124 
125             // Start the application.
126             final ProcessBuilder builder = new ProcessBuilder(command);
127             builder.redirectOutput(ProcessBuilder.Redirect.to(outputFile));
128             builder.redirectError(ProcessBuilder.Redirect.to(errorFile));
129             final Process testingProcess = builder.start();
130 
131             // Open the stdin of the process and write to a custom data sink.
132             // Note: The 'bridge' command only supports 32-bit data in order to
133             // demonstrate passing suitable data for TestU01 BigCrush.
134             final Source64Mode source64 = null;
135             try (RngDataOutput sink = RNGUtils.createDataOutput(rng, source64,
136                     testingProcess.getOutputStream(), buffer.capacity() * 4, byteOrder)) {
137                 sink.write(rng);
138             }
139 
140             final Integer exitValue = ProcessUtils.getExitValue(testingProcess);
141             if (exitValue == null) {
142                 LogUtils.error("%s did not exit. Process was killed.", command.get(0));
143             } else {
144                 final int value = exitValue;
145                 if (value != 0) {
146                     LogUtils.error("%s exit code = %d", command.get(0), value);
147                 }
148             }
149 
150         } catch (IOException ex) {
151             throw new ApplicationException("Failed to run process: " + ex.getMessage(), ex);
152         }
153     }
154 
155     /**
156      * Write an {@code int} value to the writer and the buffer output. The native Java
157      * value will be written to the writer using the debugging output of the
158      * {@link OutputCommand}.
159      *
160      * @param textOutput the text data writer.
161      * @param buffer the buffer.
162      * @param value the value.
163      * @throws IOException Signals that an I/O exception has occurred.
164      * @see OutputCommand#writeInt(java.io.Writer, int)
165      */
166     private static void writeInt(Writer textOutput,
167                                  IntBuffer buffer,
168                                  int value) throws IOException {
169         OutputCommand.writeInt(textOutput, value);
170         buffer.put(value);
171     }
172 }