https://github.com/akkartik/mu/blob/main/linux/bootstrap/040tests.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 Transform.push_back(create_test_function);
    14
    15 :(code)
    16 void test_run_test() {
    17   Mem.push_back(vma(0xbd000000));  // manually allocate memory
    18   Reg[ESP].u = 0xbd000100;
    19   run(
    20       "== code 0x1\n"  // code segment
    21       "main:\n"
    22       "  e8/call run-tests/disp32\n"  // 5 bytes
    23       "  f4/halt\n"                   // 1 byte
    24       "test-foo:\n"  // offset 7
    25       "  01 d8\n"  // just some unique instruction: add EBX to EAX
    26       "  c3/return\n"
    27   );
    28   // check that code in test-foo ran (implicitly called by run-tests)
    29   CHECK_TRACE_CONTENTS(
    30       "run: 0x00000007 opcode: 01\n"
    31   );
    32 }
    33
    34 void create_test_function(program& p) {
    35   if (p.segments.empty()) return;
    36   segment& code = *find(p, "code");
    37   trace(3, "transform") << "-- create 'run-tests'" << end();
    38   vector<line> new_insts;
    39   for (int i = 0;  i < SIZE(code.lines);  ++i) {
    40     line& inst = code.lines.at(i);
    41     for (int j = 0;  j < SIZE(inst.words);  ++j) {
    42       const word& curr = inst.words.at(j);
    43       if (*curr.data.rbegin() != ':') continue;  // not a label
    44       if (!starts_with(curr.data, "test-")) continue;
    45       string fn = drop_last(curr.data);
    46       new_insts.push_back(call(fn));
    47     }
    48   }
    49   if (new_insts.empty()) return;  // no tests found
    50   code.lines.push_back(label("run-tests"));
    51   code.lines.insert(code.lines.end(), new_insts.begin(), new_insts.end());
    52   code.lines.push_back(ret());
    53 }
    54
    55 string to_string(const segment& s) {
    56   ostringstream out;
    57   for (int i = 0;  i < SIZE(s.lines);  ++i) {
    58     const line& l = s.lines.at(i);
    59     for (int j = 0;  j < SIZE(l.words);  ++j) {
    60       if (j > 0) out << ' ';
    61       out << to_string(l.words.at(j));
    62     }
    63     out << '\n';
    64   }
    65   return out.str();
    66 }
    67
    68 line call(string s) {
    69   line result;
    70   result.words.push_back(call());
    71   result.words.push_back(disp32(s));
    72   return result;
    73 }
    74
    75 word call() {
    76   word result;
    77   result.data = "e8";
    78   result.metadata.push_back("call");
    79   return result;
    80 }
    81
    82 word disp32(string s) {
    83   word result;
    84   result.data = s;
    85   result.metadata.push_back("disp32");
    86   return result;
    87 }
    88
    89 line ret() {
    90   line result;
    91   result.words.push_back(word());
    92   result.words.back().data = "c3";
    93   result.words.back().metadata.push_back("return");
    94   return result;
    95 }