Adding the start of the instruction test infrastructure.
This commit is contained in:
parent
e37821cf36
commit
26d5df0d38
|
@ -0,0 +1,16 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "$0" )" && pwd )"
|
||||||
|
|
||||||
|
CONFIG=release
|
||||||
|
case "$*" in
|
||||||
|
(*--debug*) CONFIG=debug;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
EXEC=$DIR/../build/xenia/$CONFIG/xenia-test
|
||||||
|
|
||||||
|
if [ ! -f "$EXEC" ]; then
|
||||||
|
python $DIR/../xenia-build.py build --$CONFIG
|
||||||
|
fi
|
||||||
|
|
||||||
|
$EXEC "$@"
|
|
@ -0,0 +1,37 @@
|
||||||
|
BINUTILS=../../build/binutils/
|
||||||
|
PPC_AS=$(BINUTILS)/gas/as-new
|
||||||
|
PPC_LD=$(BINUTILS)/ld/ld-new
|
||||||
|
PPC_OBJDUMP=$(BINUTILS)/binutils/objdump
|
||||||
|
|
||||||
|
SRCS=$(wildcard *.s)
|
||||||
|
BINS=$(SRCS:.s=.bin)
|
||||||
|
DISASMS=$(SRCS:.s=.dis)
|
||||||
|
|
||||||
|
%.o: %.s
|
||||||
|
$(PPC_AS) \
|
||||||
|
-a64 \
|
||||||
|
-be \
|
||||||
|
-mregnames \
|
||||||
|
-mpower7 \
|
||||||
|
-maltivec \
|
||||||
|
-mvsx \
|
||||||
|
-R \
|
||||||
|
-o $@ \
|
||||||
|
$<
|
||||||
|
|
||||||
|
%.dis: %.o
|
||||||
|
$(PPC_OBJDUMP) --adjust-vma=0x82010000 -Mpower7 -D -EB $< > $@
|
||||||
|
|
||||||
|
%.bin: %.o
|
||||||
|
$(PPC_LD) \
|
||||||
|
-A powerpc:common64 \
|
||||||
|
-melf64ppc \
|
||||||
|
-EB \
|
||||||
|
-nostdlib \
|
||||||
|
--oformat binary \
|
||||||
|
-Ttext 0x82010000 \
|
||||||
|
-e 0x82010000 \
|
||||||
|
-o $@ \
|
||||||
|
$<
|
||||||
|
|
||||||
|
all: $(BINS) $(DISASMS)
|
|
@ -0,0 +1,61 @@
|
||||||
|
# Codegen Tests
|
||||||
|
|
||||||
|
This directory contains the test assets used by the automated codegen test
|
||||||
|
runner.
|
||||||
|
|
||||||
|
Each test is structured as a source `[name].s` PPC assembly file and the
|
||||||
|
generated outputs. The outputs are made using the custom build of binutils
|
||||||
|
setup when `xenia-build setup` is called and are checked in to make it easier
|
||||||
|
to run the tests on Windows.
|
||||||
|
|
||||||
|
Tests are run using the `xenia-test` app or via `xenia-build test`.
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
The test binary is placed into memory at `0x82010000` and all other memory is
|
||||||
|
zeroed.
|
||||||
|
|
||||||
|
All registers are reset to zero. In order to provide useful inputs tests can
|
||||||
|
specify `# REGISTER_IN` values.
|
||||||
|
|
||||||
|
The code is jumped into at the starting address and executed until the last
|
||||||
|
instruction in the input file is reached.
|
||||||
|
|
||||||
|
After all instructions complete any `# REGISTER_OUT` values are checked and if
|
||||||
|
they do not match the test is failed.
|
||||||
|
|
||||||
|
## Annotations
|
||||||
|
|
||||||
|
Annotations can appear at any line in a file. If a number is required it can
|
||||||
|
be in either hex or decimal form, or IEEE if floating-point.
|
||||||
|
|
||||||
|
### REGISTER_IN
|
||||||
|
|
||||||
|
```
|
||||||
|
# REGISTER_IN [register name] [register value]
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets the value of a register prior to executing the instructions.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```
|
||||||
|
# REGISTER_IN r4 0x1234
|
||||||
|
# REGISTER_IN r4 5678
|
||||||
|
```
|
||||||
|
|
||||||
|
### REGISTER_OUT
|
||||||
|
|
||||||
|
```
|
||||||
|
# REGISTER_OUT [register name] [register value]
|
||||||
|
```
|
||||||
|
|
||||||
|
Defines the expected register value when the instructions have executed.
|
||||||
|
If after all instructions have completed the register value does not match
|
||||||
|
the value given here the test will fail.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
```
|
||||||
|
# REGISTER_OUT r3 123
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO: memory setup/assertions
|
|
@ -0,0 +1 @@
|
||||||
|
`<60><><EFBFBD>
|
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
ori.o: file format elf64-powerpc
|
||||||
|
|
||||||
|
|
||||||
|
Disassembly of section .text:
|
||||||
|
|
||||||
|
0000000082010000 <.text>:
|
||||||
|
82010000: 60 83 ff ff ori r3,r4,65535
|
|
@ -0,0 +1,5 @@
|
||||||
|
# REGISTER_IN r4 0xDEADBEEFCAFEBABE
|
||||||
|
|
||||||
|
ori r3, r4, 0xFFFF
|
||||||
|
|
||||||
|
# REGISTER_OUT r3 0xBABE
|
|
@ -3,5 +3,6 @@
|
||||||
'includes': [
|
'includes': [
|
||||||
'xenia-info/xenia-info.gypi',
|
'xenia-info/xenia-info.gypi',
|
||||||
'xenia-run/xenia-run.gypi',
|
'xenia-run/xenia-run.gypi',
|
||||||
|
'xenia-test/xenia-test.gypi',
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <xenia/xenia.h>
|
||||||
|
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
using namespace xe;
|
||||||
|
using namespace xe::cpu;
|
||||||
|
using namespace xe::kernel;
|
||||||
|
|
||||||
|
|
||||||
|
DEFINE_string(test_path, "test/codegen/",
|
||||||
|
"Directory scanned for test files.");
|
||||||
|
|
||||||
|
|
||||||
|
typedef vector<pair<string, string> > annotations_list_t;
|
||||||
|
|
||||||
|
|
||||||
|
int read_annotations(xe_pal_ref pal, string& src_file_path,
|
||||||
|
annotations_list_t& annotations) {
|
||||||
|
// TODO(benvanik): use PAL instead of this
|
||||||
|
FILE* f = fopen(src_file_path.c_str(), "r");
|
||||||
|
char line_buffer[BUFSIZ];
|
||||||
|
while (fgets(line_buffer, sizeof(line_buffer), f)) {
|
||||||
|
if (strlen(line_buffer) > 3 &&
|
||||||
|
line_buffer[0] == '#' &&
|
||||||
|
line_buffer[1] == ' ') {
|
||||||
|
// Comment - check if formed like an annotation.
|
||||||
|
// We don't actually verify anything here.
|
||||||
|
char* next_space = strchr(line_buffer + 3, ' ');
|
||||||
|
if (next_space) {
|
||||||
|
// Looks legit.
|
||||||
|
string key = string(line_buffer + 2, next_space);
|
||||||
|
string value = string(next_space + 1);
|
||||||
|
while (value.find_last_of(" \t\n") == value.size() - 1) {
|
||||||
|
value.erase(value.end() - 1);
|
||||||
|
}
|
||||||
|
annotations.push_back(pair<string, string>(key, value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int setup_test_state(xe_memory_ref memory, Processor* processor,
|
||||||
|
annotations_list_t& annotations) {
|
||||||
|
for (annotations_list_t::iterator it = annotations.begin();
|
||||||
|
it != annotations.end(); ++it) {
|
||||||
|
if (it->first == "REGISTER_IN") {
|
||||||
|
printf("REGISTER_IN : %s\n", it->second.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int check_test_results(xe_memory_ref memory, Processor* processor,
|
||||||
|
annotations_list_t& annotations) {
|
||||||
|
for (annotations_list_t::iterator it = annotations.begin();
|
||||||
|
it != annotations.end(); ++it) {
|
||||||
|
if (it->first == "REGISTER_OUT") {
|
||||||
|
printf("REGISTER_OUT : %s\n", it->second.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_test(xe_pal_ref pal, string& src_file_path) {
|
||||||
|
int result_code = 1;
|
||||||
|
|
||||||
|
// test.s -> test.bin
|
||||||
|
string bin_file_path;
|
||||||
|
size_t dot = src_file_path.find_last_of(".s");
|
||||||
|
bin_file_path = src_file_path;
|
||||||
|
bin_file_path.replace(dot - 1, 2, ".bin");
|
||||||
|
|
||||||
|
xe_memory_ref memory = NULL;
|
||||||
|
shared_ptr<Processor> processor;
|
||||||
|
shared_ptr<Runtime> runtime;
|
||||||
|
annotations_list_t annotations;
|
||||||
|
|
||||||
|
XEEXPECTZERO(read_annotations(pal, src_file_path, annotations));
|
||||||
|
|
||||||
|
xe_memory_options_t memory_options;
|
||||||
|
xe_zero_struct(&memory_options, sizeof(memory_options));
|
||||||
|
memory = xe_memory_create(pal, memory_options);
|
||||||
|
XEEXPECTNOTNULL(memory);
|
||||||
|
|
||||||
|
processor = shared_ptr<Processor>(new Processor(pal, memory));
|
||||||
|
XEEXPECTZERO(processor->Setup());
|
||||||
|
|
||||||
|
runtime = shared_ptr<Runtime>(new Runtime(pal, processor, XT("")));
|
||||||
|
|
||||||
|
// TODO(benvanik): load test binary file into memory
|
||||||
|
// bin_file_path
|
||||||
|
// XEEXPECTZERO(runtime->LoadModule(path));
|
||||||
|
|
||||||
|
// Setup test state from annotations.
|
||||||
|
XEEXPECTZERO(setup_test_state(memory, processor.get(), annotations));
|
||||||
|
|
||||||
|
// Execute test.
|
||||||
|
// TODO(benvanik): execute test
|
||||||
|
|
||||||
|
// Assert test state expectations.
|
||||||
|
XEEXPECTZERO(check_test_results(memory, processor.get(), annotations));
|
||||||
|
|
||||||
|
result_code = 0;
|
||||||
|
XECLEANUP:
|
||||||
|
runtime.reset();
|
||||||
|
processor.reset();
|
||||||
|
xe_memory_release(memory);
|
||||||
|
return result_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
int discover_tests(string& test_path,
|
||||||
|
vector<string>& test_files) {
|
||||||
|
// TODO(benvanik): use PAL instead of this
|
||||||
|
DIR* d = opendir(test_path.c_str());
|
||||||
|
struct dirent* dir;
|
||||||
|
while ((dir = readdir(d))) {
|
||||||
|
if (dir->d_type == DT_REG) {
|
||||||
|
// Only return .s files.
|
||||||
|
string file_name = string(dir->d_name);
|
||||||
|
if (file_name.rfind(".s") != string::npos) {
|
||||||
|
string file_path = test_path;
|
||||||
|
if (*(test_path.end() - 1) != '/') {
|
||||||
|
file_path += "/";
|
||||||
|
}
|
||||||
|
file_path += file_name;
|
||||||
|
test_files.push_back(file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_tests(const xechar_t* test_name) {
|
||||||
|
int result_code = 1;
|
||||||
|
int failed_count = 0;
|
||||||
|
int passed_count = 0;
|
||||||
|
|
||||||
|
xe_pal_ref pal = NULL;
|
||||||
|
vector<string> test_files;
|
||||||
|
|
||||||
|
xe_pal_options_t pal_options;
|
||||||
|
xe_zero_struct(&pal_options, sizeof(pal_options));
|
||||||
|
pal = xe_pal_create(pal_options);
|
||||||
|
XEEXPECTNOTNULL(pal);
|
||||||
|
|
||||||
|
XEEXPECTZERO(discover_tests(FLAGS_test_path, test_files));
|
||||||
|
if (!test_files.size()) {
|
||||||
|
printf("No tests discovered - invalid path?\n");
|
||||||
|
XEFAIL();
|
||||||
|
}
|
||||||
|
printf("%d tests discovered.\n", (int)test_files.size());
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
for (vector<string>::iterator it = test_files.begin();
|
||||||
|
it != test_files.end(); ++it) {
|
||||||
|
if (test_name && *it != test_name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Running %s... ", (*it).c_str());
|
||||||
|
if (run_test(pal, *it)) {
|
||||||
|
printf("FAILED\n");
|
||||||
|
failed_count++;
|
||||||
|
} else {
|
||||||
|
printf("PASSED\n");
|
||||||
|
passed_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
printf("Total tests: %d\n", failed_count + passed_count);
|
||||||
|
printf("Passed: %d\n", passed_count);
|
||||||
|
printf("Failed: %d\n", failed_count);
|
||||||
|
|
||||||
|
result_code = failed_count ? 1 : 0;
|
||||||
|
XECLEANUP:
|
||||||
|
xe_pal_release(pal);
|
||||||
|
return result_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xenia_test(int argc, xechar_t **argv) {
|
||||||
|
string usage = "usage: ";
|
||||||
|
usage = usage + argv[0] + " some.xex";
|
||||||
|
google::SetUsageMessage(usage);
|
||||||
|
google::SetVersionString("1.0");
|
||||||
|
google::ParseCommandLineFlags(&argc, &argv, true);
|
||||||
|
|
||||||
|
int result_code = 1;
|
||||||
|
|
||||||
|
// Grab test name, if present.
|
||||||
|
const xechar_t* test_name = NULL;
|
||||||
|
if (argc >= 2) {
|
||||||
|
test_name = argv[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
result_code = run_tests(test_name);
|
||||||
|
|
||||||
|
google::ShutDownCommandLineFlags();
|
||||||
|
return result_code;
|
||||||
|
}
|
||||||
|
XE_MAIN_THUNK(xenia_test);
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Copyright 2013 Ben Vanik. All Rights Reserved.
|
||||||
|
{
|
||||||
|
'targets': [
|
||||||
|
{
|
||||||
|
'target_name': 'xenia-test',
|
||||||
|
'type': 'executable',
|
||||||
|
|
||||||
|
'dependencies': [
|
||||||
|
'xeniacore',
|
||||||
|
'xeniacpu',
|
||||||
|
'xeniagpu',
|
||||||
|
'xeniakernel',
|
||||||
|
],
|
||||||
|
|
||||||
|
'include_dirs': [
|
||||||
|
'.',
|
||||||
|
],
|
||||||
|
|
||||||
|
'sources': [
|
||||||
|
'xenia-test.cc',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
|
@ -61,6 +61,7 @@ def discover_commands():
|
||||||
'pull': PullCommand(),
|
'pull': PullCommand(),
|
||||||
'gyp': GypCommand(),
|
'gyp': GypCommand(),
|
||||||
'build': BuildCommand(),
|
'build': BuildCommand(),
|
||||||
|
'test': TestCommand(),
|
||||||
'xethunk': XethunkCommand(),
|
'xethunk': XethunkCommand(),
|
||||||
'clean': CleanCommand(),
|
'clean': CleanCommand(),
|
||||||
'nuke': NukeCommand(),
|
'nuke': NukeCommand(),
|
||||||
|
@ -224,6 +225,7 @@ class SetupCommand(Command):
|
||||||
print ''
|
print ''
|
||||||
|
|
||||||
# Binutils.
|
# Binutils.
|
||||||
|
# TODO(benvanik): disable on Windows
|
||||||
print '- binutils...'
|
print '- binutils...'
|
||||||
if not os.path.exists('build/binutils'):
|
if not os.path.exists('build/binutils'):
|
||||||
os.makedirs('build/binutils')
|
os.makedirs('build/binutils')
|
||||||
|
@ -400,6 +402,35 @@ class BuildCommand(Command):
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
class TestCommand(Command):
|
||||||
|
"""'test' command."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(TestCommand, self).__init__(
|
||||||
|
name='test',
|
||||||
|
help_short='Runs all tests.',
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
def execute(self, args, cwd):
|
||||||
|
print 'Testing...'
|
||||||
|
print ''
|
||||||
|
|
||||||
|
# First run make and update all of the test files.
|
||||||
|
# TOOD(benvanik): disable on Windows
|
||||||
|
print 'Updating test files...'
|
||||||
|
result = shell_call('make -C test/codegen/')
|
||||||
|
print ''
|
||||||
|
if result != 0:
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Start the test runner.
|
||||||
|
print 'Launching test runner...'
|
||||||
|
result = shell_call('bin/xenia-test')
|
||||||
|
print ''
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class XethunkCommand(Command):
|
class XethunkCommand(Command):
|
||||||
"""'xethunk' command."""
|
"""'xethunk' command."""
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue