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