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': [
|
||||
'xenia-info/xenia-info.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(),
|
||||
'gyp': GypCommand(),
|
||||
'build': BuildCommand(),
|
||||
'test': TestCommand(),
|
||||
'xethunk': XethunkCommand(),
|
||||
'clean': CleanCommand(),
|
||||
'nuke': NukeCommand(),
|
||||
|
@ -224,6 +225,7 @@ class SetupCommand(Command):
|
|||
print ''
|
||||
|
||||
# Binutils.
|
||||
# TODO(benvanik): disable on Windows
|
||||
print '- binutils...'
|
||||
if not os.path.exists('build/binutils'):
|
||||
os.makedirs('build/binutils')
|
||||
|
@ -400,6 +402,35 @@ class BuildCommand(Command):
|
|||
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):
|
||||
"""'xethunk' command."""
|
||||
|
||||
|
|
Loading…
Reference in New Issue