Adding the start of the instruction test infrastructure.

This commit is contained in:
Ben Vanik 2013-01-27 02:51:53 -08:00
parent e37821cf36
commit 26d5df0d38
11 changed files with 402 additions and 0 deletions

16
bin/xenia-test Executable file
View File

@ -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 "$@"

37
test/codegen/Makefile Normal file
View File

@ -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)

61
test/codegen/README.md Normal file
View File

@ -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

1
test/codegen/ori.bin Executable file
View File

@ -0,0 +1 @@
`<60><><EFBFBD>

8
test/codegen/ori.dis Normal file
View File

@ -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

5
test/codegen/ori.s Normal file
View File

@ -0,0 +1,5 @@
# REGISTER_IN r4 0xDEADBEEFCAFEBABE
ori r3, r4, 0xFFFF
# REGISTER_OUT r3 0xBABE

View File

@ -3,5 +3,6 @@
'includes': [
'xenia-info/xenia-info.gypi',
'xenia-run/xenia-run.gypi',
'xenia-test/xenia-test.gypi',
],
}

View File

@ -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);

View File

@ -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',
],
},
],
}

View File

@ -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."""

View File

@ -4,4 +4,5 @@
alias xb='python xenia-build.py'
alias xbb='python xenia-build.py build'
alias xbt='python xenia-build.py test'
alias xbc='python xenia-build.py clean'