blob: 70a2a0466fb6aece21de1f6ac46f82799f808aa4 [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 picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
/**
* Specification for the "bridge" command.
*
* <p>This command tests that {@code int} values can be piped to a
* program that reads {@code int} values from its standard input. Only
* a limited number of {@code int} values will be written.</p>
*/
@Command(name = "bridge",
description = {"Transfer test 32-bit data to a test application sub-process via standard input."})
class BridgeTestCommand implements Callable<Void> {
/** The standard options. */
@Mixin
private StandardOptions reusableOptions;
/** The executable. */
@Parameters(index = "0",
description = "The stress test executable.")
private File executable;
/** The executable arguments. */
@Parameters(index = "1..*",
description = "The arguments to pass to the executable.",
paramLabel = "<argument>")
private List<String> executableArguments = new ArrayList<>();
/** The file output prefix. */
@Option(names = {"--prefix"},
description = "Results file prefix (default: ${DEFAULT-VALUE}).")
private File fileOutputPrefix = new File("bridge");
/** The output byte order of the binary data. */
@Option(names = {"-b", "--byte-order"},
description = {"Byte-order of the transferred data (default: ${DEFAULT-VALUE}).",
"Valid values: BIG_ENDIAN, LITTLE_ENDIAN."})
private ByteOrder byteOrder = ByteOrder.nativeOrder();
/**
* Validates the run command arguments and and runs the bridge test.
*/
@Override
public Void call() {
LogUtils.setLogLevel(reusableOptions.logLevel);
ProcessUtils.checkExecutable(executable);
ProcessUtils.checkOutputDirectory(fileOutputPrefix);
runBridgeTest();
return null;
}
/**
* Starts the executable process and writes {@code int} values to a data file and the stdin
* of the executable. Captures stdout of the executable to a file.
*/
private void runBridgeTest() {
final List<String> command = ProcessUtils.buildSubProcessCommand(executable, executableArguments);
try {
final File dataFile = new File(fileOutputPrefix + ".data");
final File outputFile = new File(fileOutputPrefix + ".out");
final File errorFile = new File(fileOutputPrefix + ".err");
// Store int values
final IntBuffer buffer = IntBuffer.allocate(Integer.SIZE * 2);
try (BufferedWriter textOutput = Files.newBufferedWriter(dataFile.toPath())) {
// Write int data using a single bit in all possible positions
int value = 1;
for (int i = 0; i < Integer.SIZE; i++) {
writeInt(textOutput, buffer, value);
value <<= 1;
}
// Write random int data
while (buffer.remaining() != 0) {
writeInt(textOutput, buffer, ThreadLocalRandom.current().nextInt());
}
}
// Pass the same values to the output application
buffer.flip();
final UniformRandomProvider rng = new IntProvider() {
@Override
public int next() {
return buffer.get();
}
};
// Start the application.
final ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectOutput(ProcessBuilder.Redirect.to(outputFile));
builder.redirectError(ProcessBuilder.Redirect.to(errorFile));
final Process testingProcess = builder.start();
// Open the stdin of the process and write to a custom data sink.
// Note: The 'bridge' command only supports 32-bit data in order to
// demonstrate passing suitable data for TestU01 BigCrush.
final Source64Mode source64 = null;
try (RngDataOutput sink = RNGUtils.createDataOutput(rng, source64,
testingProcess.getOutputStream(), buffer.capacity() * 4, byteOrder)) {
sink.write(rng);
}
final Integer exitValue = ProcessUtils.getExitValue(testingProcess);
if (exitValue == null) {
LogUtils.error("%s did not exit. Process was killed.", command.get(0));
} else {
final int value = exitValue;
if (value != 0) {
LogUtils.error("%s exit code = %d", command.get(0), value);
}
}
} catch (IOException ex) {
throw new ApplicationException("Failed to run process: " + ex.getMessage(), ex);
}
}
/**
* Write an {@code int} value to the writer and the buffer output. The native Java
* value will be written to the writer using the debugging output of the
* {@link OutputCommand}.
*
* @param textOutput the text data writer.
* @param buffer the buffer.
* @param value the value.
* @throws IOException Signals that an I/O exception has occurred.
* @see OutputCommand#writeInt(java.io.Writer, int)
*/
private static void writeInt(Writer textOutput,
IntBuffer buffer,
int value) throws IOException {
OutputCommand.writeInt(textOutput, value);
buffer.put(value);
}
}