about summary refs log tree commit diff stats
path: root/050scenario.cc
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2015-05-05 21:17:24 -0700
committerKartik K. Agaram <vc@akkartik.com>2015-05-05 21:17:24 -0700
commitb96af395b9af2ff9df94b3e82213171f30827c8d (patch)
tree17c8c12648ccc25625e2534ec8d74fbe8f1542cc /050scenario.cc
parent2e3b597fe85b654e82b891c22d50754fa5a26156 (diff)
downloadmu-b96af395b9af2ff9df94b3e82213171f30827c8d.tar.gz
1276 - make C++ version the default
I've tried to update the Readme, but there are at least a couple of issues.
Diffstat (limited to '050scenario.cc')
-rw-r--r--050scenario.cc477
1 files changed, 477 insertions, 0 deletions
diff --git a/050scenario.cc b/050scenario.cc
new file mode 100644
index 00000000..07279d39
--- /dev/null
+++ b/050scenario.cc
@@ -0,0 +1,477 @@
+//: Mu scenarios. This will get long, but these are the tests we want to
+//: support in this layer.
+
+//: You can use variable names in scenarios, but for the most part we'll use
+//: raw location numbers, because that lets us make assertions on memory.
+//: Tests should avoid abstraction as far as possible.
+:(scenarios run_mu_scenario)
+:(scenario scenario_block)
+scenario foo [
+  run [
+    1:integer <- copy 13:literal
+  ]
+  memory-should-contain [
+    1 <- 13
+  ]
+]
+# checks are inside scenario
+
+:(scenario scenario_multiple_blocks)
+scenario foo [
+  run [
+    1:integer <- copy 13:literal
+  ]
+  memory-should-contain [
+    1 <- 13
+  ]
+  run [
+    2:integer <- copy 13:literal
+  ]
+  memory-should-contain [
+    1 <- 13
+    2 <- 13
+  ]
+]
+
+:(scenario scenario_check_memory_and_trace)
+scenario foo [
+  run [
+    1:integer <- copy 13:literal
+    trace [a], [a b c]
+  ]
+  memory-should-contain [
+    1 <- 13
+  ]
+  trace-should-contain [
+    a: a b c
+  ]
+  trace-should-not-contain [
+    a: x y z
+  ]
+]
+
+//:: 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);
+  skip_bracket(in, "'scenario' must begin with '['");
+  ostringstream buffer;
+  slurp_until_matching_bracket(in, buffer);
+  result.to_run = buffer.str();
+  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 << ".";
+}
+
+:(before "End Globals")
+const scenario* Current_scenario = NULL;
+:(code)
+void run_mu_scenario(const scenario& s) {
+  Current_scenario = &s;
+  bool not_already_inside_test = !Trace_stream;
+  if (not_already_inside_test) {
+    Trace_file = s.name;
+    Trace_stream = new trace_stream;
+    setup();
+  }
+  run("recipe "+s.name+" [ " + s.to_run + " ]");
+  if (not_already_inside_test) {
+    teardown();
+    ofstream fout((Trace_dir+Trace_file).c_str());
+    fout << Trace_stream->readable_contents("");
+    fout.close();
+    delete Trace_stream;
+    Trace_stream = NULL;
+    Trace_file = "";
+  }
+  Current_scenario = NULL;
+}
+
+//:: 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 In Run.
+  // End Predefined Scenario Locals In Run.
+  transform_all();
+//?   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()
+}
+
+:(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) {
+      if (Current_scenario)
+        raise << "\nF - " << Current_scenario->name << ": expected location " << address << " to contain " << value << " but saw " << Memory[address] << '\n';
+      else
+        raise << "expected location " << address << " to contain " << value << " but saw " << Memory[address] << '\n';
+      Passed = false;
+      return;
+    }
+    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;
+  while (in >> c) {
+    if (c == '[') ++brace_depth;
+    if (c == ']') --brace_depth;
+    if (brace_depth == 0) break;  // drop final ']'
+    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);
+}