about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2016-03-20 00:48:04 -0700
committerKartik K. Agaram <vc@akkartik.com>2016-03-20 00:55:33 -0700
commit2429c65cce16a11841212a71ec2bb50373aa54d4 (patch)
tree97c5f2fff44f80ea1a02d0476b9e31b5d44d1d50
parent3ac523393d15d51b0fd1b042eab4f7cb539f68a9 (diff)
downloadmu-2429c65cce16a11841212a71ec2bb50373aa54d4.tar.gz
2799 - new approach to undoing changes in tests
As outlined at the end of 2797. This worked out surprisingly well. Now
the snapshotting code touches fewer layers, and it's much better
behaved, with less need for special-case logic, particularly inside
run_interactive(). 30% slower, but should hopefully not cause any more
bugs.
-rw-r--r--010vm.cc30
-rw-r--r--011load.cc20
-rw-r--r--012transform.cc7
-rw-r--r--020run.cc19
-rw-r--r--030container.cc33
-rw-r--r--037new.cc1
-rw-r--r--042name.cc13
-rw-r--r--056static_dispatch.cc14
-rw-r--r--058shape_shifting_recipe.cc19
-rw-r--r--091run_interactive.cc123
10 files changed, 92 insertions, 187 deletions
diff --git a/010vm.cc b/010vm.cc
index 56148f2a..1120280c 100644
--- a/010vm.cc
+++ b/010vm.cc
@@ -208,6 +208,36 @@ assert(Next_recipe_ordinal < 1000);  // recipes being tested didn't overflow int
 :(before "End Setup")
 Next_recipe_ordinal = 1000;  // consistent new numbers for each test
 
+//: One final detail: tests can modify our global tables of recipes and types,
+//: so we need some way to clean up after each test is done so it doesn't
+//: influence later ones.
+:(before "End Globals")
+map<string, recipe_ordinal> Recipe_ordinal_snapshot;
+map<recipe_ordinal, recipe> Recipe_snapshot;
+map<string, type_ordinal> Type_ordinal_snapshot;
+map<type_ordinal, type_info> Type_snapshot;
+:(before "End One-time Setup")
+save_snapshots();
+:(before "End Setup")
+restore_snapshots();
+
+:(code)
+void save_snapshots() {
+  Recipe_ordinal_snapshot = Recipe_ordinal;
+  Recipe_snapshot = Recipe;
+  Type_ordinal_snapshot = Type_ordinal;
+  Type_snapshot = Type;
+  // End save_snapshots
+}
+
+void restore_snapshots() {
+  Recipe = Recipe_snapshot;
+  Recipe_ordinal = Recipe_ordinal_snapshot;
+  Type_ordinal = Type_ordinal_snapshot;
+  Type = Type_snapshot;
+  // End restore_snapshots
+}
+
 
 
 //:: Helpers
diff --git a/011load.cc b/011load.cc
index ee8df684..d71a67b5 100644
--- a/011load.cc
+++ b/011load.cc
@@ -60,8 +60,6 @@ int slurp_recipe(istream& in) {
   slurp_body(in, result);
   // End Recipe Body(result)
   put(Recipe, get(Recipe_ordinal, result.name), result);
-  // track added recipes because we may need to undo them in tests; see below
-  Recently_added_recipes.push_back(get(Recipe_ordinal, result.name));
   return get(Recipe_ordinal, result.name);
 }
 
@@ -228,24 +226,6 @@ void show_rest_of_stream(istream& in) {
   exit(0);
 }
 
-//: Have tests clean up any recipes they added.
-:(before "End Globals")
-vector<recipe_ordinal> Recently_added_recipes;
-int Reserved_for_tests = 1000;
-:(before "End Setup")
-clear_recently_added_recipes();
-:(code)
-void clear_recently_added_recipes() {
-  for (int i = 0; i < SIZE(Recently_added_recipes); ++i) {
-    if (Recently_added_recipes.at(i) >= Reserved_for_tests  // don't renumber existing recipes, like 'interactive'
-        && contains_key(Recipe, Recently_added_recipes.at(i)))  // in case previous test had duplicate definitions
-      Recipe_ordinal.erase(get(Recipe, Recently_added_recipes.at(i)).name);
-    Recipe.erase(Recently_added_recipes.at(i));
-  }
-  // Clear Other State For Recently_added_recipes
-  Recently_added_recipes.clear();
-}
-
 :(scenario parse_comment_outside_recipe)
 # this comment will be dropped by the tangler, so we need a dummy recipe to stop that
 def f1 [
diff --git a/012transform.cc b/012transform.cc
index 0130cd16..a416c20a 100644
--- a/012transform.cc
+++ b/012transform.cc
@@ -56,13 +56,6 @@ void transform_all() {
   // End Transform All
 }
 
-// Later we'll have transforms create recipes out of other recipes. This
-// helper will help ensure we don't lose the new recipes.
-void transform_permanently() {
-  transform_all();
-  Recently_added_recipes.clear();
-}
-
 void parse_int_reagents() {
   trace(9991, "transform") << "--- parsing any uninitialized reagents as integers" << end();
   for (map<recipe_ordinal, recipe>::iterator p = Recipe.begin(); p != Recipe.end(); ++p) {
diff --git a/020run.cc b/020run.cc
index 41154f06..f3891cf6 100644
--- a/020run.cc
+++ b/020run.cc
@@ -136,8 +136,7 @@ inline const vector<instruction>& routine::steps() const {
 // Load .mu Core
 //? Trace_file = "interactive";
 //? START_TRACING_UNTIL_END_OF_SCOPE;
-load_permanently("core.mu");
-transform_permanently();
+load_file_or_directory("core.mu");
 //? DUMP("");
 //? exit(0);
 
@@ -152,14 +151,15 @@ if (argc > 1) {
   // ignore argv past '--'; that's commandline args for 'main'
   while (argc > 0) {
     if (string(*argv) == "--") break;
-    load_permanently(*argv);
+    load_file_or_directory(*argv);
     argv++;
     argc--;
   }
-  transform_permanently();
   if (Run_tests) Recipe.erase(get(Recipe_ordinal, "main"));
   // End Loading .mu Files
 }
+transform_all();
+save_snapshots();
 
 //: Step 3: if we aren't running tests, locate a recipe called 'main' and
 //: start running it.
@@ -209,9 +209,9 @@ void cleanup_main() {
 atexit(cleanup_main);
 
 :(code)
-void load_permanently(string filename) {
+void load_file_or_directory(string filename) {
   if (is_directory(filename)) {
-    load_all_permanently(filename);
+    load_all(filename);
     return;
   }
   ifstream fin(filename.c_str());
@@ -222,9 +222,6 @@ void load_permanently(string filename) {
   trace(9990, "load") << "=== " << filename << end();
   load(fin);
   fin.close();
-  // freeze everything so it doesn't get cleared by tests
-  Recently_added_recipes.clear();
-  // End load_permanently.
 }
 
 bool is_directory(string path) {
@@ -233,13 +230,13 @@ bool is_directory(string path) {
   return info.st_mode & S_IFDIR;
 }
 
-void load_all_permanently(string dir) {
+void load_all(string dir) {
   dirent** files;
   int num_files = scandir(dir.c_str(), &files, NULL, alphasort);
   for (int i = 0; i < num_files; ++i) {
     string curr_file = files[i]->d_name;
     if (isdigit(curr_file.at(0)))
-      load_permanently(dir+'/'+curr_file);
+      load_file_or_directory(dir+'/'+curr_file);
     free(files[i]);
     files[i] = NULL;
   }
diff --git a/030container.cc b/030container.cc
index 4cc0d58e..a29f7171 100644
--- a/030container.cc
+++ b/030container.cc
@@ -416,7 +416,6 @@ void insert_container(const string& command, kind_of_type kind, istream& in) {
   trace(9999, "parse") << "type number: " << get(Type_ordinal, name) << end();
   skip_bracket(in, "'container' must begin with '['");
   type_info& info = get_or_insert(Type, get(Type_ordinal, name));
-  Recently_added_types.push_back(get(Type_ordinal, name));
   info.name = name;
   info.kind = kind;
   while (has_data(in)) {
@@ -475,40 +474,12 @@ def main [
 +mem: storing 34 in location 3
 +mem: storing 35 in location 4
 
-//: ensure types created in one scenario don't leak outside it.
-:(before "End Globals")
-vector<type_ordinal> Recently_added_types;
-:(before "End load_permanently")  //: for non-tests
-Recently_added_types.clear();
+//: ensure scenarios are consistent by always starting them at the same type
+//: number.
 :(before "End Setup")  //: for tests
-for (int i = 0; i < SIZE(Recently_added_types); ++i) {
-  if (!contains_key(Type, Recently_added_types.at(i))) continue;
-  Type_ordinal.erase(get(Type, Recently_added_types.at(i)).name);
-  // todo: why do I explicitly need to provide this?
-  for (int j = 0; j < SIZE(Type.at(Recently_added_types.at(i)).elements); ++j)
-    Type.at(Recently_added_types.at(i)).elements.at(j).clear();
-  Type.erase(Recently_added_types.at(i));
-}
-Recently_added_types.clear();
-// delete recent type references
-// can't rely on Recently_added_types to cleanup Type_ordinal, because of deliberately misbehaving tests with references to undefined types
-map<string, type_ordinal>::iterator p = Type_ordinal.begin();
-while(p != Type_ordinal.end()) {
-  // save current item
-  string name = p->first;
-  type_ordinal t = p->second;
-  // increment iterator
-  ++p;
-  // now delete current item if necessary
-  if (t >= 1000) Type_ordinal.erase(name);
-}
-//: lastly, ensure scenarios are consistent by always starting them at the
-//: same type number.
 Next_type_ordinal = 1000;
 :(before "End Test Run Initialization")
 assert(Next_type_ordinal < 1000);
-:(before "End Setup")
-Next_type_ordinal = 1000;
 
 //:: Allow container definitions anywhere in the codebase, but complain if you
 //:: can't find a definition at the end.
diff --git a/037new.cc b/037new.cc
index d7daa397..feebd58e 100644
--- a/037new.cc
+++ b/037new.cc
@@ -55,6 +55,7 @@ def main [
 +mem: storing 0 in location 3
 
 :(before "End Globals")
+const int Reserved_for_tests = 1000;
 int Memory_allocated_until = Reserved_for_tests;
 int Initial_memory_per_routine = 100000;
 :(before "End Setup")
diff --git a/042name.cc b/042name.cc
index 46e3a816..872b5678 100644
--- a/042name.cc
+++ b/042name.cc
@@ -23,10 +23,15 @@ Transform.push_back(transform_names);  // idempotent
 
 :(before "End Globals")
 map<recipe_ordinal, map<string, int> > Name;
-:(after "Clear Other State For Recently_added_recipes")
-for (int i = 0; i < SIZE(Recently_added_recipes); ++i) {
-  Name.erase(Recently_added_recipes.at(i));
-}
+
+//: the Name map is a global, so save it before tests and reset it for every
+//: test, just to be safe.
+:(before "End Globals")
+map<recipe_ordinal, map<string, int> > Name_snapshot;
+:(before "End save_snapshots")
+Name_snapshot = Name;
+:(before "End restore_snapshots")
+Name = Name_snapshot;
 
 :(code)
 void transform_names(const recipe_ordinal r) {
diff --git a/056static_dispatch.cc b/056static_dispatch.cc
index da97ea80..263dc2bf 100644
--- a/056static_dispatch.cc
+++ b/056static_dispatch.cc
@@ -21,13 +21,13 @@ def test a:number, b:number -> z:number [
 map<string, vector<recipe_ordinal> > Recipe_variants;
 :(before "End One-time Setup")
 put(Recipe_variants, "main", vector<recipe_ordinal>());  // since we manually added main to Recipe_ordinal
-:(before "Clear Other State For Recently_added_recipes")
-for (map<string, vector<recipe_ordinal> >::iterator p = Recipe_variants.begin(); p != Recipe_variants.end(); ++p) {
-  for (int i = 0; i < SIZE(p->second); ++i) {
-    if (find(Recently_added_recipes.begin(), Recently_added_recipes.end(), p->second.at(i)) != Recently_added_recipes.end())
-      p->second.at(i) = -1;  // just leave a ghost
-  }
-}
+
+:(before "End Globals")
+map<string, vector<recipe_ordinal> > Recipe_variants_snapshot;
+:(before "End save_snapshots")
+Recipe_variants_snapshot = Recipe_variants;
+:(before "End restore_snapshots")
+Recipe_variants = Recipe_variants_snapshot;
 
 :(before "End Load Recipe Header(result)")
 // there can only ever be one variant for main
diff --git a/058shape_shifting_recipe.cc b/058shape_shifting_recipe.cc
index 2046c622..f80edb3b 100644
--- a/058shape_shifting_recipe.cc
+++ b/058shape_shifting_recipe.cc
@@ -41,23 +41,6 @@ if (Current_routine->calls.front().running_step_index == 0
 :(before "End Matching Types For Literal(to)")
 if (contains_type_ingredient_name(to)) return false;
 
-//: We'll be creating recipes without loading them from anywhere by
-//: *specializing* existing recipes.
-//:
-//: Keep track of these new recipes in a separate variable in addition to
-//: Recently_added_recipes, so that edit/ can clear them before reloading to
-//: regenerate errors.
-:(before "End Globals")
-vector<recipe_ordinal> Recently_added_shape_shifting_recipes;
-:(before "End Setup")
-Recently_added_shape_shifting_recipes.clear();
-
-//: make sure we don't clear any of these recipes when we start running tests
-:(before "End Loading .mu Files")
-Recently_added_recipes.clear();
-Recently_added_types.clear();
-Recently_added_shape_shifting_recipes.clear();
-
 //: save original name of specialized recipes
 :(before "End recipe Fields")
 string original_name;
@@ -243,8 +226,6 @@ recipe_ordinal new_variant(recipe_ordinal exemplar, const instruction& inst, con
   // make a copy
   assert(contains_key(Recipe, exemplar));
   assert(!contains_key(Recipe, new_recipe_ordinal));
-  Recently_added_recipes.push_back(new_recipe_ordinal);
-  Recently_added_shape_shifting_recipes.push_back(new_recipe_ordinal);
   put(Recipe, new_recipe_ordinal, get(Recipe, exemplar));
   recipe& new_recipe = get(Recipe, new_recipe_ordinal);
   new_recipe.name = new_name;
diff --git a/091run_interactive.cc b/091run_interactive.cc
index 1e6bf9c2..b84d8ca9 100644
--- a/091run_interactive.cc
+++ b/091run_interactive.cc
@@ -62,8 +62,6 @@ bool Track_most_recent_products = false;
 :(before "End Tracing")
 trace_stream* Save_trace_stream = NULL;
 string Save_trace_file;
-vector<recipe_ordinal> Save_recently_added_recipes;
-vector<recipe_ordinal> Save_recently_added_shape_shifting_recipes;
 :(before "End Setup")
 Track_most_recent_products = false;
 :(code)
@@ -79,9 +77,9 @@ bool run_interactive(int address) {
       Memory.erase(i);
   }
   string command = trim(strip_comments(read_mu_string(address)));
-  if (command.empty()) return false;
   Name[get(Recipe_ordinal, "interactive")].clear();
   run_code_begin(/*snapshot_recently_added_recipes*/true);
+  if (command.empty()) return false;
   // don't kill the current routine on parse errors
   routine* save_current_routine = Current_routine;
   Current_routine = NULL;
@@ -108,16 +106,22 @@ bool run_interactive(int address) {
   return true;
 }
 
+//: Carefully update all state to exactly how it was -- including snapshots.
+
+:(before "End Globals")
+map<string, recipe_ordinal> Recipe_ordinal_snapshot_stash;
+map<recipe_ordinal, recipe> Recipe_snapshot_stash;
+map<string, type_ordinal> Type_ordinal_snapshot_stash;
+map<type_ordinal, type_info> Type_snapshot_stash;
+map<recipe_ordinal, map<string, int> > Name_snapshot_stash;
+map<string, vector<recipe_ordinal> > Recipe_variants_snapshot_stash;
+:(code)
 void run_code_begin(bool snapshot_recently_added_recipes) {
   // stuff to undo later, in run_code_end()
   Hide_errors = true;
   Disable_redefine_checks = true;
-  if (snapshot_recently_added_recipes) {
-    Save_recently_added_recipes = Recently_added_recipes;
-    Recently_added_recipes.clear();
-    Save_recently_added_shape_shifting_recipes = Recently_added_shape_shifting_recipes;
-    Recently_added_shape_shifting_recipes.clear();
-  }
+  if (snapshot_recently_added_recipes)
+    stash_snapshots();
   Save_trace_stream = Trace_stream;
   Save_trace_file = Trace_file;
   Trace_file = "";
@@ -134,13 +138,28 @@ void run_code_end() {
   Trace_file = Save_trace_file;
   Save_trace_file.clear();
   Recipe.erase(get(Recipe_ordinal, "interactive"));  // keep past sandboxes from inserting errors
-  if (!Save_recently_added_recipes.empty()) {
-    clear_recently_added_recipes();
-    Recently_added_recipes = Save_recently_added_recipes;
-    Save_recently_added_recipes.clear();
-    Recently_added_shape_shifting_recipes = Save_recently_added_shape_shifting_recipes;
-    Save_recently_added_shape_shifting_recipes.clear();
-  }
+  if (!Recipe_snapshot_stash.empty())
+    unstash_snapshots();
+}
+
+// keep sync'd with save_snapshots and restore_snapshots
+void stash_snapshots() {
+  Recipe_ordinal_snapshot_stash = Recipe_ordinal_snapshot;
+  Recipe_snapshot_stash = Recipe_snapshot;
+  Type_ordinal_snapshot_stash = Type_ordinal_snapshot;
+  Type_snapshot_stash = Type_snapshot;
+  Name_snapshot_stash = Name_snapshot;
+  Recipe_variants_snapshot_stash = Recipe_variants_snapshot;
+  save_snapshots();
+}
+void unstash_snapshots() {
+  restore_snapshots();
+  Recipe_ordinal_snapshot = Recipe_ordinal_snapshot_stash;  Recipe_ordinal_snapshot_stash.clear();
+  Recipe_snapshot = Recipe_snapshot_stash;  Recipe_snapshot_stash.clear();
+  Type_ordinal_snapshot = Type_ordinal_snapshot_stash;  Type_ordinal_snapshot_stash.clear();
+  Type_snapshot = Type_snapshot_stash;  Type_snapshot_stash.clear();
+  Name_snapshot = Name_snapshot_stash;  Name_snapshot_stash.clear();
+  Recipe_variants_snapshot = Recipe_variants_snapshot_stash;  Recipe_variants_snapshot_stash.clear();
 }
 
 :(before "End Load Recipes")
@@ -161,8 +180,6 @@ load(string(
   "$cleanup-run-interactive\n" +
   "return output, errors, screen, stashes, completed?\n" +
 "]\n");
-transform_all();
-Recently_added_recipes.clear();
 
 //: adjust errors in the sandbox
 :(after "string maybe(string s)")
@@ -315,31 +332,6 @@ b:number <- copy 0
 # no errors
 +mem: storing 0 in location 3
 
-:(code)
-void test_run_interactive_cleans_up_any_created_specializations() {
-  // define a generic recipe
-  assert(!contains_key(Recipe_ordinal, "foo"));
-  load("recipe foo x:_elem -> n:number [\n"
-       "  return 34\n"
-       "]\n");
-  assert(SIZE(Recently_added_recipes) == 1);  // foo
-  assert(variant_count("foo") == 1);
-  // run-interactive a call that specializes this recipe
-  run("recipe main [\n"
-       "  1:number/raw <- copy 0\n"
-       "  2:address:shared:array:character <- new [foo 1:number/raw]\n"
-       "  run-interactive 2:address:shared:array:character\n"
-       "]\n");
-  assert(SIZE(Recently_added_recipes) == 2);  // foo, main
-  // check that number of variants doesn't change
-  CHECK_EQ(variant_count("foo"), 1);
-}
-
-int variant_count(string recipe_name) {
-  if (!contains_key(Recipe_variants, recipe_name)) return 0;
-  return non_ghost_size(get(Recipe_variants, recipe_name));
-}
-
 :(before "End Globals")
 string Most_recent_products;
 :(before "End Setup")
@@ -456,25 +448,6 @@ case RELOAD: {
 }
 :(before "End Primitive Recipe Implementations")
 case RELOAD: {
-  // clear any containers in advance
-  for (int i = 0; i < SIZE(Recently_added_types); ++i) {
-    if (!contains_key(Type, Recently_added_types.at(i))) continue;
-    Type_ordinal.erase(get(Type, Recently_added_types.at(i)).name);
-    Type.erase(Recently_added_types.at(i));
-  }
-  for (map<string, vector<recipe_ordinal> >::iterator p = Recipe_variants.begin(); p != Recipe_variants.end(); ++p) {
-    vector<recipe_ordinal>& variants = p->second;
-    for (int i = 0; i < SIZE(p->second); ++i) {
-      if (variants.at(i) == -1) continue;
-      if (find(Recently_added_shape_shifting_recipes.begin(), Recently_added_shape_shifting_recipes.end(), variants.at(i)) != Recently_added_shape_shifting_recipes.end())
-        variants.at(i) = -1;  // ghost
-    }
-  }
-  for (int i = 0; i < SIZE(Recently_added_shape_shifting_recipes); ++i) {
-    Recipe_ordinal.erase(get(Recipe, Recently_added_shape_shifting_recipes.at(i)).name);
-    Recipe.erase(Recently_added_shape_shifting_recipes.at(i));
-  }
-  Recently_added_shape_shifting_recipes.clear();
   string code = read_mu_string(ingredients.at(0).at(0));
   run_code_begin(/*snapshot_recently_added_recipes*/false);
   routine* save_current_routine = Current_routine;
@@ -503,29 +476,3 @@ def main [
   1:number/raw <- copy 34
 ]
 +mem: storing 34 in location 1
-
-:(code)
-void test_reload_cleans_up_any_created_specializations() {
-  // define a generic recipe and a call to it
-  assert(!contains_key(Recipe_ordinal, "foo"));
-  assert(variant_count("foo") == 0);
-  // a call that specializes this recipe
-  run("recipe main [\n"
-      "  local-scope\n"
-      "  x:address:shared:array:character <- new [recipe foo x:_elem -> n:number [\n"
-      "local-scope\n"
-      "load-ingredients\n"
-      "return 34\n"
-      "]\n"
-      "recipe main2 [\n"
-      "local-scope\n"
-      "load-ingredients\n"
-      "x:number <- copy 34\n"
-      "foo x:number\n"
-      "]]\n"
-      "  reload x\n"
-      "]\n");
-  // check that number of variants includes specialization
-  assert(SIZE(Recently_added_recipes) == 4);  // foo, main, main2, foo specialization
-  CHECK_EQ(variant_count("foo"), 2);
-}