about summary refs log tree commit diff stats
path: root/059stop.subx
blob: f6cb6fae114e0e820e5800349a28fdfbd77a423b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# stop: dependency-injected wrapper around the exit() syscall
#
# We'd like to be able to write tests for functions that call exit(), and to
# make assertions about whether they exit() or not in a given situation. To
# achieve this we'll call exit() via a smarter wrapper called 'stop'.
#
# In the context of a test, calling a function X that calls 'stop' (directly
# or through further intervening calls) will unwind the stack until X returns,
# so that we can say check any further assertions after the execution of X. To
# achieve this end, we'll pass the return address of X as a 'target' argument
# into X, plumbing it through to 'stop'. When 'stop' gets a non-null target it
# unwinds the stack until the target. If it gets a null target it calls
# exit().
#
# We'd also like to get the exit status out of 'stop', so we'll combine the
# input target with an output status parameter into a type called 'exit-descriptor'.
#
# So the exit-descriptor looks like this:
#   target: address  # return address for 'stop' to unwind to
#   value: int  # exit status stop was called with
#
# 'stop' thus takes two parameters: an exit-descriptor and the exit status.
#
# 'stop' won't bother cleaning up any other processor state besides the stack,
# such as registers. Only esp will have a well-defined value after 'stop'
# returns. (This is a poor man's setjmp/longjmp, if you know what that is.)
#
# Before you can call any function that may call 'stop', you need to pass in an
# exit-descriptor to it. To create an exit-descriptor use 'tailor-exit-descriptor'
# below. It's not the most pleasant abstraction in the world.
#
# An exit-descriptor's target is its input, computed during 'tailor-exit-descriptor'.
# Its value is its output, computed during stop and available to the test.

== code
#   instruction                     effective address                                                   register    displacement    immediate
# . op          subop               mod             rm32          base        index         scale       r32
# . 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

# Configure an exit-descriptor for a call pushing 'nbytes' bytes of args to
# the stack.
# Ugly that we need to know the size of args. Don't allocate variables between
# tailor-exit-descriptor and the call it's for.
tailor-exit-descriptor:  # ed: (addr exit-descriptor), nbytes: int
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . save registers
    50/push-eax
    51/push-ecx
    # eax = nbytes
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           0/r32/eax   0xc/disp8       .                 # copy *(ebp+12) to eax
    # Let X be the value of esp in the caller, before the call to tailor-exit-descriptor.
    # The return address for a call in the caller's body will be at:
    #   X-8 if the caller takes 4 bytes of args for the exit-descriptor (add 4 bytes for the return address)
    #   X-12 if the caller takes 8 bytes of args
    #   ..and so on
    # That's the value we need to return: X-nbytes-4
    #
    # However, we also need to account for the perturbance to esp caused by the
    # call to tailor-exit-descriptor. It pushes 8 bytes of args followed by 4
    # bytes for the return address and 4 bytes to push ebp above.
    # So ebp at this point is X-16.
    #
    # So the return address for the next call in the caller is:
    #   ebp+8 if the caller takes 4 bytes of args
    #   ebp+4 if the caller takes 8 bytes of args
    #   ebp if the caller takes 12 bytes of args
    #   ebp-4 if the caller takes 16 bytes of args
    #   ..and so on
    # That's ebp+12-nbytes.
    # option 1: 6 + 3 bytes
#?     2d/subtract                     3/mod/direct    0/rm32/eax    .           .             .           .           .               8/imm32           # subtract from eax
#?     8d/copy-address                 0/mod/indirect  4/rm32/sib    5/base/ebp  0/index/eax   .           0/r32/eax   .               .                 # copy ebp+eax to eax
    # option 2: 2 + 4 bytes
    f7          3/subop/negate      3/mod/direct    0/rm32/eax    .           .             .           .           .               .                 # negate eax
    8d/copy-address                 1/mod/*+disp8   4/rm32/sib    5/base/ebp  0/index/eax   .           0/r32/eax   0xc/disp8         .               # copy ebp+eax+12 to eax
    # copy eax to ed->target
    8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .             .           1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
    89/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # copy eax to *ecx
    # initialize ed->value
    c7          0/subop/copy        1/mod/*+disp8   1/rm32/ecx    .           .             .           .           4/disp8         0/imm32           # copy to *(ecx+4)
$tailor-exit-descriptor:end:
    # . restore registers
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

stop:  # ed: (addr exit-descriptor), value: int
    # no prologue; one way or another, we're going to clobber registers
    # eax = ed
    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/esp  4/index/none  .           0/r32/eax   4/disp8         .                 # copy *(esp+4) to eax
    # if (ed == 0) really exit
    3d/compare-eax-and 0/imm32
    74/jump-if-=  $stop:real/disp8
    # if (ed->target == 0) really exit
    81          7/subop/compare     0/mod/indirect  0/rm32/eax    .           .             .           .           .               0/imm32           # compare *eax
    74/jump-if-=  $stop:real/disp8
$stop:fake:
    # ed->value = value+1
    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/esp  4/index/none  .           1/r32/ecx   8/disp8         .                 # copy *(esp+8) to ecx
    41/increment-ecx
    89/copy                         1/mod/*+disp8   0/rm32/eax    .           .             .           1/r32/ecx   4/disp8         .                 # copy ecx to *(eax+4)
    # perform a non-local jump to ed->target
    8b/copy                         0/mod/indirect  0/rm32/eax    .           .             .           4/r32/esp   .               .                 # copy *eax to esp
$stop:end1:
    # never gets here
    c3/return  # doesn't return to caller
$stop:real:
    # . syscall(exit, value)
    8b/copy                         1/mod/*+disp8   4/rm32/sib    4/base/esp  4/index/none  .           3/r32/ebx   8/disp8         .                 # copy *(esp+8) to ebx
    e8/call  syscall_exit/disp32
$stop:end2:
    # never gets here
    c3/return  # doesn't return to caller

test-stop-skips-returns-on-exit:
    # This looks like the standard prologue, but is here for different reasons.
    # A function calling 'stop' can't rely on ebp persisting past the call.
    #
    # Use ebp here as a stable base to refer to locals and arguments from in the
    # presence of push/pop/call instructions.
    # *Don't* use ebp as a way to restore esp.
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # Make room for an exit descriptor on the stack. That's almost always the
    # right place for it, available only as long as it's legal to use. Once this
    # containing function returns we'll need a new exit descriptor.
    # var ed/eax: exit-descriptor
    68/push  0/imm32
    68/push  0/imm32
    89/copy                         3/mod/direct    0/rm32/eax    .           .             .           4/r32/esp   .               .                 # copy esp to eax
    # Size the exit-descriptor precisely for the next call below, to _test-stop-1.
    # tailor-exit-descriptor(ed, 4)
    # . . push args
    68/push  4/imm32/nbytes-of-args-for-_test-stop-1
    50/push-eax
    # . . call
    e8/call  tailor-exit-descriptor/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    # . _test-stop-1(ed)
    # . . push args
    50/push-eax
    # . . call
    e8/call  _test-stop-1/disp32
    # registers except esp may be clobbered at this point
    # restore args
    58/pop-to-eax
    # check that _test-stop-1 tried to call exit(1)
    # . check-ints-equal(ed->value, 2, msg)  # i.e. stop was called with value 1
    # . . push args
    68/push  "F - test-stop-skips-returns-on-exit"/imm32
    68/push  2/imm32
    # . . push ed->value
    ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           4/disp8         .                 # push *(eax+4)
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    # don't restore esp from ebp; manually reclaim locals
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    5d/pop-to-ebp
    c3/return

_test-stop-1:  # ed: (addr exit-descriptor)
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # _test-stop-2(ed)
    # . . push args
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  _test-stop-2/disp32
    # should never get past this point
$_test-stop-1:dead-end:
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    # signal test failed: check-ints-equal(1, 0, msg)
    # . . push args
    68/push  "F - test-stop-skips-returns-on-exit"/imm32
    68/push  0/imm32
    68/push  1/imm32
    # . . call
    e8/call  check-ints-equal/disp32
    # . . discard args
    81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

_test-stop-2:  # ed: (addr exit-descriptor)
    # . prologue
    55/push-ebp
    89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
    # . stop(ed, 1)
    # . . push args
    68/push  1/imm32
    ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
    # . . call
    e8/call  stop/disp32
    # should never get past this point
$_test-stop-2:dead-end:
    # . epilogue
    89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
    5d/pop-to-ebp
    c3/return

# . . vim:nowrap:textwidth=0
//: CHECK_TRACE_CONTENTS("label", "fact 1: 34\n" //: "fact 2: 35\n"); //: //: Since we never call anything but the run() function directly, we never have //: to rewrite the tests when we reorganize the internals of the program. We //: just have to make sure our rewrite deduces the same facts about the domain, //: and that's something we're going to have to do anyway. //: //: To avoid the combinatorial explosion of integration tests, each layer //: mainly logs facts to the trace with a common *label*. All tests in a layer //: tend to check facts with this label. Validating the facts logged with a //: specific label is like calling functions of that layer directly. //: //: To build robust tests, trace facts about your domain rather than details of //: how you computed them. //: //: More details: http://akkartik.name/blog/tracing-tests //: //: --- //: //: Between layers and domain-driven testing, programming starts to look like a //: fundamentally different activity. Instead of focusing on a) superficial, //: b) local rules on c) code [like say http://blog.bbv.ch/2013/06/05/clean-code-cheat-sheet], //: we allow programmers to engage with the a) deep, b) global structure of //: the c) domain. If you can systematically track discontinuities in the //: domain, you don't care if the code used gotos as long as it passed all //: tests. If tests become more robust to run, it becomes easier to try out //: radically different implementations for the same program. If code is //: super-easy to rewrite, it becomes less important what indentation style it //: uses, or that the objects are appropriately encapsulated, or that the //: functions are referentially transparent. //: //: Instead of plumbing, programming becomes building and gradually refining a //: map of the environment the program must operate under. Whether a program //: is 'correct' at a given point in time is a red herring; what matters is //: avoiding regression by monotonically nailing down the more 'eventful' //: parts of the terrain. It helps readers new and old, and rewards curiosity, //: to organize large programs in self-similar hierarchies of example tests //: colocated with the code that makes them work. //: //: "Programming properly should be regarded as an activity by which //: programmers form a mental model, rather than as production of a program." //: -- Peter Naur (http://akkartik.name/naur.pdf) //:: == Core data structures :(before "End Globals") trace_stream* Trace_stream = NULL; :(before "End Types") struct trace_stream { vector<trace_line> past_lines; // End trace_stream Fields trace_stream() { // End trace_stream Constructor } ~trace_stream() { // End trace_stream Destructor } // End trace_stream Methods }; //:: == Adding to the trace //: Top-level method is trace() which can be used like an ostream. Usage: //: trace(depth, label) << ... << end(); //: Don't forget the 'end()' to actually append to the trace. :(before "End Includes") // No brackets around the expansion so that it prints nothing if Trace_stream // isn't initialized. #define trace(...) !Trace_stream ? cerr : Trace_stream->stream(__VA_ARGS__) :(before "End trace_stream Fields") // accumulator for current trace_line ostringstream* curr_stream; string curr_label; int curr_depth; // other stuff int collect_depth; // avoid tracing lower levels for speed ofstream null_stream; // never opened, so writes to it silently fail //: Some constants. :(before "struct trace_stream") // include constants in all cleaved compilation units const int Max_depth = 9999; :(before "End trace_stream Constructor") curr_stream = NULL; curr_depth = Max_depth; collect_depth = Max_depth; :(before "struct trace_stream") struct trace_line { string contents; string label; int depth; // 0 is 'sea level'; positive integers are progressively 'deeper' and lower level trace_line(string c, string l) { contents = c; label = l; depth = 0; } trace_line(string c, string l, int d) { contents = c; label = l; depth = d; } }; string unescape_newline(string& s) { std::stringstream ss; for (int i = 0; i < SIZE(s); ++i) { if (s.at(i) == '\n') ss << "\\n"; else ss << s.at(i); } return ss.str(); } void dump_trace_line(ostream& s, trace_line& t) { s << std::setw(2) << t.depth << ' ' << t.label << ": " << unescape_newline(t.contents) << '\n'; } //: Starting a new trace line. :(before "End trace_stream Methods") ostream& stream(string label) { return stream(Max_depth, label); } ostream& stream(int depth, string label) { if (depth > collect_depth) return null_stream; curr_stream = new ostringstream; curr_label = label; curr_depth = depth; (*curr_stream) << std::hex; // printing addresses is the common case return *curr_stream; } //: End of a trace line; append it to the trace. :(before "End Types") struct end {}; :(code) ostream& operator<<(ostream& os, end /*unused*/) { if (Trace_stream) Trace_stream->newline(); return os; } //: Fatal error. :(before "End Types") struct die {}; :(code) ostream& operator<<(ostream& /*unused*/, die /*unused*/) { if (Trace_stream) Trace_stream->newline(); exit(1); } :(before "End trace_stream Methods") void newline(); :(code) void trace_stream::newline() { if (!curr_stream) return; string curr_contents = curr_stream->str(); if (!curr_contents.empty()) { past_lines.push_back(trace_line(curr_contents, trim(curr_label), curr_depth)); // preserve indent in contents // maybe incrementally dump trace trace_line& t = past_lines.back(); if (should_incrementally_print_trace()) { dump_trace_line(cerr, t); } // Hack: on 'bootstrap --trace --dump', emit only to stderr, not 'last_run'. if (Dump_trace) past_lines.pop_back(); // economize on memory // End trace Commit } // clean up delete curr_stream; curr_stream = NULL; curr_label.clear(); curr_depth = Max_depth; } //:: == Initializing the trace in tests :(before "End Includes") #define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer; :(before "End Test Setup") START_TRACING_UNTIL_END_OF_SCOPE //: Trace_stream is a resource, lease_tracer uses RAII to manage it. :(before "End Types") struct lease_tracer { lease_tracer(); ~lease_tracer(); }; :(code) lease_tracer::lease_tracer() { Trace_stream = new trace_stream; } lease_tracer::~lease_tracer() { delete Trace_stream; Trace_stream = NULL; } //:: == Errors and warnings using traces :(before "End Includes") #define raise (!Trace_stream ? (++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error")) #define warn (!Trace_stream ? (++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Warn_depth, "warn")) //: Print errors and warnings to the screen by default. :(before "struct trace_stream") // include constants in all cleaved compilation units const int Error_depth = 0; const int Warn_depth = 1; :(before "End Globals") int Hide_errors = false; // if set, don't print errors or warnings to screen int Hide_warnings = false; // if set, don't print warnings to screen :(before "End Reset") Hide_errors = false; Hide_warnings = false; //: Never dump warnings in tests :(before "End Test Setup") Hide_warnings = true; :(code) bool trace_stream::should_incrementally_print_trace() { if (!Hide_errors && curr_depth == Error_depth) return true; if (!Hide_warnings && !Hide_errors && curr_depth == Warn_depth) return true; // End Incremental Trace Print Conditions return false; } :(before "End trace_stream Methods") bool should_incrementally_print_trace(); :(before "End Globals") int Trace_errors = 0; // used only when Trace_stream is NULL // Fail tests that displayed (unexpected) errors. // Expected errors should always be hidden and silently checked for. :(before "End Test Teardown") if (Passed && !Hide_errors && trace_contains_errors()) { Passed = false; } :(code) bool trace_contains_errors() { return Trace_errors > 0 || trace_count("error") > 0; } :(before "End Includes") // If we aren't yet sure how to deal with some corner case, use assert_for_now // to indicate that it isn't an inviolable invariant. #define assert_for_now assert #define raise_for_now raise //:: == Other assertions on traces //: Primitives: //: - CHECK_TRACE_CONTENTS(lines) //: Assert that the trace contains the given lines (separated by newlines) //: in order. There can be other intervening lines between them. //: - CHECK_TRACE_DOESNT_CONTAIN(line) //: - CHECK_TRACE_DOESNT_CONTAIN(label, contents) //: Assert that the trace doesn't contain the given (single) line. //: - CHECK_TRACE_COUNT(label, count) //: Assert that the trace contains exactly 'count' lines with the given //: 'label'. //: - CHECK_TRACE_CONTAINS_ERRORS() //: - CHECK_TRACE_DOESNT_CONTAIN_ERRORS() //: - trace_count_prefix(label, prefix) //: Count the number of trace lines with the given 'label' that start with //: the given 'prefix'. :(before "End Includes") #define CHECK_TRACE_CONTENTS(...) check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) #define CHECK_TRACE_DOESNT_CONTAIN(...) CHECK(trace_doesnt_contain(__VA_ARGS__)) #define CHECK_TRACE_COUNT(label, count) \ if (Passed && trace_count(label) != (count)) { \ cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): trace_count of " << label << " should be " << count << '\n'; \ cerr << " got " << trace_count(label) << '\n'; /* multiple eval */ \ DUMP(label); \ Passed = false; \ return; /* Currently we stop at the very first failure. */ \ } #define CHECK_TRACE_CONTAINS_ERRORS() CHECK(trace_contains_errors()) #define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \ if (Passed && trace_contains_errors()) { \ cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \ DUMP("error"); \ Passed = false; \ return; \ } // Allow tests to ignore trace lines generated during setup. #define CLEAR_TRACE delete Trace_stream, Trace_stream = new trace_stream :(code) bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) { if (!Passed) return false; if (!Trace_stream) return false; vector<string> expected_lines = split(expected, "\n"); int curr_expected_line = 0; while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) ++curr_expected_line; if (curr_expected_line == SIZE(expected_lines)) return true; string label, contents; split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { if (label != p->label) continue; string t = trim(p->contents); if (contents != unescape_newline(t)) continue; ++curr_expected_line; while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) ++curr_expected_line; if (curr_expected_line == SIZE(expected_lines)) return true; split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); } if (line_exists_anywhere(label, contents)) { cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): line [" << label << ": " << contents << "] out of order in trace:\n"; DUMP(""); } else { cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n"; DUMP(label); } Passed = false; return false; } bool trace_doesnt_contain(string expected) { vector<string> tmp = split_first(expected, ": "); if (SIZE(tmp) == 1) { raise << expected << ": missing label or contents in trace line\n" << end(); assert(false); } return trace_count(tmp.at(0), tmp.at(1)) == 0; } int trace_count(string label) { return trace_count(label, ""); } int trace_count(string label, string line) { if (!Trace_stream) return 0; long result = 0; for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { if (label == p->label) { if (line == "" || trim(line) == trim(p->contents)) ++result; } } return result; } int trace_count_prefix(string label, string prefix) { if (!Trace_stream) return 0; long result = 0; for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { if (label == p->label) { if (starts_with(trim(p->contents), trim(prefix))) ++result; } } return result; } void split_label_contents(const string& s, string* label, string* contents) { static const string delim(": "); size_t pos = s.find(delim); if (pos == string::npos) { *label = ""; *contents = trim(s); } else { *label = trim(s.substr(0, pos)); *contents = trim(s.substr(pos+SIZE(delim))); } } bool line_exists_anywhere(const string& label, const string& contents) { for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { if (label != p->label) continue; if (contents == trim(p->contents)) return true; } return false; } vector<string> split(string s, string delim) { vector<string> result; size_t begin=0, end=s.find(delim); while (true) { if (end == string::npos) { result.push_back(string(s, begin, string::npos)); break; } result.push_back(string(s, begin, end-begin)); begin = end+SIZE(delim); end = s.find(delim, begin); } return result; } vector<string> split_first(string s, string delim) { vector<string> result; size_t end=s.find(delim); result.push_back(string(s, 0, end)); if (end != string::npos) result.push_back(string(s, end+SIZE(delim), string::npos)); return result; } //:: == Helpers for debugging using traces :(before "End Includes") // To debug why a test is failing, dump its trace using '?'. #define DUMP(label) if (Trace_stream) cerr << Trace_stream->readable_contents(label); // To add temporary prints to the trace, use 'dbg'. // `git log` should never show any calls to 'dbg'. #define dbg trace(0, "a") //: Dump the entire trace to file where it can be browsed offline. //: Dump the trace as it happens; that way you get something even if the //: program crashes. :(before "End Globals") ofstream Trace_file; :(before "End Commandline Options(*arg)") else if (is_equal(*arg, "--trace")) { cerr << "saving trace to 'last_run'\n"; Trace_file.open("last_run"); // Add a dummy line up top; otherwise the `browse_trace` tool currently has // no way to expand any lines above an error. Trace_file << " 0 dummy: start\n"; // End --trace Settings } :(before "End trace Commit") if (Trace_file) { dump_trace_line(Trace_file, t); } :(before "End One-time Setup") atexit(cleanup_main); :(code) void cleanup_main() { if (Trace_file) Trace_file.close(); // End cleanup_main } :(before "End trace_stream Methods") string readable_contents(string label) { string trim(const string& s); // prototype ostringstream output; label = trim(label); for (vector<trace_line>::iterator p = past_lines.begin(); p != past_lines.end(); ++p) if (label.empty() || label == p->label) dump_trace_line(output, *p); return output.str(); } //: Print traces to the screen as they happen. //: Particularly useful when juggling multiple trace streams, like when //: debugging sandboxes. :(before "End Globals") bool Dump_trace = false; :(before "End Commandline Options(*arg)") else if (is_equal(*arg, "--dump")) { Dump_trace = true; } :(before "End Incremental Trace Print Conditions") if (Dump_trace) return true; //: Miscellaneous helpers. :(code) string trim(const string& s) { string::const_iterator first = s.begin(); while (first != s.end() && isspace(*first)) ++first; if (first == s.end()) return ""; string::const_iterator last = --s.end(); while (last != s.begin() && isspace(*last)) --last; ++last; return string(first, last); } :(before "End Includes") #include <vector> using std::vector; #include <list> using std::list; #include <set> using std::set; #include <sstream> using std::istringstream; using std::ostringstream; #include <fstream> using std::ifstream; using std::ofstream;