about summary refs log tree commit diff stats
path: root/cpp/050scenario.cc
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-05-04 11:56:16 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-05-04 11:56:16 -0700
commit255cc28813b9f53dd678c78551e3adb036002553 (patch)
tree0f4907058f703b4252abee2087f878d8477ec3ac /cpp/050scenario.cc
parent1fd41772092d1c9031c2921f47419bf2de31a528 (diff)
downloadmu-255cc28813b9f53dd678c78551e3adb036002553.tar.gz
1251 - cleanup flow of 'scenario' implementation
Diffstat (limited to 'cpp/050scenario.cc')
-rw-r--r--cpp/050scenario.cc406
1 files changed, 375 insertions, 31 deletions
diff --git a/cpp/050scenario.cc b/cpp/050scenario.cc
index b1baa1a5..afea6ff6 100644
--- a/cpp/050scenario.cc
+++ b/cpp/050scenario.cc
@@ -1,16 +1,5 @@
-//: Support a scenario [ ... ] form at the top level so we can start creating
-//: scenarios in mu files just like we do in C++.
-
-:(before "End Types")
-struct scenario {
-  string name;
-  string to_run;
-};
-
-:(before "End Globals")
-vector<scenario> Scenarios;
-
-//:: How we check Scenarios.
+//: Mu scenarios. This will get long, but these are the tests we want to
+//: support in this layer.
 
 :(scenarios run_mu_scenario)
 :(scenario scenario_block)
@@ -58,16 +47,26 @@ scenario foo [
   ]
 ]
 
-:(code)
-// just for tests
-void run_mu_scenario(const string& form) {
-  istringstream in(form);
-  in >> std::noskipws;
-  assert(next_word(in) == "scenario");
-  scenario s = parse_scenario(in);
-  run_mu_scenario(s);
+//:: Core data structure
+
+:(before "End Types")
+struct scenario {
+  string name;
+  string to_run;
+};
+
+:(before "End Globals")
+vector<scenario> Scenarios;
+
+//:: Parse the 'scenario' form.
+//: Simply store the text of the scenario.
+
+:(before "End Command Handlers")
+else if (command == "scenario") {
+  Scenarios.push_back(parse_scenario(in));
 }
 
+:(code)
 scenario parse_scenario(istream& in) {
   scenario result;
   result.name = next_word(in);
@@ -78,6 +77,19 @@ scenario parse_scenario(istream& in) {
   return result;
 }
 
+//:: Run scenarios when we run 'mu test'.
+//: Treat the text of the scenario as a regular series of instructions.
+
+:(before "End Tests")
+time_t mu_time; time(&mu_time);
+cerr << "\nMu tests: " << ctime(&mu_time);
+for (index_t i = 0; i < Scenarios.size(); ++i) {
+//?   cerr << Passed << '\n'; //? 1
+  run_mu_scenario(Scenarios[i]);
+  if (Passed) cerr << ".";
+}
+
+:(code)
 void run_mu_scenario(const scenario& s) {
   bool not_already_inside_test = !Trace_stream;
   if (not_already_inside_test) {
@@ -97,23 +109,341 @@ void run_mu_scenario(const scenario& s) {
   }
 }
 
-:(before "End Command Handlers")
-else if (command == "scenario") {
-  Scenarios.push_back(parse_scenario(in));
+//:: The special instructions we want to support inside scenarios.
+//: In a compiler for the mu VM these will require more work.
+
+//: 'run' interprets a string as a set of instructions
+
+:(scenarios run)
+:(scenario run)
+#? % Trace_stream->dump_layer = "all";
+recipe main [
+  run [
+    1:integer <- copy 13:literal
+  ]
+]
++mem: storing 13 in location 1
+
+:(before "End Primitive Recipe Declarations")
+RUN,
+:(before "End Primitive Recipe Numbers")
+Recipe_number["run"] = RUN;
+:(before "End Primitive Recipe Implementations")
+case RUN: {
+//?   cout << "recipe " << current_instruction().ingredients[0].name << '\n'; //? 1
+  ostringstream tmp;
+  tmp << "recipe run" << Next_recipe_number << " [ " << current_instruction().ingredients[0].name << " ]";
+//?   Show_rest_of_stream = true; //? 1
+  vector<recipe_number> tmp_recipe = load(tmp.str());
+  // Predefined Scenario Locals
+//?   cout << "mapping local screen in recipe " << tmp_recipe[0] << '\n'; //? 1
+  Name[tmp_recipe[0]]["screen"] = Reserved_for_tests-1;
+  // End Predefined Scenario Locals
+  transform_all();
+  // There's a restriction on the number of variables 'run' can use, so that
+  // it can avoid colliding with the dynamic allocator in case it doesn't
+  // initialize a default-space.
+  assert(Name[tmp_recipe[0]][""] < Reserved_for_tests-1);
+//?   cout << tmp_recipe[0] << ' ' << Recipe_number["main"] << '\n'; //? 1
+  Current_routine->calls.push(call(tmp_recipe[0]));
+  continue;  // not done with caller; don't increment current_step_index()
 }
 
-:(before "End Tests")
-time_t mu_time; time(&mu_time);
-cerr << "\nMu tests: " << ctime(&mu_time);
-for (index_t i = 0; i < Scenarios.size(); ++i) {
-//?   cerr << Passed << '\n'; //? 1
-  run_mu_scenario(Scenarios[i]);
-  if (Passed) cerr << ".";
+:(scenario run_multiple)
+recipe main [
+  run [
+    1:integer <- copy 13:literal
+  ]
+  run [
+    2:integer <- copy 13:literal
+  ]
+]
++mem: storing 13 in location 1
++mem: storing 13 in location 2
+
+//: 'memory-should-contain' raises warnings if specific locations aren't as expected
+//: Also includes some special support for checking strings.
+
+:(scenario memory_check)
+% Hide_warnings = true;
+recipe main [
+  memory-should-contain [
+    1 <- 13
+  ]
+]
++run: checking location 1
++warn: expected location 1 to contain 13 but saw 0
+
+:(before "End Primitive Recipe Declarations")
+MEMORY_SHOULD_CONTAIN,
+:(before "End Primitive Recipe Numbers")
+Recipe_number["memory-should-contain"] = MEMORY_SHOULD_CONTAIN;
+:(before "End Primitive Recipe Implementations")
+case MEMORY_SHOULD_CONTAIN: {
+//?   cout << current_instruction().ingredients[0].name << '\n'; //? 1
+  check_memory(current_instruction().ingredients[0].name);
+  break;
+}
+
+:(code)
+void check_memory(const string& s) {
+  istringstream in(s);
+  in >> std::noskipws;
+  set<index_t> locations_checked;
+  while (true) {
+    skip_whitespace_and_comments(in);
+    if (in.eof()) break;
+    string lhs = next_word(in);
+    if (!is_number(lhs)) {
+      check_type(lhs, in);
+      continue;
+    }
+    int address = to_int(lhs);
+    skip_whitespace_and_comments(in);
+    string _assign;  in >> _assign;  assert(_assign == "<-");
+    skip_whitespace_and_comments(in);
+    int value = 0;  in >> value;
+    if (locations_checked.find(address) != locations_checked.end())
+      raise << "duplicate expectation for location " << address << '\n';
+    trace("run") << "checking location " << address;
+    if (Memory[address] != value)
+      raise << "expected location " << address << " to contain " << value << " but saw " << Memory[address] << '\n';
+    locations_checked.insert(address);
+  }
+}
+
+void check_type(const string& lhs, istream& in) {
+  reagent x(lhs);
+  if (x.properties[0].second[0] == "string") {
+    x.set_value(to_int(x.name));
+    skip_whitespace_and_comments(in);
+    string _assign = next_word(in);
+    assert(_assign == "<-");
+    skip_whitespace_and_comments(in);
+    string literal = next_word(in);
+    index_t address = x.value;
+    // exclude quoting brackets
+    assert(literal[0] == '[');  literal.erase(0, 1);
+    assert(literal[literal.size()-1] == ']');  literal.erase(literal.size()-1);
+    check_string(address, literal);
+    return;
+  }
+  raise << "don't know how to check memory for " << lhs << '\n';
+}
+
+void check_string(index_t address, const string& literal) {
+  trace("run") << "checking string length at " << address;
+  if (Memory[address] != static_cast<signed>(literal.size()))
+    raise << "expected location " << address << " to contain length " << literal.size() << " of string [" << literal << "] but saw " << Memory[address] << '\n';
+  ++address;  // now skip length
+  for (index_t i = 0; i < literal.size(); ++i) {
+    trace("run") << "checking location " << address+i;
+    if (Memory[address+i] != literal[i])
+      raise << "expected location " << (address+i) << " to contain " << literal[i] << " but saw " << Memory[address+i] << '\n';
+  }
+}
+
+:(scenario memory_check_multiple)
+% Hide_warnings = true;
+recipe main [
+  memory-should-contain [
+    1 <- 0
+    1 <- 0
+  ]
+]
++warn: duplicate expectation for location 1
+
+:(scenario memory_check_string_length)
+% Hide_warnings = true;
+recipe main [
+  1:integer <- copy 3:literal
+  2:integer <- copy 97:literal  # 'a'
+  3:integer <- copy 98:literal  # 'b'
+  4:integer <- copy 99:literal  # 'c'
+  memory-should-contain [
+    1:string <- [ab]
+  ]
+]
++warn: expected location 1 to contain length 2 of string [ab] but saw 3
+
+:(scenario memory_check_string)
+recipe main [
+  1:integer <- copy 3:literal
+  2:integer <- copy 97:literal  # 'a'
+  3:integer <- copy 98:literal  # 'b'
+  4:integer <- copy 99:literal  # 'c'
+  memory-should-contain [
+    1:string <- [abc]
+  ]
+]
++run: checking string length at 1
++run: checking location 2
++run: checking location 3
++run: checking location 4
+
+:(code)
+//: '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_warns_on_failure)
+% Hide_warnings = true;
+recipe main [
+  trace-should-contain [
+    a: b
+    a: d
+  ]
+]
++warn: missing [b] in trace layer a
+
+:(before "End Primitive Recipe Declarations")
+TRACE_SHOULD_CONTAIN,
+:(before "End Primitive Recipe Numbers")
+Recipe_number["trace-should-contain"] = TRACE_SHOULD_CONTAIN;
+:(before "End Primitive Recipe Implementations")
+case TRACE_SHOULD_CONTAIN: {
+  check_trace(current_instruction().ingredients[0].name);
+  break;
+}
+
+:(code)
+// simplified version of check_trace_contents() that emits warnings rather
+// than just printing to stderr
+bool check_trace(const string& expected) {
+  Trace_stream->newline();
+  vector<pair<string, string> > expected_lines = parse_trace(expected);
+  if (expected_lines.empty()) return true;
+  index_t curr_expected_line = 0;
+  for (vector<pair<string, pair<int, string> > >::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) {
+    if (expected_lines[curr_expected_line].first != p->first) continue;
+    if (expected_lines[curr_expected_line].second != p->second.second) continue;
+    // match
+    ++curr_expected_line;
+    if (curr_expected_line == expected_lines.size()) return true;
+  }
+
+  raise << "missing [" << expected_lines[curr_expected_line].second << "] "
+        << "in trace layer " << expected_lines[curr_expected_line].first << '\n';
+  Passed = false;
+  return false;
 }
 
+vector<pair<string, string> > parse_trace(const string& expected) {
+  vector<string> buf = split(expected, "\n");
+  vector<pair<string, string> > result;
+  for (index_t i = 0; i < buf.size(); ++i) {
+    buf[i] = trim(buf[i]);
+    if (buf[i].empty()) continue;
+    index_t delim = buf[i].find(": ");
+    result.push_back(pair<string, string>(buf[i].substr(0, delim), buf[i].substr(delim+2)));
+  }
+  return result;
+}
+
+:(scenario trace_check_warns_on_failure_in_later_line)
+% Hide_warnings = true;
+recipe main [
+  run [
+    trace [a], [b]
+  ]
+  trace-should-contain [
+    a: b
+    a: d
+  ]
+]
++warn: missing [d] in trace layer a
+
+:(scenario trace_check_passes_silently)
+% Hide_warnings = true;
+recipe main [
+  run [
+    trace [a], [b]
+  ]
+  trace-should-contain [
+    a: b
+  ]
+]
+-warn: missing [b] in trace layer a
+
+//: '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_warns_on_failure)
+% Hide_warnings = true;
+recipe main [
+  run [
+    trace [a], [b]
+  ]
+  trace-should-not-contain [
+    a: b
+  ]
+]
++warn: unexpected [b] in trace layer a
+
+:(before "End Primitive Recipe Declarations")
+TRACE_SHOULD_NOT_CONTAIN,
+:(before "End Primitive Recipe Numbers")
+Recipe_number["trace-should-not-contain"] = TRACE_SHOULD_NOT_CONTAIN;
+:(before "End Primitive Recipe Implementations")
+case TRACE_SHOULD_NOT_CONTAIN: {
+  check_trace_missing(current_instruction().ingredients[0].name);
+  break;
+}
+
+:(code)
+// simplified version of check_trace_contents() that emits warnings rather
+// than just printing to stderr
+bool check_trace_missing(const string& in) {
+  Trace_stream->newline();
+  vector<pair<string, string> > lines = parse_trace(in);
+  for (index_t i = 0; i < lines.size(); ++i) {
+    if (trace_count(lines[i].first, lines[i].second) != 0) {
+      raise << "unexpected [" << lines[i].second << "] in trace layer " << lines[i].first << '\n';
+      Passed = false;
+      return false;
+    }
+  }
+  return true;
+}
+
+:(scenario trace_negative_check_passes_silently)
+% Hide_warnings = true;
+recipe main [
+  trace-should-not-contain [
+    a: b
+  ]
+]
+-warn: unexpected [b] in trace layer a
+
+:(scenario trace_negative_check_warns_on_any_unexpected_line)
+% Hide_warnings = true;
+recipe main [
+  run [
+    trace [a], [d]
+  ]
+  trace-should-not-contain [
+    a: b
+    a: d
+  ]
+]
++warn: unexpected [d] in trace layer a
+
 //:: Helpers
 
 :(code)
+// just for the scenarios running scenarios in C++ layers
+void run_mu_scenario(const string& form) {
+  istringstream in(form);
+  in >> std::noskipws;
+  string _scenario = next_word(in);
+//?   cout << _scenario << '\n'; //? 1
+  assert(_scenario == "scenario");
+  scenario s = parse_scenario(in);
+  run_mu_scenario(s);
+}
+
 void slurp_until_matching_bracket(istream& in, ostream& out) {
   int brace_depth = 1;  // just scanned '['
   char c;
@@ -124,3 +454,17 @@ void slurp_until_matching_bracket(istream& in, ostream& out) {
     out << c;
   }
 }
+
+// see tests for this function in tangle/030tangle.test.cc
+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);
+}