# Mu - subx/040---tests.cc

     1 //: Automatically aggregate functions starting with 'test-' into a test suite
     2 //: called 'run-tests'. Running this function will run all tests.
     3 //:
     4 //: This is actually SubX's first (trivial) compiler. We generate all the code
     5 //: needed for the 'run-tests' function.
     6 //:
     7 //: By convention, temporary functions needed by tests will start with
     8 //: '_test-'.
     9
    10 //: We don't rely on any transforms running in previous layers, but this layer
    11 //: knows about labels and will emit labels for previous layers to transform.
    12 :(after "Begin Transforms")
    13 // Begin Level-4 Transforms
    14 Transform.push_back(create_test_function);
    15 // End Level-4 Transforms
    16
    17 :(scenario run_test)
    18 % Reg[ESP].u = 0x100;
    19 == 0x1
    20 main:
    21   e8/call run-tests/disp32  # 5 bytes
    22   f4/halt                   # 1 byte
    23
    24 test-foo:  # offset 7
    25   01 d8  # just some unique instruction: add EBX to EAX
    26   c3/return
    27
    28 # check that code in test_foo ran (implicitly called by run-tests)
    29 +run: inst: 0x00000007
    30
    31 :(code)
    32 void create_test_function(program& p) {
    33   if (p.segments.empty()) return;
    34   segment& code = p.segments.at(0);
    35   trace(99, "transform") << "-- create 'run-tests'" << end();
    36   vector<line> new_insts;
    37   for (int i = 0;  i < SIZE(code.lines);  ++i) {
    38     line& inst = code.lines.at(i);
    39     for (int j = 0;  j < SIZE(inst.words);  ++j) {
    40       const word& curr = inst.words.at(j);
    41       if (*curr.data.rbegin() != ':') continue;  // not a label
    42       if (!starts_with(curr.data, "test-")) continue;
    43       string fn = drop_last(curr.data);
    44       new_insts.push_back(call(fn));
    45     }
    46   }
    47   if (new_insts.empty()) return;  // no tests found
    48   code.lines.push_back(label("run-tests"));
    49   code.lines.insert(code.lines.end(), new_insts.begin(), new_insts.end());
    50   code.lines.push_back(ret());
    51 }
    52
    53 string to_string(const segment& s) {
    54   ostringstream out;
    55   for (int i = 0;  i < SIZE(s.lines);  ++i) {
    56     const line& l = s.lines.at(i);
    57     for (int j = 0;  j < SIZE(l.words);  ++j) {
    58       if (j > 0) out << ' ';
    59       out << to_string(l.words.at(j));
    60     }
    61     out << '\n';
    62   }
    63   return out.str();
    64 }
    65
    66 line call(string s) {
    67   line result;
    68   result.words.push_back(call());
    69   result.words.push_back(disp32(s));
    70   return result;
    71 }
    72
    73 word call() {
    74   word result;
    75   result.data = "e8";
    76   result.metadata.push_back("call");
    77   return result;
    78 }
    79
    80 word disp32(string s) {
    81   word result;
    82   result.data = s;
    83   result.metadata.push_back("disp32");
    84   return result;
    85 }
    86
    87 line ret() {
    88   line result;
    89   result.words.push_back(word());
    90   result.words.back().data = "c3";
    91   result.words.back().metadata.push_back("return");
    92   return result;
    93 }