4567 - support automated tests in SubX
All it takes is to code-generate a simple function called 'run_tests' that calls all functions starting with 'test_' one by one. I've temporarily switched the factorial app to run as a test. But that's temporary, because all the code to print '.' vs 'F' needs to get extracted out into a helper.
This commit is contained in:
parent
0828df68de
commit
caaeccd68e
|
@ -0,0 +1,104 @@
|
||||||
|
//: Beginning of level 3: support for automatically aggregating functions into
|
||||||
|
//: test suites.
|
||||||
|
//:
|
||||||
|
//: (As explained in the transform layer, level 3 runs before level 2. We
|
||||||
|
//: can't use any of the transforms in previous layers. But we *do* rely on
|
||||||
|
//: those concepts being present in the input. Particularly labels.)
|
||||||
|
|
||||||
|
:(after "Begin Transforms")
|
||||||
|
// Begin Level-3 Transforms
|
||||||
|
Transform.push_back(create_test_function);
|
||||||
|
// End Level-3 Transforms
|
||||||
|
|
||||||
|
:(scenario run_test)
|
||||||
|
% Reg[ESP].u = 0x100;
|
||||||
|
== 0x1
|
||||||
|
main:
|
||||||
|
e8/call run_tests/disp32 # 5 bytes
|
||||||
|
f4/halt # 1 byte
|
||||||
|
|
||||||
|
test_foo: # offset 7
|
||||||
|
01 d8 # just some unique instruction: add EBX to EAX
|
||||||
|
c3/return
|
||||||
|
|
||||||
|
# check that code in test_foo ran (implicitly called by run_tests)
|
||||||
|
+run: inst: 0x00000007
|
||||||
|
|
||||||
|
:(code)
|
||||||
|
void create_test_function(program& p) {
|
||||||
|
if (p.segments.empty()) return;
|
||||||
|
segment& code = p.segments.at(0);
|
||||||
|
trace(99, "transform") << "-- create 'run_tests'" << end();
|
||||||
|
vector<line> new_insts;
|
||||||
|
for (int i = 0; i < SIZE(code.lines); ++i) {
|
||||||
|
line& inst = code.lines.at(i);
|
||||||
|
for (int j = 0; j < SIZE(inst.words); ++j) {
|
||||||
|
const word& curr = inst.words.at(j);
|
||||||
|
if (*curr.data.rbegin() != ':') continue; // not a label
|
||||||
|
if (!starts_with(curr.data, "test_")) continue;
|
||||||
|
string fn = drop_last(curr.data);
|
||||||
|
new_insts.push_back(call(fn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (new_insts.empty()) return; // no tests found
|
||||||
|
code.lines.push_back(label("run_tests"));
|
||||||
|
code.lines.insert(code.lines.end(), new_insts.begin(), new_insts.end());
|
||||||
|
code.lines.push_back(ret());
|
||||||
|
}
|
||||||
|
|
||||||
|
string to_string(const segment& s) {
|
||||||
|
ostringstream out;
|
||||||
|
for (int i = 0; i < SIZE(s.lines); ++i) {
|
||||||
|
const line& l = s.lines.at(i);
|
||||||
|
for (int j = 0; j < SIZE(l.words); ++j) {
|
||||||
|
if (j > 0) out << ' ';
|
||||||
|
out << to_string(l.words.at(j));
|
||||||
|
}
|
||||||
|
out << '\n';
|
||||||
|
}
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
string to_string(const word& w) {
|
||||||
|
ostringstream out;
|
||||||
|
out << w.data;
|
||||||
|
for (int i = 0; i < SIZE(w.metadata); ++i)
|
||||||
|
out << '/' << w.metadata.at(i);
|
||||||
|
return out.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
line label(string s) {
|
||||||
|
line result;
|
||||||
|
result.words.push_back(word());
|
||||||
|
result.words.back().data = (s+":");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
line call(string s) {
|
||||||
|
line result;
|
||||||
|
result.words.push_back(call());
|
||||||
|
result.words.push_back(disp32(s));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
word call() {
|
||||||
|
word result;
|
||||||
|
result.data = "e8";
|
||||||
|
result.metadata.push_back("call");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
word disp32(string s) {
|
||||||
|
word result;
|
||||||
|
result.data = s;
|
||||||
|
result.metadata.push_back("disp32");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
line ret() {
|
||||||
|
line result;
|
||||||
|
result.words.push_back(word());
|
||||||
|
result.words.back().data = "c3";
|
||||||
|
result.words.back().metadata.push_back("return");
|
||||||
|
return result;
|
||||||
|
}
|
Binary file not shown.
|
@ -13,17 +13,18 @@
|
||||||
# 1-3 bytes 3 bits 2 bits 3 bits 3 bits 3 bits 2 bits 2 bits 0/1/2/4 bytes 0/1/2/4 bytes
|
# 1-3 bytes 3 bits 2 bits 3 bits 3 bits 3 bits 2 bits 2 bits 0/1/2/4 bytes 0/1/2/4 bytes
|
||||||
|
|
||||||
# main:
|
# main:
|
||||||
# prepare to make a call
|
e8/call run_tests/disp32
|
||||||
55/push . . . . . . . . # push EBP
|
#? # prepare to make a call
|
||||||
89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
|
#? 55/push . . . . . . . . # push EBP
|
||||||
# factorial(5)
|
#? 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP
|
||||||
68/push . . . . . . . 5/imm32 # push 5
|
#? # factorial(5)
|
||||||
e8/call . . . . . . factorial/disp32
|
#? 68/push . . . . . . . 5/imm32 # push 5
|
||||||
# discard arg
|
#? e8/call . . . . . . factorial/disp32
|
||||||
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add 4 to ESP
|
#? # discard arg
|
||||||
# clean up after call
|
#? 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add 4 to ESP
|
||||||
89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
|
#? # clean up after call
|
||||||
5d/pop . . . . . . . . # pop to EBP
|
#? 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP
|
||||||
|
#? 5d/pop . . . . . . . . # pop to EBP
|
||||||
|
|
||||||
# exit(EAX)
|
# exit(EAX)
|
||||||
89/copy 3/mod/direct 3/rm32/EBX . . . 0/r32/EAX . . # copy EAX to EBX
|
89/copy 3/mod/direct 3/rm32/EBX . . . 0/r32/EAX . . # copy EAX to EBX
|
||||||
|
@ -59,4 +60,67 @@ factorial:
|
||||||
$factorial:exit:
|
$factorial:exit:
|
||||||
c3/return
|
c3/return
|
||||||
|
|
||||||
|
test_factorial:
|
||||||
|
# factorial(5)
|
||||||
|
# push arg
|
||||||
|
68/push . . . . . . . 5/imm32 # push 5
|
||||||
|
# call
|
||||||
|
e8/call . . . . . . factorial/disp32
|
||||||
|
# discard arg
|
||||||
|
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add 4 to ESP
|
||||||
|
# if EAX == 120
|
||||||
|
3d/compare . . . . . . . 0x78/imm32/120 # compare EAX with 120
|
||||||
|
75/jump-if-unequal . . . . . . $test_factorial:else/disp8
|
||||||
|
# print('.')
|
||||||
|
# push args
|
||||||
|
68/push . . . . . . . Test_passed/imm32
|
||||||
|
# call
|
||||||
|
e8/call . . . . . . write_stderr/disp32
|
||||||
|
# discard arg
|
||||||
|
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add 4 to ESP
|
||||||
|
# return
|
||||||
|
c3/return
|
||||||
|
# else:
|
||||||
|
$test_factorial:else:
|
||||||
|
# print('F')
|
||||||
|
# push args
|
||||||
|
68/push . . . . . . . Test_failed/imm32
|
||||||
|
# call
|
||||||
|
e8/call . . . . . . write_stderr/disp32
|
||||||
|
# discard arg
|
||||||
|
81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add 4 to ESP
|
||||||
|
# end
|
||||||
|
c3/return
|
||||||
|
|
||||||
|
## helpers
|
||||||
|
|
||||||
|
write_stderr: # s : (address array byte) -> <void>
|
||||||
|
# write(2/stderr, (data) s+4, (size) *s)
|
||||||
|
# fd = 2 (stderr)
|
||||||
|
bb/copy . . . . . . . 2/imm32 # copy 2 to EBX
|
||||||
|
# x = s+4
|
||||||
|
8b/copy 1/mod/*+disp8 4/rm32/SIB 4/base/ESP 4/index/none . 1/r32/ECX 4/disp8 . # copy *(ESP+4) to ECX
|
||||||
|
81 0/subop/add 3/mod/direct 1/rm32/ECX . . . . . 4/imm32 # add 4 to ECX
|
||||||
|
# size = *s
|
||||||
|
8b/copy 1/mod/*+disp8 4/rm32/SIB 4/base/ESP 4/index/none . 2/r32/EDX 4/disp8 . # copy *(ESP+4) to EDX
|
||||||
|
8b/copy 0/mod/indirect 2/rm32/EDX . . . 2/r32/EDX . . # copy *EDX to EDX
|
||||||
|
# call write()
|
||||||
|
b8/copy . . . . . . . 4/imm32/write # copy 1 to EAX
|
||||||
|
cd/syscall . . . . . . . 0x80/imm8 # int 80h
|
||||||
|
# end
|
||||||
|
c3/return
|
||||||
|
|
||||||
|
== data
|
||||||
|
Test_passed:
|
||||||
|
# size
|
||||||
|
01 00 00 00
|
||||||
|
# data
|
||||||
|
2e/dot
|
||||||
|
|
||||||
|
Test_failed:
|
||||||
|
# size
|
||||||
|
01 00 00 00
|
||||||
|
# data
|
||||||
|
46/F
|
||||||
|
|
||||||
# vim:ft=subx:nowrap:so=0
|
# vim:ft=subx:nowrap:so=0
|
||||||
|
|
Loading…
Reference in New Issue