mu/050scenario.cc

916 lines
28 KiB
C++
Raw Normal View History

//: Mu scenarios. This will get long, but these are the tests we want to
//: support in this layer.
2015-04-20 17:14:56 +00:00
//: We avoid raw numeric locations in Mu -- except in scenarios, where they're
//: handy to check the values of specific variables
:(scenarios run_mu_scenario)
:(scenario scenario_block)
scenario foo [
run [
2016-09-17 17:28:25 +00:00
1:num <- copy 13
]
memory-should-contain [
1 <- 13
]
]
# checks are inside scenario
2015-04-20 17:14:56 +00:00
:(scenario scenario_multiple_blocks)
2015-04-20 17:14:56 +00:00
scenario foo [
run [
2016-09-17 17:28:25 +00:00
1:num <- copy 13
]
memory-should-contain [
1 <- 13
]
run [
2016-09-17 17:28:25 +00:00
2:num <- copy 13
2015-04-20 17:14:56 +00:00
]
memory-should-contain [
1 <- 13
2 <- 13
2015-04-20 17:14:56 +00:00
]
]
2016-07-11 04:47:24 +00:00
# checks are inside scenario
2015-04-20 17:14:56 +00:00
:(scenario scenario_check_memory_and_trace)
scenario foo [
run [
2016-09-17 17:28:25 +00:00
1:num <- copy 13
trace 1, [a], [a b c]
]
memory-should-contain [
1 <- 13
]
trace-should-contain [
a: a b c
]
trace-should-not-contain [
a: x y z
]
]
2016-07-11 04:47:24 +00:00
# checks are inside scenario
//:: Core data structure
:(before "End Types")
struct scenario {
string name;
string to_run;
void clear() {
name.clear();
to_run.clear();
}
};
:(before "End Globals")
vector<scenario> Scenarios, Scenarios_snapshot;
set<string> Scenario_names, Scenario_names_snapshot;
:(before "End save_snapshots")
Scenarios_snapshot = Scenarios;
Scenario_names_snapshot = Scenario_names;
:(before "End restore_snapshots")
Scenarios = Scenarios_snapshot;
Scenario_names = Scenario_names_snapshot;
//:: Parse the 'scenario' form.
//: Simply store the text of the scenario.
:(before "End Command Handlers")
else if (command == "scenario") {
scenario result = parse_scenario(in);
if (!result.name.empty())
Scenarios.push_back(result);
}
else if (command == "pending-scenario") {
// for temporary use only
parse_scenario(in); // discard
}
:(code)
scenario parse_scenario(istream& in) {
scenario result;
result.name = next_word(in);
if (contains_key(Scenario_names, result.name))
raise << "duplicate scenario name: '" << result.name << "'\n" << end();
Scenario_names.insert(result.name);
if (result.name.empty()) {
assert(!has_data(in));
raise << "incomplete scenario at end of file\n" << end();
return result;
}
skip_whitespace_and_comments(in);
2016-05-05 15:13:45 +00:00
if (in.peek() != '[') {
raise << "Expected '[' after scenario '" << result.name << "'\n" << end();
2016-05-05 15:13:45 +00:00
exit(0);
}
// scenarios are take special 'code' strings so we need to ignore brackets
// inside comments
2015-06-14 23:24:37 +00:00
result.to_run = slurp_quoted(in);
// delete [] delimiters
if (!starts_with(result.to_run, "[")) {
raise << "scenario " << result.name << " should start with '['\n" << end();
result.clear();
return result;
}
2015-06-14 23:24:37 +00:00
result.to_run.erase(0, 1);
if (result.to_run.at(SIZE(result.to_run)-1) != ']') {
raise << "scenario " << result.name << " has an unbalanced '['\n" << end();
result.clear();
return result;
}
2015-06-14 23:24:37 +00:00
result.to_run.erase(SIZE(result.to_run)-1);
return result;
}
:(scenario read_scenario_with_bracket_in_comment)
scenario foo [
# ']' in comment
2016-09-17 17:28:25 +00:00
1:num <- copy 0
]
+run: {1: "number"} <- copy {0: "literal"}
:(scenario read_scenario_with_bracket_in_comment_in_nested_string)
scenario foo [
1:text <- new [# not a comment]
]
+run: {1: ("address" "array" "character")} <- new {"# not a comment": "literal-string"}
:(scenarios run)
:(scenario duplicate_scenarios)
% Hide_errors = true;
scenario foo [
1:num <- copy 0
]
scenario foo [
2:num <- copy 0
]
+error: duplicate scenario name: 'foo'
2016-10-22 23:56:07 +00:00
//:: Run scenarios when we run './mu test'.
//: Treat the text of the scenario as a regular series of instructions.
:(before "End Globals")
int Num_core_mu_scenarios = 0;
:(after "Check For .mu Files")
Num_core_mu_scenarios = SIZE(Scenarios);
:(before "End Tests")
2016-02-25 16:24:14 +00:00
Hide_missing_default_space_errors = false;
2016-10-25 19:35:03 +00:00
if (Num_core_mu_scenarios > 0) {
time(&t);
cerr << "Mu tests: " << ctime(&t);
2016-10-20 05:10:35 +00:00
for (int i = 0; i < Num_core_mu_scenarios; ++i) {
2016-08-14 12:45:30 +00:00
//? cerr << '\n' << i << ": " << Scenarios.at(i).name;
run_mu_scenario(Scenarios.at(i));
if (Passed) cerr << ".";
else ++num_failures;
}
cerr << "\n";
}
run_app_scenarios:
if (Num_core_mu_scenarios != SIZE(Scenarios)) {
time(&t);
cerr << "App tests: " << ctime(&t);
2016-10-20 05:10:35 +00:00
for (int i = Num_core_mu_scenarios; i < SIZE(Scenarios); ++i) {
2016-08-14 12:45:30 +00:00
//? cerr << '\n' << i << ": " << Scenarios.at(i).name;
run_mu_scenario(Scenarios.at(i));
if (Passed) cerr << ".";
else ++num_failures;
}
cerr << "\n";
}
//: For faster debugging, support running tests for just the Mu app(s) we are
//: loading.
:(before "End Globals")
bool Test_only_app = false;
:(before "End Commandline Options(*arg)")
else if (is_equal(*arg, "--test-only-app")) {
Test_only_app = true;
}
:(after "End Test Run Initialization")
if (Test_only_app && Num_core_mu_scenarios < SIZE(Scenarios)) {
goto run_app_scenarios;
}
2015-05-12 16:20:19 +00:00
//: Convenience: run a single named scenario.
:(after "Test Runs")
2016-10-20 05:10:35 +00:00
for (int i = 0; i < SIZE(Scenarios); ++i) {
if (Scenarios.at(i).name == argv[argc-1]) {
run_mu_scenario(Scenarios.at(i));
if (Passed) cerr << ".\n";
return 0;
2015-05-12 16:20:19 +00:00
}
}
:(before "End Globals")
// this isn't a constant, just a global of type const*
const scenario* Current_scenario = NULL;
:(code)
void run_mu_scenario(const scenario& s) {
Current_scenario = &s;
bool not_already_inside_test = !Trace_stream;
//? cerr << s.name << '\n';
if (not_already_inside_test) {
Trace_stream = new trace_stream;
reset();
}
vector<recipe_ordinal> tmp = load("recipe scenario_"+s.name+" [ "+s.to_run+" ]");
2016-05-27 16:50:39 +00:00
mark_autogenerated(tmp.at(0));
bind_special_scenario_names(tmp.at(0));
transform_all();
if (!trace_contains_errors())
run(tmp.front());
// End Mu Test Teardown
2016-11-16 05:55:56 +00:00
if (!Hide_errors && trace_contains_errors() && !Scenario_testing_scenario)
Passed = false;
if (not_already_inside_test && Trace_stream) {
if (Save_trace) Trace_stream->save();
delete Trace_stream;
Trace_stream = NULL;
}
Current_scenario = NULL;
}
//: Permit numeric locations to be accessed in scenarios.
:(before "End check_default_space Special-cases")
// user code should never create recipes with underscores in their names
if (starts_with(caller.name, "scenario_")) return; // skip Mu scenarios which will use raw memory locations
if (starts_with(caller.name, "run_")) return; // skip calls to 'run', which should be in scenarios and will also use raw memory locations
:(before "End maybe(recipe_name) Special-cases")
if (starts_with(recipe_name, "scenario_"))
return recipe_name.substr(strlen("scenario_")) + ": ";
2016-05-27 16:50:39 +00:00
//: Some variables for fake resources always get special /raw addresses in scenarios.
2016-12-12 18:31:57 +00:00
:(code)
2016-05-27 16:50:39 +00:00
// Should contain everything passed by is_special_name but failed by is_disqualified.
2016-10-22 23:10:23 +00:00
void bind_special_scenario_names(const recipe_ordinal r) {
2016-05-27 16:50:39 +00:00
// Special Scenario Variable Names(r)
// End Special Scenario Variable Names(r)
}
2016-08-25 17:11:04 +00:00
:(before "Done Placing Ingredient(ingredient, inst, caller)")
maybe_make_raw(ingredient, caller);
:(before "Done Placing Product(product, inst, caller)")
maybe_make_raw(product, caller);
2016-05-27 16:50:39 +00:00
:(code)
void maybe_make_raw(reagent& r, const recipe& caller) {
if (!is_special_name(r.name)) return;
if (starts_with(caller.name, "scenario_"))
r.properties.push_back(pair<string, string_tree*>("raw", NULL));
// End maybe_make_raw
}
//: Test.
:(before "End is_special_name Special-cases")
2016-05-27 16:50:39 +00:00
if (s == "__maybe_make_raw_test__") return true;
:(before "End Special Scenario Variable Names(r)")
//: ugly: we only need this for this one test, but need to define it for all time
Name[r]["__maybe_make_raw_test__"] = Reserved_for_tests-1;
:(code)
void test_maybe_make_raw() {
// check that scenarios can use local-scope and special variables together
vector<recipe_ordinal> tmp = load(
"def scenario_foo [\n"
" local-scope\n"
2016-09-17 17:28:25 +00:00
" __maybe_make_raw_test__:num <- copy 34\n"
2016-05-27 16:50:39 +00:00
"]\n");
mark_autogenerated(tmp.at(0));
bind_special_scenario_names(tmp.at(0));
transform_all();
run(tmp.at(0));
2016-11-16 05:55:56 +00:00
CHECK_TRACE_DOESNT_CONTAIN_ERRORS();
2016-05-27 16:50:39 +00:00
}
//: Watch out for redefinitions of scenario routines. We should never ever be
//: doing that, regardless of anything else.
:(scenario forbid_redefining_scenario_even_if_forced)
2016-02-25 15:58:09 +00:00
% Hide_errors = true;
% Disable_redefine_checks = true;
def scenario-foo [
2016-09-17 17:28:25 +00:00
1:num <- copy 34
]
def scenario-foo [
2016-09-17 17:28:25 +00:00
1:num <- copy 35
]
2016-02-25 15:58:09 +00:00
+error: redefining recipe scenario-foo
:(scenario scenario_containing_parse_error)
% Hide_errors = true;
scenario foo [
memory-should-contain [
1 <- 0
# missing ']'
]
# no crash
:(scenario scenario_containing_transform_error)
% Hide_errors = true;
def main [
local-scope
add x, 1
]
# no crash
:(after "bool should_check_for_redefine(const string& recipe_name)")
2015-08-24 21:19:52 +00:00
if (recipe_name.find("scenario-") == 0) return true;
//:: The special instructions we want to support inside scenarios.
2016-10-22 23:56:07 +00:00
//: These are easy to support in an interpreter, but will require more work
//: when we eventually build a compiler.
//: 'run' is a purely lexical convenience to separate the code actually being
//: tested from any setup
:(scenario run)
def main [
run [
2016-09-17 17:28:25 +00:00
1:num <- copy 13
]
]
+mem: storing 13 in location 1
:(before "End Rewrite Instruction(curr, recipe result)")
if (curr.name == "run") {
// Just inline all instructions inside the run block in the containing
// recipe. 'run' is basically a comment; pretend it doesn't exist.
istringstream in2("[\n"+curr.ingredients.at(0).name+"\n]\n");
slurp_body(in2, result);
curr.clear();
2015-10-02 00:30:14 +00:00
}
:(scenario run_multiple)
def main [
run [
2016-09-17 17:28:25 +00:00
1:num <- copy 13
]
run [
2016-09-17 17:28:25 +00:00
2:num <- copy 13
]
]
+mem: storing 13 in location 1
+mem: storing 13 in location 2
//: 'memory-should-contain' raises errors if specific locations aren't as expected
//: Also includes some special support for checking Mu texts.
:(before "End Globals")
bool Scenario_testing_scenario = false;
2017-07-09 21:34:17 +00:00
:(before "End Reset")
Scenario_testing_scenario = false;
:(scenario memory_check)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
memory-should-contain [
1 <- 13
]
]
+run: checking location 1
+error: F - main: expected location '1' to contain 13 but saw 0
:(before "End Primitive Recipe Declarations")
MEMORY_SHOULD_CONTAIN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "memory-should-contain", MEMORY_SHOULD_CONTAIN);
2015-10-02 00:30:14 +00:00
:(before "End Primitive Recipe Checks")
case MEMORY_SHOULD_CONTAIN: {
break;
}
:(before "End Primitive Recipe Implementations")
case MEMORY_SHOULD_CONTAIN: {
2015-05-28 01:32:07 +00:00
if (!Passed) break;
check_memory(current_instruction().ingredients.at(0).name);
break;
}
:(code)
void check_memory(const string& s) {
istringstream in(s);
in >> std::noskipws;
set<int> locations_checked;
while (true) {
skip_whitespace_and_comments(in);
if (!has_data(in)) break;
string lhs = next_word(in);
if (lhs.empty()) {
assert(!has_data(in));
raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (0)\n" << end();
return;
}
2015-05-17 04:24:21 +00:00
if (!is_integer(lhs)) {
check_type(lhs, in);
continue;
}
int address = to_integer(lhs);
skip_whitespace_and_comments(in);
string _assign; in >> _assign; assert(_assign == "<-");
skip_whitespace_and_comments(in);
2016-02-25 05:04:11 +00:00
string rhs = next_word(in);
if (rhs.empty()) {
assert(!has_data(in));
raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (1)\n" << end();
return;
}
2016-02-25 05:04:11 +00:00
if (!is_integer(rhs) && !is_noninteger(rhs)) {
if (!Hide_errors) cerr << '\n';
raise << "F - " << maybe(current_recipe_name()) << "location '" << address << "' can't contain non-number " << rhs << '\n' << end();
if (!Scenario_testing_scenario) Passed = false;
2016-02-25 05:04:11 +00:00
return;
}
double value = to_double(rhs);
if (contains_key(locations_checked, address))
raise << maybe(current_recipe_name()) << "duplicate expectation for location '" << address << "'\n" << end();
trace("run") << "checking location " << address << end();
if (get_or_insert(Memory, address) != value) {
if (!Hide_errors) cerr << '\n';
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();
if (!Scenario_testing_scenario) Passed = false;
return;
}
locations_checked.insert(address);
}
}
void check_type(const string& lhs, istream& in) {
reagent x(lhs);
3663 - fix a refcounting bug: '(type)' != 'type' This was a large commit, and most of it is a follow-up to commit 3309, undoing what is probably the final ill-considered optimization I added to s-expressions in Mu: I was always representing (a b c) as (a b . c), etc. That is now gone. Why did I need to take it out? The key problem was the error silently ignored in layer 30. That was causing size_of("(type)") to silently return garbage rather than loudly complain (assuming 'type' was a simple type). But to take it out I had to modify types_strictly_match (layer 21) to actually strictly match and not just do a prefix match. In the process of removing the prefix match, I had to make extracting recipe types from recipe headers more robust. So far it only matched the first element of each ingredient's type; these matched: (recipe address:number -> address:number) (recipe address -> address) I didn't notice because the dotted notation optimization was actually representing this as: (recipe address:number -> address number) --- One final little thing in this commit: I added an alias for 'assert' called 'assert_for_now', to indicate that I'm not sure something's really an invariant, that it might be triggered by (invalid) user programs, and so require more thought on error handling down the road. But this may well be an ill-posed distinction. It may be overwhelmingly uneconomic to continually distinguish between model invariants and error states for input. I'm starting to grow sympathetic to Google Analytics's recent approach of just banning assertions altogether. We'll see..
2016-11-11 05:39:02 +00:00
if (is_mu_array(x.type) && is_mu_character(array_element(x.type))) {
2015-05-17 04:24:21 +00:00
x.set_value(to_integer(x.name));
skip_whitespace_and_comments(in);
string _assign = next_word(in);
if (_assign.empty()) {
assert(!has_data(in));
raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (2)\n" << end();
return;
}
assert(_assign == "<-");
skip_whitespace_and_comments(in);
string literal = next_word(in);
if (literal.empty()) {
assert(!has_data(in));
raise << maybe(current_recipe_name()) << "incomplete 'memory-should-contain' block at end of file (3)\n" << end();
return;
}
int address = x.value;
// exclude quoting brackets
if (*literal.begin() != '[') {
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();
return;
}
literal.erase(literal.begin());
assert(*--literal.end() == ']'); literal.erase(--literal.end());
check_mu_text(address, literal);
return;
}
// End Scenario Type Special-cases
raise << "don't know how to check memory for '" << lhs << "'\n" << end();
}
void check_mu_text(int start, const string& literal) {
trace("run") << "checking text length at " << start << end();
int array_length = static_cast<int>(get_or_insert(Memory, start));
if (array_length != SIZE(literal)) {
if (!Hide_errors) cerr << '\n';
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();
if (!Scenario_testing_scenario) Passed = false;
2015-05-23 19:30:58 +00:00
return;
}
int curr = start+1; // now skip length
2016-10-20 05:10:35 +00:00
for (int i = 0; i < SIZE(literal); ++i) {
trace("run") << "checking location " << curr+i << end();
if (get_or_insert(Memory, curr+i) != literal.at(i)) {
if (!Hide_errors) cerr << '\n';
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();
if (!Scenario_testing_scenario) Passed = false;
2015-05-23 19:30:58 +00:00
return;
}
}
}
:(scenario memory_check_multiple)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
memory-should-contain [
1 <- 0
1 <- 0
]
]
+error: main: duplicate expectation for location '1'
:(scenario memory_check_mu_text_length)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
2016-09-17 17:28:25 +00:00
1:num <- copy 3
2:num <- copy 97 # 'a'
3:num <- copy 98 # 'b'
4:num <- copy 99 # 'c'
memory-should-contain [
1:array:character <- [ab]
]
]
+error: F - main: expected location '1' to contain length 2 of text [ab] but saw 3 (for text [abc])
:(scenario memory_check_mu_text)
def main [
2016-09-17 17:28:25 +00:00
1:num <- copy 3
2:num <- copy 97 # 'a'
3:num <- copy 98 # 'b'
4:num <- copy 99 # 'c'
memory-should-contain [
1:array:character <- [abc]
]
]
+run: checking text length at 1
+run: checking location 2
+run: checking location 3
+run: checking location 4
2016-02-25 05:04:11 +00:00
:(scenario memory_invalid_string_check)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
2016-02-25 05:04:11 +00:00
memory-should-contain [
1 <- [abc]
]
]
+error: F - main: location '1' can't contain non-number [abc]
2016-02-25 05:04:11 +00:00
:(scenario memory_invalid_string_check2)
% Hide_errors = true;
def main [
1:num <- copy 3
2:num <- copy 97 # 'a'
3:num <- copy 98 # 'b'
4:num <- copy 99 # 'c'
memory-should-contain [
1:array:character <- 0
]
]
+error: main: array:character types inside 'memory-should-contain' can only be compared with text literals surrounded by [], not '0'
2016-02-25 05:04:11 +00:00
:(scenario memory_check_with_comment)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
2016-02-25 05:04:11 +00:00
memory-should-contain [
1 <- 34 # comment
]
]
-error: location 1 can't contain non-number 34 # comment
# but there'll be an error signalled by memory-should-contain
2016-02-25 05:04:11 +00:00
//: 'trace-should-contain' is like the '+' lines in our scenarios so far
// Like runs of contiguous '+' lines, order is important. The trace checks
// that the lines are present *and* in the specified sequence. (There can be
// other lines in between.)
:(scenario trace_check_fails)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
trace-should-contain [
a: b
a: d
]
]
+error: F - main: missing [b] in trace with label 'a'
:(before "End Primitive Recipe Declarations")
TRACE_SHOULD_CONTAIN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "trace-should-contain", TRACE_SHOULD_CONTAIN);
2015-10-02 00:30:14 +00:00
:(before "End Primitive Recipe Checks")
case TRACE_SHOULD_CONTAIN: {
break;
}
:(before "End Primitive Recipe Implementations")
case TRACE_SHOULD_CONTAIN: {
2015-05-28 01:32:07 +00:00
if (!Passed) break;
check_trace(current_instruction().ingredients.at(0).name);
break;
}
:(code)
// simplified version of check_trace_contents() that emits errors rather
// than just printing to stderr
void check_trace(const string& expected) {
Trace_stream->newline();
2015-05-22 01:10:17 +00:00
vector<trace_line> expected_lines = parse_trace(expected);
if (expected_lines.empty()) return;
int curr_expected_line = 0;
2016-10-20 05:10:35 +00:00
for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
2015-05-22 01:10:17 +00:00
if (expected_lines.at(curr_expected_line).label != p->label) continue;
if (expected_lines.at(curr_expected_line).contents != trim(p->contents)) continue;
// match
++curr_expected_line;
if (curr_expected_line == SIZE(expected_lines)) return;
}
if (!Hide_errors) cerr << '\n';
raise << "F - " << maybe(current_recipe_name()) << "missing [" << expected_lines.at(curr_expected_line).contents << "] "
<< "in trace with label '" << expected_lines.at(curr_expected_line).label << "'\n" << end();
if (!Hide_errors)
DUMP(expected_lines.at(curr_expected_line).label);
if (!Scenario_testing_scenario) Passed = false;
2015-04-21 05:20:39 +00:00
}
2015-05-22 01:10:17 +00:00
vector<trace_line> parse_trace(const string& expected) {
vector<string> buf = split(expected, "\n");
2015-05-22 01:10:17 +00:00
vector<trace_line> result;
2016-10-20 05:10:35 +00:00
for (int i = 0; i < SIZE(buf); ++i) {
buf.at(i) = trim(buf.at(i));
if (buf.at(i).empty()) continue;
int delim = buf.at(i).find(": ");
2016-09-27 21:45:51 +00:00
if (delim == -1) {
raise << maybe(current_recipe_name()) << "lines in 'trace-should-contain' should be of the form <label>: <contents>. Both parts are required.\n" << end();
2016-09-27 21:45:51 +00:00
result.clear();
return result;
}
2015-05-22 01:10:17 +00:00
result.push_back(trace_line(trim(buf.at(i).substr(0, delim)), trim(buf.at(i).substr(delim+2))));
}
return result;
}
:(scenario trace_check_fails_in_nonfirst_line)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
run [
trace 1, [a], [b]
]
trace-should-contain [
a: b
a: d
]
]
+error: F - main: missing [d] in trace with label 'a'
:(scenario trace_check_passes_silently)
% Scenario_testing_scenario = true;
def main [
run [
trace 1, [a], [b]
]
trace-should-contain [
a: b
]
]
2016-07-20 20:06:28 +00:00
-error: missing [b] in trace with label 'a'
$error: 0
//: 'trace-should-not-contain' is like the '-' lines in our scenarios so far
//: Each trace line is separately checked for absense. Order is *not*
//: important, so you can't say things like "B should not exist after A."
:(scenario trace_negative_check_fails)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
run [
trace 1, [a], [b]
]
trace-should-not-contain [
a: b
]
]
+error: F - main: unexpected [b] in trace with label 'a'
:(before "End Primitive Recipe Declarations")
TRACE_SHOULD_NOT_CONTAIN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "trace-should-not-contain", TRACE_SHOULD_NOT_CONTAIN);
2015-10-02 00:30:14 +00:00
:(before "End Primitive Recipe Checks")
case TRACE_SHOULD_NOT_CONTAIN: {
break;
}
:(before "End Primitive Recipe Implementations")
case TRACE_SHOULD_NOT_CONTAIN: {
2015-05-28 01:32:07 +00:00
if (!Passed) break;
check_trace_missing(current_instruction().ingredients.at(0).name);
break;
}
:(code)
// simplified version of check_trace_contents() that emits errors rather
// than just printing to stderr
bool check_trace_missing(const string& in) {
Trace_stream->newline();
2015-05-22 01:10:17 +00:00
vector<trace_line> lines = parse_trace(in);
2016-10-20 05:10:35 +00:00
for (int i = 0; i < SIZE(lines); ++i) {
2015-05-22 01:10:17 +00:00
if (trace_count(lines.at(i).label, lines.at(i).contents) != 0) {
raise << "F - " << maybe(current_recipe_name()) << "unexpected [" << lines.at(i).contents << "] in trace with label '" << lines.at(i).label << "'\n" << end();
if (!Scenario_testing_scenario) Passed = false;
return false;
}
}
return true;
}
:(scenario trace_negative_check_passes_silently)
% Scenario_testing_scenario = true;
def main [
trace-should-not-contain [
a: b
]
]
2016-07-20 20:06:28 +00:00
-error: unexpected [b] in trace with label 'a'
$error: 0
:(scenario trace_negative_check_fails_on_any_unexpected_line)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
run [
trace 1, [a], [d]
]
trace-should-not-contain [
a: b
a: d
]
]
+error: F - main: unexpected [d] in trace with label 'a'
:(scenario trace_count_check)
def main [
run [
trace 1, [a], [foo]
]
check-trace-count-for-label 1, [a]
]
2016-07-11 04:47:24 +00:00
# checks are inside scenario
:(before "End Primitive Recipe Declarations")
CHECK_TRACE_COUNT_FOR_LABEL,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "check-trace-count-for-label", CHECK_TRACE_COUNT_FOR_LABEL);
2015-10-02 00:30:14 +00:00
:(before "End Primitive Recipe Checks")
case CHECK_TRACE_COUNT_FOR_LABEL: {
2015-10-02 00:30:14 +00:00
if (SIZE(inst.ingredients) != 2) {
2017-05-26 23:43:18 +00:00
raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
break;
}
if (!is_mu_number(inst.ingredients.at(0))) {
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();
break;
}
2016-09-17 06:52:15 +00:00
if (!is_literal_text(inst.ingredients.at(1))) {
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();
break;
}
2015-10-02 00:30:14 +00:00
break;
}
:(before "End Primitive Recipe Implementations")
case CHECK_TRACE_COUNT_FOR_LABEL: {
if (!Passed) break;
int expected_count = ingredients.at(0).at(0);
string label = current_instruction().ingredients.at(1).name;
int count = trace_count(label);
if (count != expected_count) {
if (!Hide_errors) cerr << '\n';
raise << "F - " << maybe(current_recipe_name()) << "expected " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end();
2017-06-16 00:11:08 +00:00
if (!Hide_errors) DUMP(label);
2016-07-22 21:13:25 +00:00
if (!Scenario_testing_scenario) Passed = false;
}
break;
}
:(before "End Primitive Recipe Declarations")
CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "check-trace-count-for-label-greater-than", CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN);
:(before "End Primitive Recipe Checks")
case CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN: {
if (SIZE(inst.ingredients) != 2) {
raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
break;
}
if (!is_mu_number(inst.ingredients.at(0))) {
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();
break;
}
if (!is_literal_text(inst.ingredients.at(1))) {
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();
break;
}
break;
}
:(before "End Primitive Recipe Implementations")
case CHECK_TRACE_COUNT_FOR_LABEL_GREATER_THAN: {
if (!Passed) break;
int expected_count = ingredients.at(0).at(0);
string label = current_instruction().ingredients.at(1).name;
int count = trace_count(label);
if (count <= expected_count) {
if (!Hide_errors) cerr << '\n';
raise << maybe(current_recipe_name()) << "expected more than " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end();
if (!Hide_errors) {
cerr << "trace contents:\n";
DUMP(label);
}
if (!Scenario_testing_scenario) Passed = false;
}
break;
}
:(before "End Primitive Recipe Declarations")
CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN,
:(before "End Primitive Recipe Numbers")
put(Recipe_ordinal, "check-trace-count-for-label-lesser-than", CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN);
:(before "End Primitive Recipe Checks")
case CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN: {
if (SIZE(inst.ingredients) != 2) {
raise << maybe(get(Recipe, r).name) << "'check-trace-count-for-label' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
break;
}
if (!is_mu_number(inst.ingredients.at(0))) {
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();
break;
}
if (!is_literal_text(inst.ingredients.at(1))) {
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();
break;
}
break;
}
:(before "End Primitive Recipe Implementations")
case CHECK_TRACE_COUNT_FOR_LABEL_LESSER_THAN: {
if (!Passed) break;
int expected_count = ingredients.at(0).at(0);
string label = current_instruction().ingredients.at(1).name;
int count = trace_count(label);
if (count >= expected_count) {
if (!Hide_errors) cerr << '\n';
raise << "F - " << maybe(current_recipe_name()) << "expected less than " << expected_count << " lines in trace with label '" << label << "' in trace\n" << end();
if (!Hide_errors) {
cerr << "trace contents:\n";
DUMP(label);
}
if (!Scenario_testing_scenario) Passed = false;
}
break;
}
:(scenario trace_count_check_2)
% Scenario_testing_scenario = true;
% Hide_errors = true;
def main [
run [
trace 1, [a], [foo]
]
check-trace-count-for-label 2, [a]
]
+error: F - main: expected 2 lines in trace with label 'a' in trace
2015-07-28 22:22:58 +00:00
//: Minor detail: ignore 'system' calls in scenarios, since anything we do
2016-10-22 23:56:07 +00:00
//: with them is by definition impossible to test through Mu.
2015-07-28 22:22:58 +00:00
:(after "case _SYSTEM:")
if (Current_scenario) break;
2017-11-04 01:01:59 +00:00
//:: Warn if people use '_' manually in recipe names. They're reserved for internal use.
2016-05-27 16:50:39 +00:00
:(scenario recipe_name_with_underscore)
% Hide_errors = true;
def foo_bar [
]
+error: foo_bar: don't create recipes with '_' in the name
:(before "End recipe Fields")
bool is_autogenerated;
:(before "End recipe Constructor")
is_autogenerated = false;
:(code)
void mark_autogenerated(recipe_ordinal r) {
get(Recipe, r).is_autogenerated = true;
}
:(after "void transform_all()")
2016-10-20 05:10:35 +00:00
for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
2016-05-27 16:50:39 +00:00
const recipe& r = p->second;
if (r.name.find('_') == string::npos) continue;
if (r.is_autogenerated) continue; // created by previous call to transform_all()
raise << r.name << ": don't create recipes with '_' in the name\n" << end();
}
//:: Helpers
:(code)
// just for the scenarios running scenarios in C++ layers
void run_mu_scenario(const string& form) {
Scenario_names.clear();
istringstream in(form);
in >> std::noskipws;
skip_whitespace_and_comments(in);
string _scenario = next_word(in);
if (_scenario.empty()) {
assert(!has_data(in));
raise << "no scenario in string passed into run_mu_scenario()\n" << end();
return;
}
assert(_scenario == "scenario");
scenario s = parse_scenario(in);
run_mu_scenario(s);
}