summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorhut <hut@lavabit.com>2009-12-13 19:42:51 +0100
committerhut <hut@lavabit.com>2009-12-13 19:42:51 +0100
commit1159f9ec182496ddc5324f23fb1d5eae73fe63e3 (patch)
tree02a79bdd232b7aa5f49e7ee9ec713141a4118358
parent4c05e43d11430fbfd8a5d86ae0070e24775251b1 (diff)
downloadranger-1159f9ec182496ddc5324f23fb1d5eae73fe63e3.tar.gz
updated / added tests
-rw-r--r--ranger/container/bookmarks.py4
-rw-r--r--ranger/gui/defaultui.py6
-rw-r--r--ranger/gui/displayable.py39
-rw-r--r--ranger/gui/ui.py7
-rw-r--r--test/__init__.py21
-rw-r--r--test/tc_directory.py8
-rw-r--r--test/tc_displayable.py122
-rw-r--r--test/tc_ui.py61
-rw-r--r--test/test.py5
9 files changed, 249 insertions, 24 deletions
diff --git a/ranger/container/bookmarks.py b/ranger/container/bookmarks.py
index bf41150c..d57ff4a9 100644
--- a/ranger/container/bookmarks.py
+++ b/ranger/container/bookmarks.py
@@ -1,4 +1,3 @@
-from ranger import log, trace
 import string
 import re
 import os
@@ -112,9 +111,6 @@ Useful if two instances are running which define different bookmarks.
 			else:
 				real = None
 
-			log(key, current, original, real)
-#			trace()
-
 			if current == original and current != real:
 				continue
 
diff --git a/ranger/gui/defaultui.py b/ranger/gui/defaultui.py
index b9758e8d..5b32690d 100644
--- a/ranger/gui/defaultui.py
+++ b/ranger/gui/defaultui.py
@@ -1,8 +1,8 @@
 
 RATIO = ( 3, 3, 12, 9 )
 
-from ranger.gui.ui import UI as SuperClass
-class DefaultUI(SuperClass):
+from ranger.gui.ui import UI
+class DefaultUI(UI):
 	def setup(self):
 		"""Build up the UI by initializing widgets."""
 		from ranger.gui.widgets.filelistcontainer import FileListContainer
@@ -20,7 +20,7 @@ class DefaultUI(SuperClass):
 
 	def update_size(self):
 		"""resize all widgets"""
-		SuperClass.update_size(self)
+		UI.update_size(self)
 		y, x = self.env.termsize
 
 		self.filelist_container.resize(1, 0, y-2, x)
diff --git a/ranger/gui/displayable.py b/ranger/gui/displayable.py
index a19633b5..ef0260dc 100644
--- a/ranger/gui/displayable.py
+++ b/ranger/gui/displayable.py
@@ -6,12 +6,19 @@ class Displayable(EnvironmentAware, FileManagerAware, SettingsAware):
 	win = None
 	colorscheme = None
 
-	def __init__(self, win):
+	def __init__(self, win, env=None, fm=None, settings=None):
+		if env is not None:
+			self.env = env
+		if fm is not None:
+			self.fm = fm
+		if settings is not None:
+			self.settings = settings
+
 		self.x = 0
 		self.y = 0
 		self.wid = 0
 		self.hei = 0
-		self.colorscheme = self.env.settings.colorscheme
+		self.colorscheme = self.settings.colorscheme
 
 		if win is not None:
 			self.win = win
@@ -24,13 +31,11 @@ class Displayable(EnvironmentAware, FileManagerAware, SettingsAware):
 		"""Is item inside the boundaries?
 item can be an iterable like [y, x] or an object with x and y methods."""
 		try:
-			y, x = item
-		except ValueError:
-			return False
-		except TypeError:
+			y, x = item.y, item.x
+		except AttributeError:
 			try:
-				y, x = item.y, item.x
-			except AttributeError:
+				y, x = item
+			except (ValueError, TypeError):
 				return False
 		
 		return self.contains_point(y, x)
@@ -93,6 +98,9 @@ Override this!"""
 			wid = wid or maxx - x
 			hei = hei or maxy - y
 
+			if x < 0 or y < 0:
+				raise OutOfBoundsException("Starting point below zero!")
+
 			if x + wid > maxx and y + hei > maxy:
 				raise OutOfBoundsException("X and Y out of bounds!")
 
@@ -110,7 +118,14 @@ Override this!"""
 
 class DisplayableContainer(Displayable):
 	container = None
-	def __init__(self, win):
+	def __init__(self, win, env=None, fm=None, settings=None):
+		if env is not None:
+			self.env = env
+		if fm is not None:
+			self.fm = fm
+		if settings is not None:
+			self.settings = settings
+
 		Displayable.__init__(self, win)
 		self.container = []
 
@@ -152,7 +167,7 @@ class DisplayableContainer(Displayable):
 	def click(self, event):
 		"""Recursively called on objects in container"""
 		focused_obj = self.get_focused_obj()
-		if focused_obj and focused_obj.click(key):
+		if focused_obj and focused_obj.click(event):
 			return True
 
 		for displayable in self.container:
@@ -162,8 +177,8 @@ class DisplayableContainer(Displayable):
 
 		return False
 
-	def add_obj(self, obj):
-		self.container.append(obj)
+	def add_obj(self, *objs):
+		self.container.extend(objs)
 
 	def destroy(self):
 		"""Recursively called on objects in container"""
diff --git a/ranger/gui/ui.py b/ranger/gui/ui.py
index 4b473021..3840a978 100644
--- a/ranger/gui/ui.py
+++ b/ranger/gui/ui.py
@@ -7,10 +7,15 @@ from ranger.container import CommandList
 class UI(DisplayableContainer):
 	is_set_up = False
 	mousemask = curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION
-	def __init__(self, commandlist = None):
+	def __init__(self, commandlist=None, env=None, fm=None):
 		import os
 		os.environ['ESCDELAY'] = '25' # don't know a cleaner way
 
+		if env is not None:
+			self.env = env
+		if fm is not None:
+			self.fm = fm
+
 		if commandlist is None:
 			self.commandlist = CommandList()
 			self.settings.keys.initialize_commands(self.commandlist)
diff --git a/test/__init__.py b/test/__init__.py
index 500d3383..3043ba18 100644
--- a/test/__init__.py
+++ b/test/__init__.py
@@ -6,3 +6,24 @@ __all__ = [ x[0:x.index('.')] \
 
 def init():
 	sys.path.append(os.path.abspath(os.path.join(sys.path[0], '..')))
+
+class Fake(object):
+	def __getattr__(self, attrname):
+		if not hasattr(self, attrname):
+			setattr(self, attrname, Fake())
+		return self.__dict__[attrname]
+
+	def __call__(self, *_):
+		return Fake()
+
+	def __clear__(self):
+		self.__dict__.clear()
+
+	def __iter__(self):
+		return iter(())
+
+class OK(Exception):
+	pass
+
+def raise_ok(*_, **__):
+	raise OK()
diff --git a/test/tc_directory.py b/test/tc_directory.py
index 676ec0fe..11c94b3b 100644
--- a/test/tc_directory.py
+++ b/test/tc_directory.py
@@ -48,11 +48,11 @@ class Test1(unittest.TestCase):
 		for name in assumed_filenames:
 			f = File(name)
 			f.load()
-			equal = 0
 			for dirfile in dir.files:
-				if (f.__dict__ == dirfile.__dict__):
-					equal += 1
-			self.assertEqual(equal, 1)
+				if (f.path == dirfile.path and f.stat == dirfile.stat):
+					break
+			else:
+				self.fail("couldn't find file {0}".format(name))
 
 	def test_nonexistant_dir(self):
 		dir = Directory(NONEXISTANT_DIR)
diff --git a/test/tc_displayable.py b/test/tc_displayable.py
new file mode 100644
index 00000000..01be0c9c
--- /dev/null
+++ b/test/tc_displayable.py
@@ -0,0 +1,122 @@
+if __name__ == '__main__': from __init__ import init; init()
+
+import unittest
+import curses
+from random import randint
+
+from ranger.gui.displayable import\
+		Displayable, DisplayableContainer, OutOfBoundsException
+from test import Fake, OK, raise_ok
+
+class TestDisplayable(unittest.TestCase):
+	def setUp(self):
+		self.win = Fake()
+		self.fm = Fake()
+		self.env = Fake()
+		self.settings = Fake()
+		self.disp = Displayable( win=self.win,
+				env=self.env, fm=self.fm, settings=self.settings)
+
+		hei, wid = 100, 100
+		self.env.termsize = (hei, wid)
+
+	def tearDown(self):
+		self.disp.destroy()
+
+	def test_colorscheme(self):
+		# Using a color method implies change of window attributes
+		disp = self.disp
+
+		self.win.chgat = raise_ok
+		self.win.attrset = raise_ok
+
+		self.assertRaises(OK, disp.color, 'a', 'b')
+		self.assertRaises(OK, disp.color_at, 0, 0, 0, 'a', 'b')
+		self.assertRaises(OK, disp.color_reset)
+
+	def test_boundaries(self):
+		disp = self.disp
+		hei, wid = self.env.termsize
+
+		self.assertRaises(OutOfBoundsException, disp.resize, 0, 0, hei + 1, wid)
+		self.assertRaises(OutOfBoundsException, disp.resize, 0, 0, hei, wid + 1)
+		self.assertRaises(OutOfBoundsException, disp.resize, -1, 0, hei, wid)
+		self.assertRaises(OutOfBoundsException, disp.resize, 0, -1, hei, wid)
+
+		box = (randint(10, 20), randint(30, 40), \
+				randint(30, 40), randint(10, 20))
+
+		def in_box(y, x):
+			return (x >= box[1] and x < box[1] + box[3]) and \
+					(y >= box[0] and y < box[0] + box[2])
+
+		disp.resize(*box)
+		for y, x in zip(range(10), range(10)):
+			is_in_box = in_box(y, x)
+
+			point1 = (y, x)
+			self.assertEqual(is_in_box, point1 in disp)
+
+			point2 = Fake()
+			point2.x = x
+			point2.y = y
+			self.assertEqual(is_in_box, point2 in disp)
+
+class TestDisplayableContainer(unittest.TestCase):
+	def setUp(self):
+		self.win = Fake()
+		self.fm = Fake()
+		self.env = Fake()
+		self.settings = Fake()
+
+		self.initdict = {'win': self.win, 'settings': self.settings,
+				'fm': self.fm, 'env': self.env}
+
+		self.disp = Displayable(**self.initdict)
+		self.disc = DisplayableContainer(**self.initdict)
+		self.disc.add_obj(self.disp)
+
+		hei, wid = (100, 100)
+		self.env.termsize = (hei, wid)
+
+	def tearDown(self):
+		self.disc.destroy()
+
+	def test_container(self):
+		self.assertTrue(self.disp in self.disc.container)
+
+	def test_click(self):
+		self.disp.click = raise_ok
+
+		self.disc.resize(0, 0, 50, 50)
+		self.disp.resize(0, 0, 20, 20)
+		fakepos = Fake()
+
+		fakepos.x = 10
+		fakepos.y = 10
+		self.assertRaises(OK, self.disc.click, fakepos)
+
+		fakepos.x = 30
+		fakepos.y = 10
+		self.disc.click(fakepos)
+
+	def test_focused_object(self):
+		d1 = Displayable(**self.initdict)
+		d2 = DisplayableContainer(**self.initdict)
+		d2.add_obj(*[Displayable(**self.initdict) for x in range(5)])
+		d3 = DisplayableContainer(**self.initdict)
+		d3.add_obj(*[Displayable(**self.initdict) for x in range(5)])
+
+		self.disc.add_obj(d1, d2, d3)
+
+		d3.container[3].focused = True
+
+		self.assertEqual(self.disc.get_focused_obj(), d3.container[3])
+
+		d3.container[3].focused = False
+		d2.container[0].focused = True
+
+		self.assertEqual(self.disc.get_focused_obj(), d2.container[0])
+
+if __name__ == '__main__':
+	unittest.main()
diff --git a/test/tc_ui.py b/test/tc_ui.py
new file mode 100644
index 00000000..fbe51f64
--- /dev/null
+++ b/test/tc_ui.py
@@ -0,0 +1,61 @@
+if __name__ == '__main__': from __init__ import init; init()
+
+import unittest
+import curses
+
+from ranger.gui import ui
+
+from test import Fake, OK, raise_ok
+
+ui.curses = Fake()
+
+class Test(unittest.TestCase):
+	def setUp(self):
+
+		self.fm = Fake()
+		self.ui = ui.UI(env=Fake(), fm=self.fm)
+
+		def fakesetup():
+			self.ui.widget = Fake()
+			self.ui.add_obj(self.ui.widget)
+		self.ui.setup = fakesetup
+
+		self.ui.initialize()
+
+	def tearDown(self):
+		self.ui.destroy()
+	
+	def test_scrolling(self):
+		# test whether scrolling works
+		self.fm.scroll = raise_ok
+		self.ui.get_focused_obj = lambda: False
+
+		ui.curses.getmouse = lambda: (0, 0, 0, 0, curses.BUTTON2_PRESSED)
+		self.assertRaises(OK, self.ui.handle_mouse)
+
+		ui.curses.getmouse = lambda: (0, 0, 0, 0, curses.BUTTON4_PRESSED)
+		self.assertRaises(OK, self.ui.handle_mouse)
+	
+	def test_passing(self):
+		# Test whether certain method calls are passed to widgets
+		widget = self.ui.widget
+
+		widget.draw = raise_ok
+		self.assertRaises(OK, self.ui.draw)
+		widget.__clear__()
+
+		widget.finalize = raise_ok
+		self.assertRaises(OK, self.ui.finalize)
+		widget.__clear__()
+
+		widget.press = raise_ok
+		random_key = 123
+		self.assertRaises(OK, self.ui.handle_key, random_key)
+		widget.__clear__()
+
+		widget.destroy = raise_ok
+		self.assertRaises(OK, self.ui.destroy)
+		widget.__clear__()
+
+if __name__ == '__main__':
+	unittest.main()
diff --git a/test/test.py b/test/test.py
new file mode 100644
index 00000000..bf285a47
--- /dev/null
+++ b/test/test.py
@@ -0,0 +1,5 @@
+"""Workaround to allow running single test cases directly"""
+try:
+	from __init__ import init, Fake, OK, raise_ok
+except:
+	from test import init, Fake, OK, raise_ok
pan class="s">"End Primitive Recipe Declarations") RUN_INTERACTIVE, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "run-interactive", RUN_INTERACTIVE); :(before "End Primitive Recipe Checks") case RUN_INTERACTIVE: { if (SIZE(inst.ingredients) != 1) { raise_error << maybe(get(Recipe, r).name) << "'run-interactive' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); break; } if (!is_mu_string(inst.ingredients.at(0))) { raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'run-interactive' should be a string, but got " << to_string(inst.ingredients.at(0)) << '\n' << end(); break; } break; } :(before "End Primitive Recipe Implementations") case RUN_INTERACTIVE: { bool new_code_pushed_to_stack = run_interactive(ingredients.at(0).at(0)); if (!new_code_pushed_to_stack) { products.resize(5); products.at(0).push_back(0); products.at(1).push_back(trace_error_warning_contents()); products.at(2).push_back(0); products.at(3).push_back(trace_app_contents()); products.at(4).push_back(1); // completed run_code_end(); break; // done with this instruction } else { continue; // not done with caller; don't increment current_step_index() } } :(before "End Globals") 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) // reads a string, tries to call it as code (treating it as a test), saving // all warnings. // returns true if successfully called (no errors found during load and transform) bool run_interactive(long long int address) { assert(contains_key(Recipe_ordinal, "interactive") && get(Recipe_ordinal, "interactive") != 0); // try to sandbox the run as best you can // todo: test this if (!Current_scenario) { for (long long int i = 1; i < Reserved_for_tests; ++i) 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); // don't kill the current routine on parse errors routine* save_current_routine = Current_routine; Current_routine = NULL; // call run(string) but without the scheduling load(string("recipe! interactive [\n") + "local-scope\n" + "screen:address:shared:screen <- next-ingredient\n" + "$start-tracking-products\n" + command + "\n" + "$stop-tracking-products\n" + "reply screen\n" + "]\n"); transform_all(); Current_routine = save_current_routine; if (trace_count("error") > 0) return false; // now call 'sandbox' which will run 'interactive' in a separate routine, // and wait for it if (Save_trace_stream) { ++Save_trace_stream->callstack_depth; trace(9999, "trace") << "run-interactive: incrementing callstack depth to " << Save_trace_stream->callstack_depth << end(); assert(Save_trace_stream->callstack_depth < 9000); // 9998-101 plus cushion } Current_routine->calls.push_front(call(get(Recipe_ordinal, "sandbox"))); return true; } void run_code_begin(bool snapshot_recently_added_recipes) { //? cerr << "loading new trace\n"; // stuff to undo later, in run_code_end() Hide_warnings = true; Hide_errors = true; Disable_redefine_warnings = 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(); } Save_trace_stream = Trace_stream; Save_trace_file = Trace_file; Trace_file = ""; Trace_stream = new trace_stream; Trace_stream->collect_depth = App_depth; } void run_code_end() { //? cerr << "back to old trace\n"; Hide_warnings = false; Hide_errors = false; Disable_redefine_warnings = false; delete Trace_stream; Trace_stream = Save_trace_stream; Save_trace_stream = NULL; 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(); } } :(before "End Load Recipes") load(string( "recipe interactive [\n") + // just a dummy version to initialize the Recipe_ordinal and so on "]\n" + "recipe sandbox [\n" + "local-scope\n" + "screen:address:shared:screen <- new-fake-screen 30, 5\n" + "r:number/routine_id <- start-running interactive, screen\n" + "limit-time r, 100000/instructions\n" + "wait-for-routine r\n" + "sandbox-state:number <- routine-state r/routine_id\n" + "completed?:boolean <- equal sandbox-state, 1/completed\n" + "output:address:shared:array:character <- $most-recent-products\n" + "warnings:address:shared:array:character <- save-errors-warnings\n" + "stashes:address:shared:array:character <- save-app-trace\n" + "$cleanup-run-interactive\n" + "reply output, warnings, screen, stashes, completed?\n" + "]\n"); transform_all(); Recently_added_recipes.clear(); //: adjust errors/warnings in the sandbox :(after "string maybe(string s)") if (s == "interactive") return ""; :(scenario run_interactive_comments) recipe main [ 1:address:shared:array:character <- new [# ab add 2, 2] 2:address:shared:array:character <- run-interactive 1:address:shared:array:character 3:array:character <- copy *2:address:shared:array:character ] +mem: storing 52 in location 4 :(before "End Primitive Recipe Declarations") _START_TRACKING_PRODUCTS, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "$start-tracking-products", _START_TRACKING_PRODUCTS); :(before "End Primitive Recipe Checks") case _START_TRACKING_PRODUCTS: { break; } :(before "End Primitive Recipe Implementations") case _START_TRACKING_PRODUCTS: { Track_most_recent_products = true; break; } :(before "End Primitive Recipe Declarations") _STOP_TRACKING_PRODUCTS, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "$stop-tracking-products", _STOP_TRACKING_PRODUCTS); :(before "End Primitive Recipe Checks") case _STOP_TRACKING_PRODUCTS: { break; } :(before "End Primitive Recipe Implementations") case _STOP_TRACKING_PRODUCTS: { Track_most_recent_products = false; break; } :(before "End Primitive Recipe Declarations") _MOST_RECENT_PRODUCTS, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "$most-recent-products", _MOST_RECENT_PRODUCTS); :(before "End Primitive Recipe Checks") case _MOST_RECENT_PRODUCTS: { break; } :(before "End Primitive Recipe Implementations") case _MOST_RECENT_PRODUCTS: { products.resize(1); products.at(0).push_back(new_mu_string(Most_recent_products)); break; } :(before "End Primitive Recipe Declarations") SAVE_ERRORS_WARNINGS, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "save-errors-warnings", SAVE_ERRORS_WARNINGS); :(before "End Primitive Recipe Checks") case SAVE_ERRORS_WARNINGS: { break; } :(before "End Primitive Recipe Implementations") case SAVE_ERRORS_WARNINGS: { products.resize(1); products.at(0).push_back(trace_error_warning_contents()); break; } :(before "End Primitive Recipe Declarations") SAVE_APP_TRACE, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "save-app-trace", SAVE_APP_TRACE); :(before "End Primitive Recipe Checks") case SAVE_APP_TRACE: { break; } :(before "End Primitive Recipe Implementations") case SAVE_APP_TRACE: { products.resize(1); products.at(0).push_back(trace_app_contents()); break; } :(before "End Primitive Recipe Declarations") _CLEANUP_RUN_INTERACTIVE, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "$cleanup-run-interactive", _CLEANUP_RUN_INTERACTIVE); :(before "End Primitive Recipe Checks") case _CLEANUP_RUN_INTERACTIVE: { break; } :(before "End Primitive Recipe Implementations") case _CLEANUP_RUN_INTERACTIVE: { run_code_end(); break; } :(scenario "run_interactive_converts_result_to_text") recipe main [ # try to interactively add 2 and 2 1:address:shared:array:character <- new [add 2, 2] 2:address:shared:array:character <- run-interactive 1:address:shared:array:character 10:array:character <- copy 2:address:shared:array:character/lookup ] # first letter in the output should be '4' in unicode +mem: storing 52 in location 11 :(scenario "run_interactive_returns_text") recipe main [ # try to interactively add 2 and 2 1:address:shared:array:character <- new [ x:address:shared:array:character <- new [a] y:address:shared:array:character <- new [b] z:address:shared:array:character <- append x:address:shared:array:character, y:address:shared:array:character ] 2:address:shared:array:character <- run-interactive 1:address:shared:array:character 10:array:character <- copy 2:address:shared:array:character/lookup ] # output contains "ab" +mem: storing 97 in location 11 +mem: storing 98 in location 12 :(scenario "run_interactive_returns_errors") recipe main [ # run a command that generates an error 1:address:shared:array:character <- new [x:number <- copy 34 get x:number, foo:offset] 2:address:shared:array:character, 3:address:shared:array:character <- run-interactive 1:address:shared:array:character 10:array:character <- copy 3:address:shared:array:character/lookup ] # error should be "unknown element foo in container number" +mem: storing 117 in location 11 +mem: storing 110 in location 12 +mem: storing 107 in location 13 +mem: storing 110 in location 14 # ... :(scenario run_interactive_with_comment) recipe main [ # 2 instructions, with a comment after the first 1:address:shared:array:number <- new [a:number <- copy 0 # abc b:number <- copy 0 ] 2:address:shared:array:character, 3:address:shared:array:character <- run-interactive 1:address:shared:array:character ] # 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" " reply 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); } long long 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") Most_recent_products = ""; :(before "End of Instruction") if (Track_most_recent_products) { track_most_recent_products(current_instruction(), products); } :(code) void track_most_recent_products(const instruction& instruction, const vector<vector<double> >& products) { ostringstream out; for (long long int i = 0; i < SIZE(products); ++i) { // string if (i < SIZE(instruction.products)) { if (is_mu_string(instruction.products.at(i))) { if (!scalar(products.at(i))) { tb_shutdown(); cerr << read_mu_string(trace_error_warning_contents()) << '\n'; cerr << SIZE(products.at(i)) << ": "; for (long long int j = 0; j < SIZE(products.at(i)); ++j) cerr << no_scientific(products.at(i).at(j)) << ' '; cerr << '\n'; } assert(scalar(products.at(i))); out << read_mu_string(products.at(i).at(0)) << '\n'; continue; } // End Record Product Special-cases } for (long long int j = 0; j < SIZE(products.at(i)); ++j) out << no_scientific(products.at(i).at(j)) << ' '; out << '\n'; } Most_recent_products = out.str(); } :(code) string strip_comments(string in) { ostringstream result; for (long long int i = 0; i < SIZE(in); ++i) { if (in.at(i) != '#') { result << in.at(i); } else { while (i+1 < SIZE(in) && in.at(i+1) != '\n') ++i; } } return result.str(); } long long int stringified_value_of_location(long long int address) { // convert to string ostringstream out; out << no_scientific(get_or_insert(Memory, address)); return new_mu_string(out.str()); } long long int trace_error_warning_contents() { if (!Trace_stream) return 0; ostringstream out; for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { if (p->depth > Warning_depth) continue; out << p->contents; if (*--p->contents.end() != '\n') out << '\n'; } string result = out.str(); if (result.empty()) return 0; truncate(result); return new_mu_string(result); } long long int trace_app_contents() { if (!Trace_stream) return 0; ostringstream out; for (vector<trace_line>::iterator p = Trace_stream->past_lines.begin(); p != Trace_stream->past_lines.end(); ++p) { if (p->depth != App_depth) continue; out << p->contents; if (*--p->contents.end() != '\n') out << '\n'; } string result = out.str(); if (result.empty()) return 0; truncate(result); return new_mu_string(result); } void truncate(string& x) { if (SIZE(x) > 512) { x.erase(512); *x.rbegin() = '\n'; *++x.rbegin() = '.'; *++++x.rbegin() = '.'; } } //: simpler version of run-interactive: doesn't do any running, just loads //: recipes and reports errors+warnings. :(before "End Primitive Recipe Declarations") RELOAD, :(before "End Primitive Recipe Numbers") put(Recipe_ordinal, "reload", RELOAD); :(before "End Primitive Recipe Checks") case RELOAD: { if (SIZE(inst.ingredients) != 1) { raise_error << maybe(get(Recipe, r).name) << "'reload' requires exactly one ingredient, but got " << to_string(inst) << '\n' << end(); break; } if (!is_mu_string(inst.ingredients.at(0))) { raise_error << maybe(get(Recipe, r).name) << "first ingredient of 'reload' should be a string, but got " << inst.ingredients.at(0).original_string << '\n' << end(); break; } break; } :(before "End Primitive Recipe Implementations") case RELOAD: { //? cerr << "== reload\n"; // clear any containers in advance for (long long 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) { //? cerr << p->first << ":\n"; vector<recipe_ordinal>& variants = p->second; for (long long 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()) { //? cerr << " " << variants.at(i) << ' ' << get(Recipe, variants.at(i)).name << '\n'; variants.at(i) = -1; // ghost } } } for (long long 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; Current_routine = NULL; vector<recipe_ordinal> recipes_reloaded = load(code); // clear a few things from previous runs // ad hoc list; we've probably missed a few for (long long int i = 0; i < SIZE(recipes_reloaded); ++i) Name.erase(recipes_reloaded.at(i)); transform_all(); Trace_stream->newline(); // flush trace Current_routine = save_current_routine; products.resize(1); products.at(0).push_back(trace_error_warning_contents()); run_code_end(); // wait until we're done with the trace contents //? cerr << "reload done\n"; break; } :(scenario reload_continues_past_error) recipe main [ local-scope x:address:shared:array:character <- new [recipe foo [ get 1234:number, foo:offset ]] reload x 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" "reply 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); }