package org.reasm.m68k.assembly.internal;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertThat;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.junit.Test;
import org.reasm.Architecture;
import org.reasm.Assembly;
import org.reasm.AssemblyCompletionStatus;
import org.reasm.AssemblyMessage;
import org.reasm.Configuration;
import org.reasm.Environment;
import org.reasm.FileFetcher;
import org.reasm.UserSymbol;
import org.reasm.m68k.ConfigurationOptions;
import org.reasm.m68k.ConfigurationOptionsTest;
import org.reasm.m68k.M68KArchitecture;
import org.reasm.m68k.messages.InvalidSizeAttributeErrorMessage;
import org.reasm.m68k.messages.NotSupportedOnArchitectureErrorMessage;
import org.reasm.m68k.messages.SizeAttributeNotAllowedErrorMessage;
import org.reasm.messages.UnresolvedSymbolReferenceErrorMessage;
import org.reasm.messages.WrongNumberOfOperandsErrorMessage;
import org.reasm.source.SourceFile;
import org.reasm.testhelpers.EquivalentAssemblyMessage;
import org.reasm.testhelpers.UserSymbolMatcher;
import com.google.common.collect.ImmutableMap;
/**
* Base test class for short M68000 programs.
*
* @author Francis Gagné
*/
public abstract class BaseProgramsTest {
@Nonnull
static final byte[] NO_DATA = new byte[0];
@Nonnull
static final UserSymbolMatcher<?>[] NO_SYMBOLS = new UserSymbolMatcher[0];
@Nonnull
static final AssemblyMessage WRONG_NUMBER_OF_OPERANDS = new WrongNumberOfOperandsErrorMessage();
@Nonnull
static final AssemblyMessage NOT_SUPPORTED_ON_ARCHITECTURE = new NotSupportedOnArchitectureErrorMessage();
@Nonnull
static final AssemblyMessage INVALID_SIZE_ATTRIBUTE_EMPTY = new InvalidSizeAttributeErrorMessage("");
@Nonnull
static final AssemblyMessage INVALID_SIZE_ATTRIBUTE_B = new InvalidSizeAttributeErrorMessage("B");
@Nonnull
static final AssemblyMessage INVALID_SIZE_ATTRIBUTE_W = new InvalidSizeAttributeErrorMessage("W");
@Nonnull
static final AssemblyMessage INVALID_SIZE_ATTRIBUTE_L = new InvalidSizeAttributeErrorMessage("L");
@Nonnull
static final AssemblyMessage INVALID_SIZE_ATTRIBUTE_Z = new InvalidSizeAttributeErrorMessage("Z");
@Nonnull
static final AssemblyMessage SIZE_ATTRIBUTE_NOT_ALLOWED = new SizeAttributeNotAllowedErrorMessage();
@Nonnull
static final AssemblyMessage UNDEFINED_SYMBOL = new UnresolvedSymbolReferenceErrorMessage("UNDEFINED");
@Nonnull
private final String code;
private final int steps;
@Nonnull
private final byte[] output;
@Nonnull
private final M68KArchitecture architecture;
@CheckForNull
private final AssemblyMessage expectedMessage;
@CheckForNull
private final AssemblyMessage[] expectedMessages;
@CheckForNull
private final UserSymbolMatcher<?>[] symbolMatchers;
/**
* Initializes a new BaseProgramsTest.
*
* @param code
* assembly code to assemble
* @param steps
* the number of steps the program is expected to take to assemble completely
* @param output
* the program's output
* @param architecture
* the target architecture
* @param expectedMessage
* an {@link AssemblyMessage} that is expected to be generated while assembling the code
* @param expectedMessages
* an array of {@link AssemblyMessage AssemblyMessages} that are expected to be generated while assembling the code.
* Takes priority over <code>expectedMessage</code>.
* @param symbolMatchers
* an array of {@link UserSymbolMatcher UserSymbolMatchers} that match the {@link UserSymbol UserSymbols} that are
* expected to be generated while assembling the code, or <code>null</code> to omit checking the generated symbols
*/
public BaseProgramsTest(@Nonnull String code, int steps, @Nonnull byte[] output, @Nonnull M68KArchitecture architecture,
@CheckForNull AssemblyMessage expectedMessage, @CheckForNull AssemblyMessage[] expectedMessages,
@CheckForNull UserSymbolMatcher<?>[] symbolMatchers) {
this.code = code;
this.steps = steps;
this.output = output;
this.architecture = architecture;
this.expectedMessage = expectedMessage;
this.expectedMessages = expectedMessages;
this.symbolMatchers = symbolMatchers;
}
/**
* Asserts that a program assembles correctly.
*
* @throws IOException
* an I/O exception occurred
*/
@Test
public void assemble() throws IOException {
try {
final Environment environment = this.getEnvironment();
final SourceFile mainSourceFile = new SourceFile(this.code, null);
final Configuration configuration = new Configuration(environment, mainSourceFile, this.architecture).setFileFetcher(
this.getFileFetcher()).setCustomConfigurationOptions(this.getCustomConfigurationOptions());
final Assembly assembly = new Assembly(configuration);
int steps = 0;
AssemblyCompletionStatus status;
do {
assertThat("The assembly is performing more steps than expected.", steps, is(lessThan(this.steps)));
status = assembly.step();
++steps;
} while (status != AssemblyCompletionStatus.COMPLETE);
assertThat("The assembly is performing fewer steps than expected.", steps, is(this.steps));
if (this.expectedMessages != null) {
final EquivalentAssemblyMessage[] matchers = new EquivalentAssemblyMessage[this.expectedMessages.length];
for (int i = 0; i < this.expectedMessages.length; i++) {
matchers[i] = new EquivalentAssemblyMessage(this.expectedMessages[i]);
}
assertThat(assembly.getMessages(), contains(matchers));
} else if (this.expectedMessage != null) {
assertThat(assembly.getMessages(), contains(new EquivalentAssemblyMessage(this.expectedMessage)));
} else {
assertThat(assembly.getMessages(), is(empty()));
}
if (this.symbolMatchers != null) {
assertThat(assembly.getSymbols(), containsInAnyOrder(this.symbolMatchers));
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
assembly.writeAssembledDataTo(out);
final byte[] outputBytes = out.toByteArray();
assertThat(outputBytes.length, is(this.output.length));
for (int i = 0; i < this.output.length; i++) {
assertThat(outputBytes[i], is(this.output[i]));
}
} catch (AssertionError e) {
throw new AssertionError(this.code + "\n" + e.getMessage(), e);
}
}
/**
* Gets a {@link Map} of configuration options to pass to
* {@link Configuration#Configuration(Environment, SourceFile, Architecture)}.
*
* @return the {@link Map} of configuration options
*/
@Nonnull
protected Map<Object, Object> getCustomConfigurationOptions() {
final Map<String, Object> m68kOptions = this.getM68KConfigurationOptions();
if (m68kOptions != null) {
return ImmutableMap.of(ConfigurationOptions.KEY,
(Object) ConfigurationOptions.create(m68kOptions, ConfigurationOptionsTest.FAILING_CONSUMER));
}
return ImmutableMap.of();
}
/**
* Gets the {@link Environment} to pass to {@link Configuration#Configuration(Environment, SourceFile, Architecture)}.
*
* @return the {@link Environment}
*/
@Nonnull
protected Environment getEnvironment() {
return Environment.DEFAULT;
}
/**
* Gets the {@link FileFetcher} to pass to {@link Configuration#setFileFetcher(FileFetcher)}.
*
* @return the {@link FileFetcher}
*/
@CheckForNull
protected FileFetcher getFileFetcher() {
return null;
}
/**
* Gets a {@link Map} of M68K-specific configuration options to use for the test class.
*
* @return the {@link Map} of configuration options, or <code>null</code> to use the default options
*/
@CheckForNull
protected Map<String, Object> getM68KConfigurationOptions() {
return null;
}
}