https://github.com/akkartik/mu/blob/master/050scenario.cc
  1 //: Mu scenarios. This will get long, but these are the tests we want to
  2 //: support in this layer.
  3 
  4 //: We avoid raw numeric locations in Mu -- except in scenarios, where they're
  5 //: handy to check the values of specific variables
  6 :(scenarios run_mu_scenario)
  7 :(scenario scenario_block)
  8 scenario foo [
  9   run [
 10     1:num <- copy 13
 11   ]
 12   memory-should-contain [
 13     1 <- 13
 14   ]
 15 ]
 16 # checks are inside scenario
 17 
 18 :(scenario scenario_multiple_blocks)
 19 scenario foo [
 20   run [
 21     1:num <- copy 13
 22   ]
 23   memory-should-contain [
 24     1 <- 13
 25   ]
 26   run [
 27     2:num <- copy 13
 28   ]
 29   memory-should-contain [
 30     1 <- 13
 31     2 <- 13
 32   ]
 33 ]
 34 # checks are inside scenario
 35 
 36 :(scenario scenario_check_memory_and_trace)
 37 scenario foo [
 38   run [
 39     1:num <- copy 13
 40     trace 1, [a], [a b c]
 41   ]
 42   memory-should-contain [
 43     1 <- 13
 44   ]
 45   trace-should-contain [
 46     a: a b c
 47   ]
 48   trace-should-not-contain [
 49     a: x y z
 50   ]
 51 ]
 52 # checks are inside scenario
 53 
 54 //:: Core data structure
 55 
 56 :(before "End Types")
 57 pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <cmocka.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>

#include "event/server_events.h"
#include "xmpp/roster_list.h"
#include "xmpp/chat_session.h"
#include "config/preferences.h"
#include "ui/ui.h"
#include "ui/stub_ui.h"
#include "xmpp/muc.h"
#include "plugins/plugins.h"
#include "ui/window_list.h"

void console_shows_online_presence_when_set_online(void **state)
{
    prefs_set_string(PREF_STATUSES_CONSOLE, "online");
    plugins_init();
    roster_create();
    roster_process_pending_presence();
    char *barejid = "test1@server";
    roster_add(barejid, "bob", NULL, "both", FALSE);
    Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);

    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
    expect_value(ui_contact_online, last_activity, NULL);

    sv_ev_contact_online(barejid, resource, NULL, NULL);

    roster_destroy();
    plugins_shutdown();
}

void console_shows_online_presence_when_set_all(void **state)
{
    prefs_set_string(PREF_STATUSES_CONSOLE, "all");
    plugins_init();
    roster_create();
    roster_process_pending_presence();
    char *barejid = "test1@server";
    roster_add(barejid, "bob", NULL, "both", FALSE);
    Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);

    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
    expect_value(ui_contact_online, last_activity, NULL);

    sv_ev_contact_online(barejid, resource, NULL, NULL);

    roster_destroy();
    plugins_shutdown();
}

void console_shows_dnd_presence_when_set_all(void **state)
{
    prefs_set_string(PREF_STATUSES_CONSOLE, "all");
    plugins_init();
    roster_create();
    roster_process_pending_presence();
    char *barejid = "test1@server";
    roster_add(barejid, "bob", NULL, "both", FALSE);
    Resource *resource = resource_new("resource", RESOURCE_ONLINE, NULL, 10);

    expect_memory(ui_contact_online, barejid, barejid, sizeof(barejid));
    expect_memory(ui_contact_online, resource, resource, sizeof(resource));
    expect_value(ui_contact_online, last_activity, NULL);

    sv_ev_contact_online(barejid, resource, NULL, NULL);

    roster_destroy();
    plugins_shutdown();
}

void handle_offline_removes_chat_session(void **state)
{
    plugins_init();
    roster_create();
    roster_process_pending_presence();
    chat_sessions_init();
    char *barejid = "friend@server.chat.com";
    char *resource = "home";
    roster_add(barejid, "bob", NULL, "both", FALSE);
    Resource *resourcep = resource_new(resource, RESOURCE_ONLINE, NULL, 10);
    roster_update_presence(barejid, resourcep, NULL);
    chat_session_recipient_active(barejid, resource, FALSE);
    ProfConsoleWin *console = malloc(sizeof(ProfConsoleWin));
    will_return(win_create_console, &console->window);
    wins_init();
    sv_ev_contact_offline(barejid, resource, NULL);
    ChatSession *session = chat_session_get(barejid);

    assert_null(session);

    roster_destroy();
    chat_sessions_clear();
    plugins_shutdown();
}

void lost_connection_clears_chat_sessions(void **state)
{
    roster_create();
    roster_process_pending_presence();
    chat_sessions_init();
    chat_session_recipient_active("bob@server.org", "laptop", FALSE);
    chat_session_recipient_active("steve@server.org", "mobile", FALSE);
    expect_any_cons_show_error();

    sv_ev_lost_connection();

    ChatSession *session1 = chat_session_get("bob@server.org");
    ChatSession *session2 = chat_session_get("steve@server.org");
    assert_null(session1);
    assert_null(session2);
}
="LineNr">220 bind_special_scenario_names(tmp.at(0)); 221 transform_all(); 222 if (!trace_contains_errors()) 223 run(tmp.front()); 224 // End Mu Test Teardown 225 if (!Hide_errors && trace_contains_errors() && !Scenario_testing_scenario) 226 Passed = false; 227 if (not_already_inside_test && Trace_stream) { 228 if (Save_trace) Trace_stream->save(); 229 delete Trace_stream; 230 Trace_stream = NULL; 231 } 232 Current_scenario = NULL; 233 } 234 235 //: Permit numeric locations to be accessed in scenarios. 236 :(before "End check_default_space Special-cases") 237 // user code should never create recipes with underscores in their names 238 if (starts_with(caller.name, "scenario_")) return; // skip Mu scenarios which will use raw memory locations 239 if (starts_with(caller.name, "run_")) return; // skip calls to 'run', which should be in scenarios and will also use raw memory locations 240 241 :(before "End maybe(recipe_name) Special-cases") 242 if (starts_with(recipe_name, "scenario_")) 243 return recipe_name.substr(strlen("scenario_")) + ": "; 244 245 //: Some variables for fake resources always get special /raw addresses in scenarios. 246 247 :(code) 248 // Should contain everything passed by is_special_name but failed by is_disqualified. 249 void bind_special_scenario_names(const recipe_ordinal r) { 250 // Special Scenario Variable Names(r) 251 // End Special Scenario Variable Names(r) 252 } 253 :(before "Done Placing Ingredient(ingredient, inst, caller)") 254 maybe_make_raw(ingredient, caller); 255 :(before "Done Placing Product(product, inst, caller)") 256 maybe_make_raw(product, caller); 257 :(code) 258 void maybe_make_raw(reagent& r, const recipe& caller) { 259 if (!is_special_name(r.name)) return; 260 if (starts_with(caller.name, "scenario_")) 261 r.properties.push_back(pair<string, string_tree*>("raw", NULL)); 262 // End maybe_make_raw 263 } 264 265 //: Test. 266 :(before "End is_special_name Special-cases") 267 if (s == "__maybe_make_raw_test__") return true; 268 :(before "End Special Scenario Variable Names(r)") 269 //: ugly: we only need this for this one test, but need to define it for all time 270 Name[r]["__maybe_make_raw_test__"] = Reserved_for_tests-1; 271 :(code) 272 void test_maybe_make_raw() { 273 // check that scenarios can use local-scope and special variables together 274 vector<recipe_ordinal> tmp = load( 275 "def scenario_foo [\n" 276 " local-scope\n" 277 " __maybe_make_raw_test__:num <- copy 34\n" 278 "]\n"); 279 mark_autogenerated(tmp.at(0)); 280 bind_special_scenario_names(tmp.at(0)); 281 transform_all(); 282 run(tmp.at(0)); 283 CHECK_TRACE_DOESNT_CONTAIN_ERRORS(); 284 } 285 286 //: Watch out for redefinitions of scenario routines. We should never ever be 287 //: doing that, regardless of anything else. 288 :(scenario forbid_redefining_scenario_even_if_forced) 289 % Hide_errors = true; 290 % Disable_redefine_checks = true; 291 def scenario-foo [ 292 1:num <- copy 34 293 ] 294 def scenario-foo [ 295 1:num <- copy 35 296 ] 297 +error: redefining recipe scenario-foo 298 299 :(scenario scenario_containing_parse_error) 300 % Hide_errors = true; 301 scenario foo [ 302 memory-should-contain [ 303 1 <- 0 304 # missing ']' 305 ] 306 # no crash 307 308 :(scenario scenario_containing_transform_error) 309 % Hide_errors = true; 310 def main [ 311 local-scope 312 add x, 1 313 ] 314 # no crash 315 316 :(after "bool should_check_for_redefine(const string& recipe_name)") 317 if (recipe_name.find("scenario-") == 0) return true; 318 319 //:: The special instructions we want to support inside scenarios. 320 //: These are easy to support in an interpreter, but will require more work 321 //: when we eventually build a compiler. 322 323 //: 'run' is a purely lexical convenience to separate the code actually being 324 //: tested from any setup 325 326 :(scenario run) 327 def main [ 328 run [ 329 1:num <- copy 13 330 ] 331 ] 332 +mem: storing 13 in location 1 333 334 :(before "End Rewrite Instruction(curr, recipe result)") 335 if (curr.name == "run") { 336 // Just inline all instructions inside the run block in the containing 337 // recipe. 'run' is basically a comment; pretend it doesn't exist. 338 istringstream in2("[\n"+curr.ingredients.at(0).name+"\n]\n"); 339 slurp_body(in2, result); 340 curr.clear(); 341 } 342 343 :(scenario run_multiple) 344 def main [ 345 run [ 346 1:num <- copy 13 347 ] 348 run [ 349 2:num <- copy 13 350 ] 351 ] 352 +mem: storing 13 in location 1 353 +mem: storing 13 in location 2 354 355 //: 'memory-should-contain' raises errors if specific locations aren't as expected 356 //: Also includes some special support for checking Mu texts. 357 358 :(before "End Globals") 359 bool Scenario_testing_scenario = false; 360 :(before "End Reset") 361 Scenario_testing_scenario = false; 362 363 :(scenario memory_check) 364 % Scenario_testing_scenario = true; 365 % Hide_errors = true; 366 def main [ 367 memory-should-contain [ 368 1 <- 13 369 ] 370 ] 371 +run: checking location 1 372 +error: F - main: expected location '1' to contain 13 but saw 0 373 374 :(before "End Primitive Recipe Declarations") 375 MEMORY_SHOULD_CONTAIN, 376 :(before "End Primitive Recipe Numbers") 377 put(Recipe_ordinal, "memory-should-contain", MEMORY_SHOULD_CONTAIN); 378 :(before "End Primitive Recipe Checks") 379 case MEMORY_SHOULD_CONTAIN: { 380 break; 381 } 382 :(before "End Primitive Recipe Implementations") 383 case MEMORY_SHOULD_CONTAIN: { 384 if (!Passed) break; 385 check_memory(current_instruction().ingredients.at(0).name); 386 break; 387 } 388 389 :(code) 390 void check_memory(const string& s) { 391 istringstream in(s); 392 in >> std::noskipws; 393 set<int> locations_checked; 394 while (true) { 395 skip_whitespace_and_comments(in); 396 if (!has_data(in)) break; 397 string lhs = next_word(in); 398 if (lhs.empty()) { 399 assert(!has_data(in)); 400 raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (0)\n" << end(); 401 return; 402 } 403 if (!is_integer(lhs)) { 404 check_type(lhs, in); 405 continue; 406 } 407 int address = to_integer(lhs); 408 skip_whitespace_and_comments(in); 409 string _assign; in >> _assign; assert(_assign == "<-"); 410 skip_whitespace_and_comments(in); 411 string rhs = next_word(in); 412 if (rhs.empty()) { 413 assert(!has_data(in)); 414 raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (1)\n" << end(); 415 return; 416 } 417 if (!is_integer(rhs) && !is_noninteger(rhs)) { 418 if (!Hide_errors) cerr << '\n'; 419 raise << "F - " << maybe(current_recipe_name()) << "location '" << address << "' can't contain non-number " << rhs << '\n' << end(); 420 if (!Scenario_testing_scenario) Passed = false; 421 return; 422 } 423 double value = to_double(rhs); 424 if (contains_key(locations_checked, address)) 425 raise << maybe(current_recipe_name()) << "duplicate expectation for location '" << address << "'\n" << end(); 426 trace("run") << "checking location " << address << end(); 427 if (get_or_insert(Memory, address) != value) { 428 if (!Hide_errors) cerr << '\n'; 429 raise << "F - " << maybe(current_recipe_name()) << "expected location '" << address << "' to contain " << no_scientific(value) << " but saw " << no_scientific(get_or_insert(Memory, address)) << '\n' << end(); 430 if (!Scenario_testing_scenario) Passed = false; 431 return; 432 } 433 locations_checked.insert(address); 434 } 435 } 436 437 void check_type(const string& lhs, istream& in) { 438 reagent x(lhs); 439 if (is_mu_array(x.type) && is_mu_character(array_element(x.type))) { 440 x.set_value(to_integer(x.name)); 441 skip_whitespace_and_comments(in); 442 string _assign = next_word(in); 443 if (_assign.empty()) { 444 assert(!has_data(in)); 445 raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (2)\n" << end(); 446 return; 447 } 448 assert(_assign == "<-"); 449 skip_whitespace_and_comments(in); 450 string literal = next_word(in); 451 if (literal.empty()) { 452 assert(!has_data(in)); 453 raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (3)\n" << end(); 454 return; 455 } 456 int address = x.value; 457 // exclude quoting brackets 458 if (*literal.begin() != '[') { 459 raise << maybe(current_recipe_name()) << "array:character types inside 'memory-should-contain' can only be compared with text literals surrounded by [], not '" << literal << "'\n" << end(); 460 return; 461 } 462 literal.erase(literal.begin()); 463 assert(*--literal.end() == ']'); literal.erase(--literal.end()); 464 check_mu_text(address, literal); 465 return; 466 } 467 // End Scenario Type Special-cases 468 raise << "don't know how to check memory for '" << lhs << "'\n" << end(); 469 } 470 471 void check_mu_text(int start, const string& literal) { 472 trace("run") << "checking text length at " << start << end(); 473 int array_length = static_cast<int>(get_or_insert(Memory, start)); 474 if (array_length != SIZE(literal)) { 475 if (!Hide_errors) cerr << '\n'; 476 raise << "F - " << maybe(current_recipe_name()) << "expected location '" << start << "' to contain length " << SIZE(literal) << " of text [" << literal << "] but saw " << array_length << " (for text [" << read_mu_characters(start+/*skip length*/1, array_length) << "])\n" << end(); 477 if (!Scenario_testing_scenario) Passed = false; 478 return; 479 } 480 int curr = start+1; // now skip length 481 for (int i = 0; i < SIZE(literal); ++i) { 482 trace("run") << "checking location " << curr+i << end(); 483 if (get_or_insert(Memory, curr+i) != literal.at(i)) { 484 if (!Hide_errors) cerr << '\n'; 485 raise << "F - " << maybe(current_recipe_name()) << "expected location " << (curr+i) << " to contain " << literal.at(i) << " but saw " << no_scientific(get_or_insert(Memory, curr+i)) << '\n' << end(); 486 if (!Scenario_testing_scenario) Passed = false; 487 return; 488 } 489 } 490 } 491 492 :(scenario memory_check_multiple) 493 % Scenario_testing_scenario = true; 494 % Hide_errors = true; 495 def main [ 496 memory-should-contain [ 497 1 <- 0 498 1 <- 0 499 ] 500 ] 501 +error: main: duplicate expectation for location '1' 502 503 :(scenario memory_check_mu_text_length) 504 % Scenario_testing_scenario = true; 505 % Hide_errors = true; 506 def main [ 507 1:num <- copy 3 508 2:num <- copy 97 # 'a' 509 3:num <- copy 98 # 'b' 510 4:num <- copy 99 # 'c' 511 memory-should-contain [ 512 1:array:character <- [ab] 513 ] 514 ] 515 +error: F - main: expected location '1' to contain length 2 of text [ab] but saw 3 (for text [abc]) 516 517 :(scenario memory_check_mu_text) 518 def main [ 519 1:num <- copy 3 520 2:num <- copy 97 # 'a' 521 3:num <- copy 98 # 'b' 522 4:num <- copy 99 # 'c' 523 memory-should-contain [ 524 1:array:character <- [abc] 525 ] 526 ] 527 +run: checking text length at 1 528 +run: checking location 2 529 +run: checking location 3 530 +run: checking location 4 531 532 :(scenario memory_invalid_string_check) 533 % Scenario_testing_scenario = true; 534 % Hide_errors = true; 535 def main [ 536 memory-should-contain [ 537 1 <- [abc] 538 ] 539 ] 540 +error: F - main: location '1' can't contain non-number [abc] 541 542 :(scenario memory_invalid_string_check2) 543 % Hide_errors = true; 544 def main [ 545 1:num <- copy 3 546 2:num <- copy 97 # 'a' 547 3:num <- copy 98 # 'b' 548 4:num <- copy 99 # 'c' 549 memory-should-contain [ 550 1:array:character <- 0 551 ] 552 ] 553 +error: main: array:character types inside 'memory-should-contain' can only be compared with text literals surrounded by [], not '0' 554 555 :(scenario memory_check_with_comment) 556 % Scenario_testing_scenario = true; 557 % Hide_errors = true; 558 def main [ 559 memory-should-contain [ 560 1 <- 34 # comment 561 ] 562 ] 563 -error: location 1 can't contain non-number 34 # comment 564 # but there'll be an error signalled by memory-should-contain 565 566 //: 'trace-should-contain' is like the '+' lines in our scenarios so far 567 // Like runs of contiguous '+' lines, order is important. The trace checks 568 // that the lines are present *and* in the specified sequence. (There can be 569 // other lines in between.) 570 571 :(scenario trace_check_fails) 572 % Scenario_testing_scenario = true; 573 % Hide_errors = true; 574 def main [ 575 trace-should-contain [ 576 a: b 577 a: d 578 ] 579 ] 580 +error: F - main: missing [b] in trace with label 'a' 581 582 :(before "End Primitive Recipe Declarations") 583 TRACE_SHOULD_CONTAIN, 584 :(before "End Primitive Recipe Numbers") 585 put(Recipe_ordinal, "trace-should-contain", TRACE_SHOULD_CONTAIN); 586 :(before "End Primitive Recipe Checks") 587 case TRACE_SHOULD_CONTAIN: { 588 break; 589 } 590 :(before "End Primitive Recipe Implementations") 591 case TRACE_SHOULD_CONTAIN: { 592 if (!Passed) break; 593 check_trace(current_instruction().ingredients.at(0).name); 594 break; 595 } 596 597 :(code) 598 // simplified version of check_trace_contents() that emits errors rather 599 // than just printing to stderr 600 void check_trace(const string& expected) { 601 Trace_stream->newline(); 602 vector<trace_line> expected_lines = parse_trace(expected); 603 if (expected_lines.empty()) return; 604 int curr_expected_line = 0; 605 for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { 606 if (expected_lines.at(curr_expected_line).label != p->label) continue; 607 if (expected_lines.at(curr_expected_line).contents != trim(p->contents)) continue; 608 // match 609 ++curr_expected_line; 610 if (curr_expected_line == SIZE(expected_lines)) return; 611 } 612 if (!Hide_errors) cerr << '\n'; 613 raise << "F - " << maybe(current_recipe_name()) << "missing [" << expected_lines.at(curr_expected_line).contents << "] " 614 << "in trace with label '" << expected_lines.at(curr_expected_line).label << "'\n" << end(); 615 if (!Hide_errors) 616 DUMP(expected_lines.at(curr_expected_line).label); 617 if (!Scenario_testing_scenario) Passed = false; 618 } 619 620 vector<trace_line> parse_trace(const string& expected) { 621 vector<string> buf = split(expected, "\n"); 622 vector<trace_line> result; 623 for (int i = 0; i < SIZE(buf); ++i) { 624 buf.at(i) = trim(buf.at(i)); 625 if (buf.at(i).empty()) continue; 626 int delim = buf.at(i).find(": "); 627 if (delim == -1) { 628 raise << maybe(current_recipe_name()) << "lines in 'trace-should-contain' should be of the form <label>: <contents>. Both parts are required.\n" << end(); 629 result.clear(); 630 return result; 631 } 632 result.push_back(trace_line(trim(buf.at(i).substr(0, delim)), trim(buf.at(i).substr(delim+2)))); 633 } 634 return result; 635 } 636 637 :(scenario trace_check_fails_in_nonfirst_line) 638 % Scenario_testing_scenario = true; 639 % Hide_errors = true; 640 def main [ 641 run [ 642 trace 1, [a], [b] 643 ] 644 trace-should-contain [ 645 a: b 646 a: d 647 ] 648 ] 649 +error: F - main: missing [d] in trace with label 'a' 650 651 :(scenario trace_check_passes_silently) 652 % Scenario_testing_scenario = true; 653 def main [ 654 run [ 655 trace 1, [a], [b] 656 ] 657 trace-should-contain [ 658 a: b 659 ] 660 ] 661 -error: missing [b] in trace with label 'a' 662 $error: 0 663 664 //: 'trace-should-not-contain' is like the '-' lines in our scenarios so far 665 //: Each trace line is separately checked for absense. Order is *not* 666 //: important, so you can't say things like "B should not exist after A." 667 668 :(scenario trace_negative_check_fails) 669 % Scenario_testing_scenario = true; 670 % Hide_errors = true; 671 def main [ 672 run [ 673 trace 1, [a], [b] 674 ] 675 trace-should-not-contain [ 676 a: b 677 ] 678 ] 679 +error: F - main: unexpected [b] in trace with label 'a' 680 681 :(before "End Primitive Recipe Declarations") 682 TRACE_SHOULD_NOT_CONTAIN, 683 :(before "End Primitive Recipe Numbers") 684 put(Recipe_ordinal, "trace-should-not-contain", TRACE_SHOULD_NOT_CONTAIN); 685 :(before "End Primitive Recipe Checks") 686 case TRACE_SHOULD_NOT_CONTAIN: { 687 break; 688 } 689 :(before "End Primitive Recipe Implementations") 690 case TRACE_SHOULD_NOT_CONTAIN: { 691 if (!Passed) break; 692 check_trace_missing(current_instruction().ingredients.at(0).name); 693 break; 694 } 695 696 :(code) 697 // simplified version of check_trace_contents() that emits errors rather 698 // than just printing to stderr 699 bool check_trace_missing(const string& in) { 700 Trace_stream->newline(); 701 vector<trace_line> lines = parse_trace(in); 702 for (int i = 0; i < SIZE(lines); ++i) { 703 if (trace_count(lines.at(i).label, lines.at(i).contents) != 0) { 704 raise << "F - " << maybe(current_recipe_name()) << "unexpected [" << lines.at(i).contents << "] in trace with label '" << lines.at(i).label << "'\n" << end(); 705 if (!Scenario_testing_scenario) Passed = false; 706 return false; 707 } 708 } 709 return true; 710 } 711 712 :(scenario trace_negative_check_passes_silently) 713 % Scenario_testing_scenario = true; 714 def main [ 715 trace-should-not-contain [ 716 a: b 717 ] 718 ] 719 -error: unexpected [b] in trace with label 'a' 720 $error: 0 721 722 :(scenario trace_negative_check_fails_on_any_unexpected_line) 723 % Scenario_testing_scenario = true; 724 % Hide_errors = true; 725 def main [ 726 run [ 727 trace 1, [a], [d] 728 ] 729 trace-should-not-contain [ 730 a: b 731 a: d 732 ] 733 ] 734 +error: F - main: unexpected [d] in trace with label 'a' 735 736 :(scenario trace_count_check) 737 def main [ 738 run [ 739 trace 1, [a], [foo] 740 ] 741 check-trace-count-for-label 1, [a] 742 ] 743 # checks are inside scenario 744 745 :(before "End Primitive Recipe Declarations") 746 CHECK_TRACE_COUNT_FOR_LABEL, 747 :(before "End Primitive Recipe Numbers") 748 put(Recipe_ordinal, "check-trace-count-for-label", CHECK_TRACE_COUNT_FOR_LABEL); 749 :(before "End Primitive Recipe Checks") 750 case CHECK_TRACE_COUNT_FOR_LABEL: { 751 if (SIZE(inst.ingredients) != 2) { 752 raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end(); 753 break; 754 } 755 if (!is_mu_number(inst.ingredients.at(0))) { 756 raise << maybe(get(Recipe, r).name) << "first ingredient of 'check-trace-count-for-label' should be a number (count), but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); 757 break; 758 } 759 if (!is_literal_text(inst.ingredients.at(1))) { 760 raise << maybe(get(Recipe, r).name) << "second ingredient of 'check-trace-count-for-label' should be a literal string (label), but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); 761 break; 762 } 763 break; 764 } 765 :(before "End Primitive Recipe Implementations") 766 case CHECK_TRACE_COUNT_FOR_LABEL: { 767 if (!Passed) break; 768 int expected_count = ingredients.at(0).at(0); 769 string label = current_instruction().ingredients.at(1).name; 770 int count = trace_count(label); 771 if (count != expected_count) { 772 if (!Hide_errors) cerr << '\n'; 773 raise << "F - " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end(); 774 if (!Hide_errors) DUMP(label); 775 if (!Scenario_testing_scenario) Passed = false; 776 } 777 break; 778 } 779 780 :(before "End Primitive Recipe Declarations") 781 CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN, 782 :(before "End Primitive Recipe Numbers") 783 put(Recipe_ordinal, "check-trace-count-for-label-greater-than", CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN); 784 :(before "End Primitive Recipe Checks") 785 case CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN: { 786 if (SIZE(inst.ingredients) != 2) { 787 raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end(); 788 break; 789 } 790 if (!is_mu_number(inst.ingredients.at(0))) { 791 raise << maybe(get(Recipe, r).name) << "first ingredient of 'check-trace-count-for-label' should be a number (count), but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); 792 break; 793 } 794 if (!is_literal_text(inst.ingredients.at(1))) { 795 raise << maybe(get(Recipe, r).name) << "second ingredient of 'check-trace-count-for-label' should be a literal string (label), but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); 796 break; 797 } 798 break; 799 } 800 :(before "End Primitive Recipe Implementations") 801 case CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN: { 802 if (!Passed) break; 803 int expected_count = ingredients.at(0).at(0); 804 string label = current_instruction().ingredients.at(1).name; 805 int count = trace_count(label); 806 if (count <= expected_count) { 807 if (!Hide_errors) cerr << '\n'; 808 raise << maybe(current_recipe_name()) << "expected more than " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end(); 809 if (!Hide_errors) { 810 cerr << "trace contents:\n"; 811 DUMP(label); 812 } 813 if (!Scenario_testing_scenario) Passed = false; 814 } 815 break; 816 } 817 818 :(before "End Primitive Recipe Declarations") 819 CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN, 820 :(before "End Primitive Recipe Numbers") 821 put(Recipe_ordinal, "check-trace-count-for-label-lesser-than", CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN); 822 :(before "End Primitive Recipe Checks") 823 case CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN: { 824 if (SIZE(inst.ingredients) != 2) { 825 raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end(); 826 break; 827 } 828 if (!is_mu_number(inst.ingredients.at(0))) { 829 raise << maybe(get(Recipe, r).name) << "first ingredient of 'check-trace-count-for-label' should be a number (count), but got '" << inst.ingredients.at(0).original_string << "'\n" << end(); 830 break; 831 } 832 if (!is_literal_text(inst.ingredients.at(1))) { 833 raise << maybe(get(Recipe, r).name) << "second ingredient of 'check-trace-count-for-label' should be a literal string (label), but got '" << inst.ingredients.at(1).original_string << "'\n" << end(); 834 break; 835 } 836 break; 837 } 838 :(before "End Primitive Recipe Implementations") 839 case CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN: { 840 if (!Passed) break; 841 int expected_count = ingredients.at(0).at(0); 842 string label = current_instruction().ingredients.at(1).name; 843 int count = trace_count(label); 844 if (count >= expected_count) { 845 if (!Hide_errors) cerr << '\n'; 846 raise << "F - " << maybe(current_recipe_name()) << "expected less than " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end(); 847 if (!Hide_errors) { 848 cerr << "trace contents:\n"; 849 DUMP(label); 850 } 851 if (!Scenario_testing_scenario) Passed = false; 852 } 853 break; 854 } 855 856 :(scenario trace_count_check_2) 857 % Scenario_testing_scenario = true; 858 % Hide_errors = true; 859 def main [ 860 run [ 861 trace 1, [a], [foo] 862 ] 863 check-trace-count-for-label 2, [a] 864 ] 865 +error: F - main: expected 2 lines in trace with label 'a' in trace 866 867 //: Minor detail: ignore 'system' calls in scenarios, since anything we do 868 //: with them is by definition impossible to test through Mu. 869 :(after "case _SYSTEM:") 870 if (Current_scenario) break; 871 872 //:: Warn if people use '_' manually in recipe names. They're reserved for internal use. 873 874 :(scenario recipe_name_with_underscore) 875 % Hide_errors = true; 876 def foo_bar [ 877 ] 878 +error: foo_bar: don't create recipes with '_' in the name 879 880 :(before "End recipe Fields") 881 bool is_autogenerated; 882 :(before "End recipe Constructor") 883 is_autogenerated = false; 884 :(code) 885 void mark_autogenerated(recipe_ordinal r) { 886 get(Recipe, r).is_autogenerated = true; 887 } 888 889 :(after "void transform_all()") 890 for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) { 891 const recipe& r = p->second; 892 if (r.name.find('_') == string::npos) continue; 893 if (r.is_autogenerated) continue; // created by previous call to transform_all() 894 raise << r.name << ": don't create recipes with '_' in the name\n" << end(); 895 } 896 897 //:: Helpers 898 899 :(code) 900 // just for the scenarios running scenarios in C++ layers 901 void run_mu_scenario(const string& form) { 902 Scenario_names.clear(); 903 istringstream in(form); 904 in >> std::noskipws; 905 skip_whitespace_and_comments(in); 906 string _scenario = next_word(in); 907 if (_scenario.empty()) { 908 assert(!has_data(in)); 909 raise << "no scenario in string passed into run_mu_scenario()\n" << end(); 910 return; 911 } 912 assert(_scenario == "scenario"); 913 scenario s = parse_scenario(in); 914 run_mu_scenario(s); 915 }