/*
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.asm.gen;
import java.io.*;
import java.util.*;
import java.util.Arrays;
import java.util.concurrent.*;
import com.sun.max.*;
import com.sun.max.asm.*;
import com.sun.max.asm.dis.*;
import com.sun.max.collect.*;
import com.sun.max.io.*;
import com.sun.max.io.Streams.*;
import com.sun.max.lang.*;
import com.sun.max.program.*;
import com.sun.max.util.*;
/**
* A test framework for assemblers and disassemblers.
* <p>
* For a given instruction set, you can run the assembler and the corresponding external GNU assembler (gas) with the
* same set of arguments and compare the two generated byte arrays for equality. This requires running on a platform on
* which the respective GNU assembler is installed or configuring SSH to enable remote execution on a machine with the
* respective GNU assembler installed (see {@link #setRemoteUserAndHost} for more details).
* <p>
* You can also run the assembler and then apply the disassembler to its result and see if you end up with the same
* method and arguments that you assembled.
* <p>
* Both these tests can be combined in one pass.
* <p>
* A test run iterates over every template in the assembly, and for each of those:
* <ol>
* <li>locates the assembler method in the raw assembler that corresponds to the current template</li>
* <li>generates a sequence of test argument lists based on the current template,</li>
* <li>performs the above described checks for each test argument list, invoking the located raw assembler method with
* it</li>
* </ol>
* <p>
* A sequence of test argument lists is constructed by building the cross product of eligible argument values. The
* eligible values of symbolic arguments comprise all values of the respective argument type. The eligible values of
* numeric arguments better just be a subset of the whole range defined by their argument type... They are a handful of
* values representing all important boundary cases.
* <p>
* As with the original tester in the Klein assembly framework, this framework also features "negative" tests, i.e.
* calls with illegal arguments that should be caught and reported. The number of negative tests is far fewer than in
* the Klein assembly framework as the use of static typing in this framework leaves far fewer opportunities to specify
* illegal arguments without incurring a syntax or type error from javac. The majority of the remaining possibilities
* for specifying illegal arguments lies in RISC assemblers' featuring immediate fields whose ranges of legal values is
* not precisely described by a Java primitive type (e.g. int, short, char, etc).
*/
public abstract class AssemblyTester<Template_Type extends Template> {
private final Assembly<Template_Type> assembly;
private final WordWidth addressWidth;
private final EnumSet<AssemblyTestComponent> components;
protected AssemblyTester(Assembly<Template_Type> assembly, WordWidth addressWidth, EnumSet<AssemblyTestComponent> components) {
this.assembly = assembly;
this.addressWidth = addressWidth;
this.components = components;
this.tmpFilePrefix = System.getProperty("user.name") + "-" + assembly.isa().name().toLowerCase() + "-asmTest-";
}
public Assembly<Template_Type> assembly() {
return assembly;
}
public WordWidth addressWidth() {
return addressWidth;
}
enum TestCaseLegality {
LEGAL, ILLEGAL_BY_CONSTRAINT, ILLEGAL_BY_ARGUMENT
}
/**
* This is an iterator over a (lazily generated) selection of test cases for a given template.
* Using an iterator means that each test case is generated as needed and does not
* use up memory longer than necessary. This helps prevent out of memory errors
* for templates where the number of test cases can be very large.
*
* It is important to note that the value returned by {@link #next()} is only valid
* until {@code next()} is called again. That is, the same {@code Sequence}
* object is returned by each call to {@code next()}, only its contents have changed.
*
*/
class ArgumentListIterator implements Iterator<List<Argument>> {
private final Template_Type template;
private final Parameter[] parameters;
private final Iterator<? extends Argument>[] testArgumentIterators;
private final int count;
private final Argument[] arguments;
private final List<Argument> next;
private final TestCaseLegality testCaseLegality;
private boolean hasNext;
private boolean advanced;
private int iterations;
/**
* Creates an iterator over a set of test cases for a given template.
*
* @param template
* @param legalCases if true, only legal test cases are returned by this iterator otherwise only illegal test
* cases are returned
* @param legalArguments if {@code legalCases == true}, then this parameter is ignored. Otherwise, if true, then
* all the arguments in a returned test case are {@link Parameter#getLegalTestArguments legal}
* otherwise at least one argument in a returned test case will be
* {@link Parameter#getIllegalTestArguments illegal}
*/
ArgumentListIterator(Template_Type template, TestCaseLegality testCaseLegality) {
this.testCaseLegality = testCaseLegality;
this.template = template;
this.parameters = template.parameters().toArray(new Parameter[template.parameters().size()]);
this.count = template.parameters().size();
this.arguments = new Argument[count];
this.next = Arrays.asList(arguments);
final Class<Iterator<? extends Argument>[]> type = null;
this.testArgumentIterators = Utils.cast(type, new Iterator[count]);
this.hasNext = advance();
}
/**
* @return the number of times {@link #next} has been invoked on this iterator without throwing a NoSuchElementException.
*/
public int iterations() {
return iterations;
}
public boolean hasNext() {
if (count == 0) {
return testCaseLegality == TestCaseLegality.LEGAL ? (iterations == 0) : false;
}
if (!advanced) {
hasNext = advance();
}
return hasNext;
}
/**
* The returned sequence is only valid for a single iteration and so should be copied
* if needed after this iteration.
*/
public List<Argument> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
advanced = false;
++iterations;
return next;
}
/**
* Advances this iterator to the next argument list.
*
* @return true if the iterator could advance
*/
private boolean advance() {
assert !advanced;
advanced = true;
if (count == 0) {
return testCaseLegality == TestCaseLegality.LEGAL;
}
boolean result;
do {
result = advance0();
} while (result && isLegalArgumentList(template, next) != (testCaseLegality == TestCaseLegality.LEGAL));
return result;
}
/**
* Advances the test argument iterator for parameter {@code i}.
*
* @return true if the iterator could advance
*/
private boolean advanceArgumentFor(int i) {
if (testArgumentIterators[i].hasNext()) {
arguments[i] = testArgumentIterators[i].next();
return true;
}
return false;
}
/**
* Advances this iterator to the next (potentially invalid) argument list.
*
* @return true if the iterator could advance
*/
private boolean advance0() {
for (int i = count - 1; i >= 0; --i) {
if (testArgumentIterators[i] != null) {
if (advanceArgumentFor(i)) {
return true;
}
if (i == 0) {
return false;
}
}
// Reset iterator over test arguments of the i'th parameter
final Parameter parameter = parameters[i];
if (testCaseLegality != TestCaseLegality.ILLEGAL_BY_ARGUMENT) {
final Iterable<? extends Argument> argumentsIterable = parameter.getLegalTestArguments();
final ArgumentRange argumentRange = parameter.argumentRange();
if (argumentRange == null || !argumentRange.appliesInternally()) {
testArgumentIterators[i] = argumentsIterable.iterator();
} else {
testArgumentIterators[i] = new FilterIterator<Argument>(argumentsIterable.iterator(), new Predicate<Argument>() {
public boolean evaluate(Argument argument) {
return argumentRange.includes(argument);
}
});
}
} else {
Iterator<? extends Argument> iterator = parameter.getIllegalTestArguments().iterator();
if (!iterator.hasNext()) {
// For parameters that have no illegal values (e.g. a symbolic register parameter)
// at least one argument must be returned otherwise illegal test cases where only
// one argument is illegal will never be returned by this iterator.
//
// Instead of iterating over all the legal arguments for such a parameter, only
// the last legal value is used. This involves retrieving the iterator twice but
// that's much cheaper than many more redundant iterations.
iterator = parameter.getLegalTestArguments().iterator();
int n = 0;
while (iterator.hasNext()) {
++n;
iterator.next();
}
iterator = parameter.getLegalTestArguments().iterator();
while (n-- > 1) {
iterator.next();
}
assert iterator.hasNext();
testArgumentIterators[i] = iterator;
} else {
testArgumentIterators[i] = iterator;
}
}
if (!advanceArgumentFor(i)) {
return false;
}
}
return true;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Determines if a given set of arguments for a given template is legal.
*/
protected abstract boolean isLegalArgumentList(Template_Type template, List<Argument> arguments);
protected abstract void assembleExternally(IndentWriter stream, Template_Type template, List<Argument> argumentList, String label);
private final String tmpFilePrefix;
private static final String SOURCE_EXTENSION = ".s";
private static final String BINARY_EXTENSION = ".o";
private boolean findExcludedDisassemblerTestArgument(List<? extends Parameter> parameters, List<Argument> arguments) {
for (int i = 0; i < parameters.size(); i++) {
if (parameters.get(i).excludedDisassemblerTestArguments().contains(arguments.get(i))) {
return true;
}
}
return false;
}
private boolean findExcludedExternalTestArgument(List<? extends Parameter> parameters, List<Argument> arguments) {
for (int i = 0; i < parameters.size(); i++) {
final Parameter parameter = parameters.get(i);
if (parameter.excludedExternalTestArguments().contains(arguments.get(i))) {
return true;
}
final ArgumentRange argumentRange = parameters.get(i).argumentRange();
if (argumentRange != null && !argumentRange.includes(arguments.get(i))) {
return true;
}
}
return false;
}
protected final int nNOPs = 10;
private File createExternalSourceFile(Template_Type template, Iterator<List<Argument>> argumentLists) throws IOException {
final File sourceFile = File.createTempFile(tmpFilePrefix + template.internalName(), SOURCE_EXTENSION);
final IndentWriter stream = new IndentWriter(new PrintWriter(new BufferedWriter(new FileWriter(sourceFile))));
stream.indent();
for (int i = 0; i < nNOPs; i++) {
stream.println("nop");
}
createExternalSource(template, argumentLists, stream);
for (int i = 0; i < nNOPs; i++) {
stream.println("nop");
}
stream.outdent();
stream.close();
return sourceFile;
}
private void createExternalSource(Template_Type template, Iterator<List<Argument>> argumentLists, IndentWriter stream) {
int i = 0;
while (argumentLists.hasNext()) {
final List<Argument> argumentList = argumentLists.next();
if (!findExcludedExternalTestArgument(template.parameters(), argumentList)) {
final String label = "label" + i;
assembleExternally(stream, template, argumentList, label);
}
++i;
}
}
protected abstract String assemblerCommand();
private String remoteAssemblerPath = "";
/**
* Sets the path of the directory on a remote machine containing the
* {@link #assemblerCommand assembler command}. This is required as
* some SSH installations only provide a very minimal environment
* for remotely executed commands.
*
* @param path the absolute path to the directory containing the assembler executable (must not be null)
*/
public void setRemoteAssemblerPath(String path) {
assert path != null;
remoteAssemblerPath = path;
}
private String remoteUserAndHost;
/**
* Sets the {@code user@host} string that will be used to execute the external
* assembler on a remote host via SSH. If this value is not set or set to null, the
* external assembler will be executed on the local machine.
* <p>
* Execution on a remote machine is performed via the use of the 'ssh' and 'scp'
* native executables. As such, these executables must be on the user's path.
* <p>
* The SSH layer authenticates with the remote machine via public key authentication.
* Configuring the local and remote machines for this authentication is described in
* the ssh man page.
* <p>
* Here's the console output
* captured while configuring user {@code dsimon} on machine {@code local}
* to be able to remotely execute via SSH as user {@code dsimon} on machine
* {@code remote}:
* <p>
* <pre>
* [dsimon@local:~]$ <b>ssh-keygen -t dsa</b>
* Generating public/private dsa key pair.
* Enter file in which to save the key (/home/dsimon/.ssh/id_dsa):
* Enter passphrase (empty for no passphrase):
* Enter same passphrase again:
* Your identification has been saved in /home/dsimon/.ssh/id_dsa.
* Your public key has been saved in /home/dsimon/.ssh/id_dsa.pub.
* The key fingerprint is:
* 88:ec:17:53:7a:e1:35:bd:cf:cc:0a:bb:dd:ec:99:8d dsimon@local
* [dsimon@local:~]$ <b>scp /home/dsimon/.ssh/id_dsa.pub dsimon@remote:</b>
* Password:
* id_dsa.pub 100% 617 0.6KB/s 00:00
* [dsimon@local:~]$ ssh dsimon@remote
* Password:
* [dsimon@remote:~]$ <b>{@literal cat id_dsa.pub >> .ssh/authorized_keys2}</b>
* </pre>
*
* @param remoteUserAndHost a {@code user@host} value denoting a machine that
* supports remote execution via SSH2 using public key authentication
*/
public void setRemoteUserAndHost(String remoteUserAndHost) {
this.remoteUserAndHost = remoteUserAndHost;
}
/**
* Executes a command in a subprocess redirecting the standard streams of the
* subprocess to/from the standard streams of the current process.
*
* @param command the command line to execute
*/
private void exec(String command) throws IOException, InterruptedException {
exec(command, System.out, System.err, System.in);
}
/**
* Executes a command in a subprocess redirecting the standard streams of the
* subprocess to/from the supplied streams.
*
* @param command the command line to execute
* @param out the stream to which standard output will be directed
* @param err the stream to which standard error output will be directed
* @param in the stream from which standard input will be read
*/
private void exec(String command, OutputStream out, OutputStream err, InputStream in) throws IOException, InterruptedException {
final Process process = Runtime.getRuntime().exec(command);
try {
final Redirector stderr = Streams.redirect(process, process.getErrorStream(), err, command + " [stderr]", 50);
final Redirector stdout = Streams.redirect(process, process.getInputStream(), out, command + " [stdout]");
final Redirector stdin = Streams.redirect(process, in, process.getOutputStream(), command + " [stdin]");
final int exitValue = process.waitFor();
stderr.close();
stdout.close();
stdin.close();
if (exitValue != 0) {
throw ProgramError.unexpected("execution of command failed: " + command + " [exit code = " + exitValue + "]");
}
} finally {
process.destroy();
}
}
private File createExternalBinaryFile(File sourceFile) throws IOException {
try {
final File binaryFile = new File(sourceFile.getPath().substring(0, sourceFile.getPath().length() - SOURCE_EXTENSION.length()) + BINARY_EXTENSION);
if (remoteUserAndHost != null) {
// Copy input source to remote machine
exec("scp -C " + sourceFile.getAbsolutePath() + " " + remoteUserAndHost + ":" + sourceFile.getName());
// Execute assembler remotely
exec("ssh " + remoteUserAndHost + " " + remoteAssemblerPath + assemblerCommand() + " -o " + binaryFile.getName() + " " + sourceFile.getName());
// Copy output binary to local machine
exec("scp -C " + remoteUserAndHost + ":" + binaryFile.getName() + " " + binaryFile.getAbsolutePath());
// Delete input source and output binary from remote machine
exec("ssh " + remoteUserAndHost + " rm " + binaryFile.getName() + " " + sourceFile.getName());
} else {
exec(assemblerCommand() + " " + sourceFile.getAbsolutePath() + " -o " + binaryFile.getAbsolutePath());
}
return binaryFile;
} catch (InterruptedException e) {
throw new InterruptedIOException(e.toString());
}
}
protected abstract boolean readNop(InputStream stream) throws IOException;
private boolean findStart(InputStream stream) throws IOException {
while (stream.available() > 0) {
if (readNop(stream)) {
boolean found = true;
for (int i = 1; i < nNOPs; i++) {
if (!readNop(stream)) {
found = false;
break;
}
}
if (found) {
return true;
}
}
}
return false;
}
/**
* Gets a string representation of the value and bitrange of each field in an assembled instruction.
* For example, the returned value for an assembled Power PC {@code rldimi} instruction may be
* {@code "opcd[0:5]=31{011111} fxm[12:19]=1{00000001} rs[6:10]=1{00001} bit_11[11:11]=-1{1} res_20[20:20]=0{0} xo_21_30[21:30]=144{0010010000} res_31[31:31]=0{0}"}.
*/
protected abstract String disassembleFields(Template_Type template, byte[] assembledInstruction);
protected abstract byte[] readExternalInstruction(PushbackInputStream stream, Template_Type template, byte[] internalBytes) throws IOException;
protected abstract Assembler createTestAssembler();
protected abstract Disassembler createTestDisassembler();
/**
* We use this more complicated comparison instead of 'Sequence.equals()',
* because some arguments with different identity may have equal values,
* e.g. 'FPStackRegister.ST.value() == FPStackRegister.ST_0.value()'.
* It would have been much more clean to override 'equals()' of those argument classes,
* but they are enums and Java predeclares methods inherited via Enum final :-(
*/
private boolean equals(List<Argument> arguments1, List<Argument> arguments2) {
if (arguments1.size() != arguments2.size()) {
return false;
}
for (int i = 0; i < arguments1.size(); i++) {
final Argument argument1 = arguments1.get(i);
final Argument argument2 = arguments2.get(i);
if (!argument1.equals(argument2)) {
if (!Classes.areRelated(argument1.getClass(), argument2.getClass()) || argument1.asLong() != argument2.asLong()) {
return false;
}
}
}
return true;
}
private void createExternalSource(Template_Type template, IndentWriter stream) {
if (template.isExternallyTestable()) {
final ArgumentListIterator argumentLists = new ArgumentListIterator(template, TestCaseLegality.LEGAL);
createExternalSource(template, argumentLists, stream);
Trace.line(2, "template: " + template + " [" + argumentLists.iterations() + " test cases]");
}
}
private void testDisassembler(Template_Type template, List<Argument> argumentList, byte[] internalResult) throws IOException, AssemblyException {
final BufferedInputStream disassemblyStream = new BufferedInputStream(new ByteArrayInputStream(internalResult));
final Disassembler disassembler = createTestDisassembler();
disassembler.setAbstractionPreference(template.instructionDescription().isSynthetic() ? Disassembler.AbstractionPreference.SYNTHETIC : Disassembler.AbstractionPreference.RAW);
disassembler.setExpectedNumberOfArguments(argumentList.size());
final List<DisassembledObject> disassembledObjects = disassembler.scanOne(disassemblyStream);
boolean matchFound = false;
for (DisassembledObject disassembledObject : disassembledObjects) {
if (disassembledObject instanceof DisassembledInstruction) {
final DisassembledInstruction disassembledInstruction = (DisassembledInstruction) disassembledObject;
matchFound = matchFound ||
(disassembledInstruction.template().isEquivalentTo(template) &&
equals(disassembledInstruction.arguments(), argumentList) &&
Arrays.equals(disassembledInstruction.bytes(), internalResult));
}
}
final int available = disassemblyStream.available();
if (available != 0 || !matchFound) {
System.err.println("internal disassembler test failed - " + disassembledObjects.size() + " false matches found: ");
//final Assembler assembler = createTestAssembler();
//assembly().assemble(assembler, template, argumentList);
//disassemblyStream.reset();
//disassembler.scanOne(disassemblyStream);
if (available != 0) {
System.err.print("extra bytes at end of disassembly stream:");
final int bytesToPrint = Math.min(available, 200);
for (int i = 0; i < bytesToPrint; ++i) {
final int b = disassemblyStream.read();
System.err.print(" 0x" + Integer.toHexString(b));
}
if (bytesToPrint < available) {
System.err.print("... [" + (available - bytesToPrint) + " more]");
}
System.err.println();
}
int matchNumber = 1;
for (DisassembledObject disassembledObject : disassembledObjects) {
if (disassembledObject instanceof DisassembledInstruction) {
final DisassembledInstruction disassembledInstruction = (DisassembledInstruction) disassembledObject;
System.err.println();
System.err.println("False match number " + matchNumber + ":");
System.err.println(" assembled template: " + template);
System.err.println(" disassembled template: " + disassembledInstruction.template());
System.err.println(" assembled arguments: " + argumentList);
System.err.println("disassembled arguments: " + disassembledInstruction.arguments());
System.err.println(" assembled bytes: " + DisassembledInstruction.toHexString(internalResult));
System.err.println(" disassembled bytes: " + DisassembledInstruction.toHexString(disassembledInstruction.bytes()));
++matchNumber;
}
}
throw ProgramError.unexpected("mismatch between internal assembler and disassembler");
}
disassemblyStream.close();
}
private void testTemplate(final Template_Type template, List<File> temporaryFiles) throws IOException, InterruptedException, AssemblyException {
final boolean testingExternally = components.contains(AssemblyTestComponent.EXTERNAL_ASSEMBLER) && template.isExternallyTestable();
// Process legal test cases
final ArgumentListIterator argumentLists = new ArgumentListIterator(template, TestCaseLegality.LEGAL);
ProgramError.check(argumentLists.hasNext(), "no test cases were generated for template: " + template);
final File binaryFile;
final PushbackInputStream externalInputStream;
if (testingExternally) {
final File sourceFile = createExternalSourceFile(template, argumentLists);
temporaryFiles.add(sourceFile);
binaryFile = createExternalBinaryFile(sourceFile);
temporaryFiles.add(binaryFile);
externalInputStream = new PushbackInputStream(new BufferedInputStream(new FileInputStream(binaryFile)));
if (!findStart(externalInputStream)) {
throw ProgramError.unexpected("could not find start sequence in: " + binaryFile.getAbsolutePath());
}
} else {
binaryFile = null;
externalInputStream = null;
}
int testCaseNumber = 0;
for (final ArgumentListIterator iterator = new ArgumentListIterator(template, TestCaseLegality.LEGAL); iterator.hasNext();) {
final List<Argument> argumentList = iterator.next();
final Assembler assembler = createTestAssembler();
assembly().assemble(assembler, template, argumentList);
final byte[] internalResult = assembler.toByteArray();
if (Trace.hasLevel(3)) {
Trace.line(3, "assembleInternally[" + testCaseNumber + "]: " + assembly().createMethodCallString(template, argumentList) + " = " + DisassembledInstruction.toHexString(internalResult));
}
if (components.contains(AssemblyTestComponent.DISASSEMBLER) && template.isDisassemblable() &&
!findExcludedDisassemblerTestArgument(template.parameters(), argumentList)) {
try {
testDisassembler(template, argumentList, internalResult);
} catch (IOException e) {
throw new AssemblyException(e.toString());
}
}
if (testingExternally && !findExcludedExternalTestArgument(template.parameters(), argumentList)) {
final byte[] externalResult = readExternalInstruction(externalInputStream, template, internalResult);
for (int i = 0; i < externalResult.length; i++) {
if (internalResult[i] != externalResult[i]) {
System.err.println("external assembler test case " + testCaseNumber + " failed for template: " + template);
System.err.println("arguments: " + argumentList);
System.err.println("internal result: " + DisassembledInstruction.toHexString(internalResult));
System.err.println("external result: " + DisassembledInstruction.toHexString(externalResult));
System.err.println("internal result fields: " + disassembleFields(template, internalResult));
System.err.println("external result fields: " + disassembleFields(template, externalResult));
ProgramError.unexpected("mismatch between internal and external assembler");
}
}
}
++testCaseNumber;
}
// Process illegal test cases
int illegalTestCaseNumber = 0;
final Set<String> uniqueExceptionMessages = new HashSet<String>();
for (TestCaseLegality testCaseLegality : new TestCaseLegality[]{TestCaseLegality.ILLEGAL_BY_CONSTRAINT, TestCaseLegality.ILLEGAL_BY_ARGUMENT}) {
for (final ArgumentListIterator iterator = new ArgumentListIterator(template, testCaseLegality); iterator.hasNext();) {
final List<Argument> argumentList = iterator.next();
final Assembler assembler = createTestAssembler();
Trace.line(3, "assembleInternally-negative[" + illegalTestCaseNumber + "]: " + assembly().createMethodCallString(template, argumentList));
try {
assembly().assemble(assembler, template, argumentList);
} catch (IllegalArgumentException e) {
final String exceptionMessage = e.getMessage();
uniqueExceptionMessages.add(exceptionMessage);
++illegalTestCaseNumber;
continue;
}
System.err.println("illegal assembler test case " + illegalTestCaseNumber + " did not throw an exception for template: " + template);
System.err.println("arguments: " + argumentList);
ProgramError.unexpected("failed illegal test case");
}
}
Trace.line(2, "template: " + template + " [" + testCaseNumber + " test cases, timings: " +
", " + illegalTestCaseNumber + " illegal test cases]");
for (String message : uniqueExceptionMessages) {
Trace.line(2, " caught expected IllegalArgumentException: " + message);
}
if (testingExternally) {
for (int i = 0; i < nNOPs; i++) {
if (!readNop(externalInputStream)) {
ProgramError.unexpected("end pattern missing in: " + binaryFile.getAbsolutePath());
}
}
externalInputStream.close();
}
}
private String templatePattern;
/**
* Sets the pattern that restricts which templates are tested.
*
* @param pattern if non-null, only templates whose {@link Template#internalName() name} contains {@code pattern} as
* a substring are tested
*/
public void setTemplatePattern(String pattern) {
templatePattern = pattern;
}
/**
* Tests a range of templates.
*
* @param startTemplateSerial the {@linkplain Template#serial() serial} number of the first template to test or -1
* to start with the first template of the {@linkplain #assembly() assembly}.
* @param endTemplateSerial the {@linkplain Template#serial() serial} number of the last template to test or -1 to
* end with the last template of the {@linkplain #assembly() assembly}.
* @param parallelize specifies if the testing should be parallelized
*/
public void run(int startTemplateSerial, int endTemplateSerial, boolean parallelize) {
if (components.contains(AssemblyTestComponent.EXTERNAL_ASSEMBLER)) {
try {
Runtime.getRuntime().exec(assemblerCommand());
} catch (IOException ioException) {
throw ProgramError.unexpected("Could not execute external assembler command '" + assemblerCommand() + "'", ioException);
}
}
final int numberOfWorkerThreads;
if (remoteUserAndHost != null) {
// Don't go parallel when using ssh as the max number of ssh connections on the remote host will most likely be a problem
numberOfWorkerThreads = 1;
} else {
numberOfWorkerThreads = Runtime.getRuntime().availableProcessors();
}
final ThreadPoolExecutor compilerService = (ThreadPoolExecutor) Executors.newFixedThreadPool(numberOfWorkerThreads);
final CompletionService<Template_Type> compilationCompletionService = new ExecutorCompletionService<Template_Type>(compilerService);
long submittedTests = 0;
final List<Template_Type> errors = new LinkedList<Template_Type>();
for (final Template_Type template : assembly().templates()) {
if (template.serial() > endTemplateSerial) {
break;
}
Trace.on(2);
if (!template.isRedundant() && template.serial() >= startTemplateSerial) {
if (templatePattern == null || template.internalName().contains(templatePattern)) {
++submittedTests;
compilationCompletionService.submit(new Callable<Template_Type>() {
public Template_Type call() {
final List<File> temporaryFiles = new ArrayList<File>();
try {
testTemplate(template, temporaryFiles);
} catch (Throwable throwable) {
Trace.line(2, "template: " + template + " failed testing");
throwable.printStackTrace();
synchronized (errors) {
errors.add(template);
}
} finally {
for (File temporaryFile : temporaryFiles) {
if (!temporaryFile.delete()) {
ProgramWarning.message("could not delete temporary file: " + temporaryFile.getAbsolutePath());
}
}
}
return template;
}
});
}
}
}
long completedTests = 0;
while (completedTests < submittedTests) {
try {
final Template_Type template = compilationCompletionService.take().get();
if (numberOfWorkerThreads > 1) {
Trace.line(1, "complete: " + template);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException executionException) {
throw ProgramError.unexpected(executionException.getCause());
}
++completedTests;
}
compilerService.shutdown();
if (!errors.isEmpty()) {
System.err.println("Errors occurred when testing the following templates:");
for (Template_Type template : errors) {
System.err.println(" " + template);
}
throw ProgramError.unexpected(errors.size() + " templates failed testing: see previous stack dumps in test output");
}
}
/**
* Creates external assembler source for testing a range of templates.
*
* @param startTemplateSerial
* the {@linkplain Template#serial() serial} number of the first template to test or -1 to start with the
* first template of the {@linkplain #assembly() assembly}.
* @param endTemplateSerial
* the {@linkplain Template#serial() serial} number of the last template to test or -1 to end with the
* last template of the {@linkplain #assembly() assembly}.
* @param stream
* where to print the generate source. The caller takes responsibility for closing the stream.
*/
public void createExternalSource(int startTemplateSerial, int endTemplateSerial, IndentWriter stream) {
final List<Template_Type> errors = new LinkedList<Template_Type>();
for (Template_Type template : assembly().templates()) {
if (template.serial() > endTemplateSerial) {
break;
}
Trace.on(2);
if (!template.isRedundant() && template.serial() >= startTemplateSerial) {
if (templatePattern == null || template.internalName().contains(templatePattern)) {
createExternalSource(template, stream);
}
}
}
if (!errors.isEmpty()) {
System.err.println("Errors occurred when creating external assembler test cases for the following templates:");
for (Template_Type template : errors) {
System.err.println(" " + template);
}
throw ProgramError.unexpected(errors.size() + " templates failed testing: see previous stack dumps in test output");
}
}
}