1 //: The goal of layers is to make programs more easy to understand and more
  2 //: malleable, easy to rewrite in radical ways without accidentally breaking
  3 //: some corner case. Tests further both goals. They help understandability by
  4 //: letting one make small changes and get feedback. What if I wrote this line
  5 //: like so? What if I removed this function call, is it really necessary?
  6 //: Just try it, see if the tests pass. Want to explore rewriting this bit in
  7 //: this way? Tests put many refactorings on a firmer footing.
  8 //:
  9 //: But the usual way we write tests seems incomplete. Refactorings tend to
 10 //: work in the small, but don't help with changes to function boundaries. If
 11 //: you want to extract a new function you have to manually test-drive it to
 12 //: create tests for it. If you want to inline a function its tests are no
 13 //: longer valid. In both cases you end up having to reorganize code as well as
 14 //: tests, an error-prone activity.
 15 //:
 16 //: In response, this layer introduces the notion of *domain-driven* testing.
 17 //: We focus on the domain of inputs the whole program needs to handle rather
 18 //: than the correctness of individual functions. All tests invoke the program
 19 //: in a single way: by calling run() with some input. As the program operates
 20 //: on the input, it traces out a list of _facts_ deduced about the domain:
 21 //:   trace("label") << "fact 1: " << val;
 22 //:
 23 //
# dwm - dynamic window manager
# © 2006-2007 Anselm R. Garbe, Sander van Dijk

include config.mk

SRC = dwm.c
OBJ = ${SRC:.c=.o}

all: options dwm

options:
	@echo dwm build options:
	@echo "CFLAGS   = ${CFLAGS}"
	@echo "LDFLAGS  = ${LDFLAGS}"
	@echo "CC       = ${CC}"

.c.o:
	@echo CC $<
	@${CC} -c ${CFLAGS} $<

${OBJ}: config.h config.mk

config.h:
	@echo creating $@ from config.def.h
	@cp config.def.h $@

dwm: ${OBJ}
	@echo CC -o $@
	@${CC} -o $@ ${OBJ} ${LDFLAGS}

clean:
	@echo cleaning
	@rm -f dwm ${OBJ} dwm-${VERSION}.tar.gz

dist: clean
	@echo creating dist tarball
	@mkdir -p dwm-${VERSION}
	@cp -R LICENSE Makefile README config.def.h config.mk \
		dwm.1 ${SRC} dwm-${VERSION}
	@tar -cf dwm-${VERSION}.tar dwm-${VERSION}
	@gzip dwm-${VERSION}.tar
	@rm -rf dwm-${VERSION}

install: all
	@echo installing executable file to ${DESTDIR}${PREFIX}/bin
	@mkdir -p ${DESTDIR}${PREFIX}/bin
	@cp -f dwm ${DESTDIR}${PREFIX}/bin
	@chmod 755 ${DESTDIR}${PREFIX}/bin/dwm
	@echo installing manual page to ${DESTDIR}${MANPREFIX}/man1
	@mkdir -p ${DESTDIR}${MANPREFIX}/man1
	@sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1
	@chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1

uninstall:
	@echo removing executable file from ${DESTDIR}${PREFIX}/bin
	@rm -f ${DESTDIR}${PREFIX}/bin/dwm
	@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
	@rm -f ${DESTDIR}${MANPREFIX}/man1/dwm.1

.PHONY: all options clean dist install uninstall
r">; // never opens a file, so writes silently fail 127 trace_stream() :curr_stream(NULL), curr_depth(Max_depth), callstack_depth(0), collect_depth(Max_depth) {} 128 ~trace_stream() { if (curr_stream) delete curr_stream; } 129 130 ostream& stream(string label) { 131 return stream(Max_depth, label); 132 } 133 134 ostream& stream(int depth, string label) { 135 if (depth > collect_depth) return null_stream; 136 curr_stream = new ostringstream; 137 curr_label = label; 138 curr_depth = depth; 139 return *curr_stream; 140 } 141 142 void save() { 143 cerr << "saving trace to 'last_run'\n"; 144 ofstream fout("last_run"); 145 fout << readable_contents(""); 146 fout.close(); 147 } 148 149 // be sure to call this before messing with curr_stream or curr_label 150 void newline(); 151 // useful for debugging 152 string readable_contents(string label); // empty label = show everything 153 }; 154 155 :(code) 156 void trace_stream::newline() { 157 if (!curr_stream) return; 158 string curr_contents = curr_stream->str(); 159 if (!curr_contents.empty()) { 160 past_lines.push_back(trace_line(curr_depth, trim(curr_label), curr_contents)); // preserve indent in contents 161 if ((!Hide_errors && curr_label == "error") 162 || Dump_trace 163 || (!Dump_label.empty() && curr_label == Dump_label)) 164 cerr << curr_label << ": " << curr_contents << '\n'; 165 } 166 delete curr_stream; 167 curr_stream = NULL; 168 curr_label.clear(); 169 curr_depth = Max_depth; 170 } 171 172 string trace_stream::readable_contents(string label) { 173 ostringstream output; 174 label = trim(label); 175 for (vector<trace_line>::iterator p = past_lines.begin(); p != past_lines.end(); ++p) 176 if (label.empty() || label == p->label) { 177 output << std::setw(4) << p->depth << ' ' << p->label << ": " << p->contents << '\n'; 178 } 179 return output.str(); 180 } 181 182 :(before "End Globals") 183 trace_stream* Trace_stream = NULL; 184 int Trace_errors = 0; // used only when Trace_stream is NULL 185 186 :(before "End Includes") 187 #define CLEAR_TRACE delete Trace_stream, Trace_stream = new trace_stream; 188 189 // Top-level helper. IMPORTANT: can't nest 190 #define trace(...) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(__VA_ARGS__) 191 192 // Just for debugging; 'git log' should never show any calls to 'dbg'. 193 #define dbg trace(0, "a") 194 #define DUMP(label) if (Trace_stream) cerr << Trace_stream->readable_contents(label); 195 196 // Errors are a special layer. 197 #define raise (!Trace_stream ? (scroll_to_bottom_and_close_console(),++Trace_errors,cerr) /*do print*/ : Trace_stream->stream(Error_depth, "error")) 198 // If we aren't yet sure how to deal with some corner case, use assert_for_now 199 // to indicate that it isn't an inviolable invariant. 200 #define assert_for_now assert 201 202 //: Automatically close the console in some situations. 203 :(before "End One-time Setup") 204 atexit(scroll_to_bottom_and_close_console); 205 :(code) 206 void scroll_to_bottom_and_close_console() { 207 if (!tb_is_active()) return; 208 // leave the screen in a relatively clean state 209 tb_set_cursor(tb_width()-1, tb_height()-1); 210 cout << "\r\n"; 211 tb_shutdown(); 212 } 213 214 // Inside tests, fail any tests that displayed (unexpected) errors. 215 // Expected errors in tests should always be hidden and silently checked for. 216 :(before "End Test Teardown") 217 if (Passed && !Hide_errors && trace_contains_errors()) { 218 Passed = false; 219 } 220 :(code) 221 bool trace_contains_errors() { 222 return Trace_errors > 0 || trace_count("error") > 0; 223 } 224 225 :(before "End Types") 226 struct end {}; 227 :(code) 228 ostream& operator<<(ostream& os, end /*unused*/) { 229 if (Trace_stream) Trace_stream->newline(); 230 return os; 231 } 232 233 :(before "End Globals") 234 bool Save_trace = false; // if set, write out trace to disk 235 236 // Trace_stream is a resource, lease_tracer uses RAII to manage it. 237 :(before "End Types") 238 struct lease_tracer { 239 lease_tracer(); 240 ~lease_tracer(); 241 }; 242 :(code) 243 lease_tracer::lease_tracer() { Trace_stream = new trace_stream; } 244 lease_tracer::~lease_tracer() { 245 if (Save_trace) Trace_stream->save(); 246 delete Trace_stream, Trace_stream = NULL; 247 } 248 :(before "End Includes") 249 #define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer; 250 :(before "End Test Setup") 251 START_TRACING_UNTIL_END_OF_SCOPE 252 253 :(before "End Includes") 254 #define CHECK_TRACE_CONTENTS(...) check_trace_contents(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) 255 256 #define CHECK_TRACE_CONTAINS_ERRORS() CHECK(trace_contains_errors()) 257 #define CHECK_TRACE_DOESNT_CONTAIN_ERRORS() \ 258 if (Passed && trace_contains_errors()) { \ 259 cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): unexpected errors\n"; \ 260 DUMP("error"); \ 261 Passed = false; \ 262 return; \ 263 } 264 265 #define CHECK_TRACE_COUNT(label, count) \ 266 if (Passed && trace_count(label) != (count)) { \ 267 cerr << "\nF - " << __FUNCTION__ << "(" << __FILE__ << ":" << __LINE__ << "): trace_count of " << label << " should be " << count << '\n'; \ 268 cerr << " got " << trace_count(label) << '\n'; /* multiple eval */ \ 269 DUMP(label); \ 270 Passed = false; \ 271 return; /* Currently we stop at the very first failure. */ \ 272 } 273 274 #define CHECK_TRACE_DOESNT_CONTAIN(...) CHECK(trace_doesnt_contain(__VA_ARGS__)) 275 276 :(code) 277 bool check_trace_contents(string FUNCTION, string FILE, int LINE, string expected) { 278 if (!Passed) return false; 279 if (!Trace_stream) return false; 280 vector<string> expected_lines = split(expected, "^D"); 281 int curr_expected_line = 0; 282 while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) 283 ++curr_expected_line; 284 if (curr_expected_line == SIZE(expected_lines)) return true; 285 string label, contents; 286 split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); 287 for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { 288 if (label != p->label) continue; 289 if (contents != trim(p->contents)) continue; 290 ++curr_expected_line; 291 while (curr_expected_line < SIZE(expected_lines) && expected_lines.at(curr_expected_line).empty()) 292 ++curr_expected_line; 293 if (curr_expected_line == SIZE(expected_lines)) return true; 294 split_label_contents(expected_lines.at(curr_expected_line), &label, &contents); 295 } 296 297 if (line_exists_anywhere(label, contents)) { 298 cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): line [" << label << ": " << contents << "] out of order in trace:\n"; 299 DUMP(""); 300 } 301 else { 302 cerr << "\nF - " << FUNCTION << "(" << FILE << ":" << LINE << "): missing [" << contents << "] in trace:\n"; 303 DUMP(label); 304 } 305 Passed = false; 306 return false; 307 } 308 309 void split_label_contents(const string& s, string* label, string* contents) { 310 static const string delim(": "); 311 size_t pos = s.find(delim); 312 if (pos == string::npos) { 313 *label = ""; 314 *contents = trim(s); 315 } 316 else { 317 *label = trim(s.substr(0, pos)); 318 *contents = trim(s.substr(pos+SIZE(delim))); 319 } 320 } 321 322 bool line_exists_anywhere(const string& label, const string& contents) { 323 for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { 324 if (label != p->label) continue; 325 if (contents == trim(p->contents)) return true; 326 } 327 return false; 328 } 329 330 int trace_count(string label) { 331 return trace_count(label, ""); 332 } 333 334 int trace_count(string label, string line) { 335 if (!Trace_stream) return 0; 336 long result = 0; 337 for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { 338 if (label == p->label) { 339 if (line == "" || trim(line) == trim(p->contents)) 340 ++result; 341 } 342 } 343 return result; 344 } 345 346 int trace_count_prefix(string label, string prefix) { 347 if (!Trace_stream) return 0; 348 long result = 0; 349 for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { 350 if (label == p->label) { 351 if (starts_with(trim(p->contents), trim(prefix))) 352 ++result; 353 } 354 } 355 return result; 356 } 357 358 bool trace_doesnt_contain(string label, string line) { 359 return trace_count(label, line) == 0; 360 } 361 362 bool trace_doesnt_contain(string expected) { 363 vector<string> tmp = split_first(expected, ": "); 364 return trace_doesnt_contain(tmp.at(0), tmp.at(1)); 365 } 366 367 vector<string> split(string s, string delim) { 368 vector<string> result; 369 size_t begin=0, end=s.find(delim); 370 while (true) { 371 if (end == string::npos) { 372 result.push_back(string(s, begin, string::npos)); 373 break; 374 } 375 result.push_back(string(s, begin, end-begin)); 376 begin = end+SIZE(delim); 377 end = s.find(delim, begin); 378 } 379 return result; 380 } 381 382 vector<string> split_first(string s, string delim) { 383 vector<string> result; 384 size_t end=s.find(delim); 385 result.push_back(string(s, 0, end)); 386 if (end != string::npos) 387 result.push_back(string(s, end+SIZE(delim), string::npos)); 388 return result; 389 } 390 391 string trim(const string& s) { 392 string::const_iterator first = s.begin(); 393 while (first != s.end() && isspace(*first)) 394 ++first; 395 if (first == s.end()) return ""; 396 397 string::const_iterator last = --s.end(); 398 while (last != s.begin() && isspace(*last)) 399 --last; 400 ++last; 401 return string(first, last); 402 } 403 404 :(before "End Includes") 405 #include <vector> 406 using std::vector; 407 #include <list> 408 using std::list; 409 #include <set> 410 using std::set; 411 412 #include <sstream> 413 using std::istringstream; 414 using std::ostringstream; 415 416 #include <fstream> 417 using std::ifstream; 418 using std::ofstream; 419 420 #include "termbox/termbox.h" 421 422 :(before "End Globals") 423 //: In future layers we'll use the depth field as follows: 424 //: 425 //: Errors will be depth 0. 426 //: Mu 'applications' will be able to use depths 1-100 as they like. 427 //: Primitive statements will occupy 101-9989 428 extern const int Initial_callstack_depth = 101; 429 extern const int Max_callstack_depth = 9989; 430 //: Finally, details of primitive Mu statements will occupy depth 9990-9999 431 //: (more on that later as well) 432 //: 433 //: This framework should help us hide some details at each level, mixing 434 //: static ideas like layers with the dynamic notion of call-stack depth.