Visibility: Adds some unit tests.

Adds unit tests to the Bootloader class in server/hosts. It requires
adding a common.py to server/hosts, and also tweaks a few private
names (converting double-underscore -> single underscore) in
Bootloader to make it easier to test & mock out.

Signed-off-by: John Admanski <jadmanski@google.com>



git-svn-id: http://test.kernel.org/svn/autotest/trunk@1604 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/server/hosts/bootloader.py b/server/hosts/bootloader.py
index 0b9ebeb..7994a8a 100644
--- a/server/hosts/bootloader.py
+++ b/server/hosts/bootloader.py
@@ -36,30 +36,30 @@
 
 	def __init__(self, host, xen_mode=False):
 		super(Bootloader, self).__init__()
-		self.__host = weakref.ref(host)
-		self.__boottool_path = None
+		self._host = weakref.ref(host)
+		self._boottool_path = None
 		self.xen_mode = xen_mode
 
 
 	def get_type(self):
-		return self.__run_boottool('--bootloader-probe').stdout.strip()
+		return self._run_boottool('--bootloader-probe').stdout.strip()
 
 
 	def get_architecture(self):
-		return self.__run_boottool('--arch-probe').stdout.strip()
+		return self._run_boottool('--arch-probe').stdout.strip()
 
 
 	def get_titles(self):
-		return self.__run_boottool('--info all | grep title | '
+		return self._run_boottool('--info all | grep title | '
 			'cut -d " " -f2-').stdout.strip().split('\n')
 
 
 	def get_default(self):
-		return self.__run_boottool('--default').stdout.strip()
+		return self._run_boottool('--default').stdout.strip()
 
 
-	def __get_info(self, info_id):
-		retval = self.__run_boottool('--info=%s' % info_id).stdout
+	def _get_info(self, info_id):
+		retval = self._run_boottool('--info=%s' % info_id).stdout
 
 		results = []
 		info = {}
@@ -78,7 +78,7 @@
 
 
 	def get_info(self, index):
-		results = self.__get_info(index)
+		results = self._get_info(index)
 		if results:
 			return results[0]
 		else:
@@ -86,11 +86,11 @@
 
 
 	def get_all_info(self):
-		return self.__get_info('all')
+		return self._get_info('all')
 
 
 	def set_default(self, index):
-		self.__run_boottool('--set-default=%s' % index)
+		self._run_boottool('--set-default=%s' % index)
 
 
 	# 'kernel' can be a position number or a title
@@ -101,26 +101,26 @@
 		if self.xen_mode:
 			parameters += ' --xen'
 		
-		self.__run_boottool(parameters)
+		self._run_boottool(parameters)
 
 
 	def add_xen_hypervisor_args(self, kernel, args):
-		self.__run_boottool('--xen --update-xenhyper=%s --xha="%s"' \
+		self._run_boottool('--xen --update-xenhyper=%s --xha="%s"' \
 				    % (kernel, args))
 
 
 	def remove_args(self, kernel, args):
-		params = '--update-kernel=%s --remove-args=%s' % (kernel, args)
+		params = '--update-kernel=%s --remove-args="%s"' % (kernel, args)
 		
 		#add parameter if this is a Xen entry
 		if self.xen_mode:
 			params += ' --xen'
 		
-		self.__run_boottool(params)
+		self._run_boottool(params)
 
 
 	def remove_xen_hypervisor_args(self, kernel, args):
-		self.__run_boottool('--xen --update-xenhyper=%s '
+		self._run_boottool('--xen --update-xenhyper=%s '
 			'--remove-args="%s"') % (kernel, args)
 
 
@@ -131,7 +131,7 @@
 		replaced.
 		"""
 		if title in self.get_titles():
-			self.__run_boottool('--remove-kernel "%s"' % (
+			self._run_boottool('--remove-kernel "%s"' % (
 				utils.sh_escape(title),))
 		
 		parameters = '--add-kernel "%s" --title "%s"' % (
@@ -158,40 +158,40 @@
 				parameters += ' --xenhyper "%s"' % (
 					utils.sh_escape(xen_hypervisor),)
 		
-		self.__run_boottool(parameters)
+		self._run_boottool(parameters)
 
 
 	def remove_kernel(self, kernel):
-		self.__run_boottool('--remove-kernel=%s' % kernel)
+		self._run_boottool('--remove-kernel=%s' % kernel)
 
 
 	def boot_once(self, title):
-		self.__run_boottool('--boot-once --title=%s' % title)
+		self._run_boottool('--boot-once --title=%s' % title)
 
 
 	def install_boottool(self):
-		if self.__host() is None:
+		if self._host() is None:
 			raise error.AutoservError(
 			    "Host does not exist anymore")
-		tmpdir = self.__host().get_tmp_dir()
-		self.__host().send_file(os.path.abspath(os.path.join(
+		tmpdir = self._host().get_tmp_dir()
+		self._host().send_file(os.path.abspath(os.path.join(
 			utils.get_server_dir(), BOOTTOOL_SRC)), tmpdir)
-		self.__boottool_path= os.path.join(tmpdir, 
+		self._boottool_path= os.path.join(tmpdir, 
 			os.path.basename(BOOTTOOL_SRC))
 
 
-	def __get_boottool_path(self):
-		if not self.__boottool_path:
+	def _get_boottool_path(self):
+		if not self._boottool_path:
 			self.install_boottool()
-		return self.__boottool_path
+		return self._boottool_path
 
 
-	def __set_boottool_path(self, path):
-		self.__boottool_path = path
+	def _set_boottool_path(self, path):
+		self._boottool_path = path
 
 	
-	boottool_path = property(__get_boottool_path, __set_boottool_path)
+	boottool_path = property(_get_boottool_path, _set_boottool_path)
 
 
-	def __run_boottool(self, cmd):
-		return self.__host().run(self.boottool_path + ' ' + cmd)
+	def _run_boottool(self, cmd):
+		return self._host().run(self.boottool_path + ' ' + cmd)
diff --git a/server/hosts/bootloader_unittest.py b/server/hosts/bootloader_unittest.py
new file mode 100644
index 0000000..681c9bf
--- /dev/null
+++ b/server/hosts/bootloader_unittest.py
@@ -0,0 +1,356 @@
+#!/usr/bin/python
+
+import unittest, os
+import common
+
+from autotest_lib.client.unittest import mock
+from autotest_lib.client.common_lib import error, utils as common_utils
+from autotest_lib.server import utils
+from autotest_lib.server.hosts import bootloader, ssh_host
+
+
+class test_bootloader_install(unittest.TestCase):
+	def setUp(self):
+		self.god = mock.mock_god()
+
+		# mock out get_server_dir
+		self.god.stub_function(utils, "get_server_dir")
+
+
+	def tearDown(self):
+		self.god.unstub_all()
+
+
+	def create_mock_sshhost(self):
+		# useful for building disposable SSHHost mocks
+		return self.god.create_mock_class(ssh_host.SSHHost, "SSHHost")
+
+
+	def create_install_boottool_mock(self, loader, dst_dir):
+		mock_install_boottool = \
+			self.god.create_mock_function("install_boottool")
+		def install_boottool():
+			loader._boottool_path = dst_dir
+			mock_install_boottool()
+		loader.install_boottool = install_boottool
+		return mock_install_boottool
+
+
+	def test_install_fails_without_host(self):
+		host = self.create_mock_sshhost()
+		loader = bootloader.Bootloader(host)
+		del host
+		self.assertRaises(error.AutoservError, loader.install_boottool)
+
+
+	def test_installs_to_tmpdir(self):
+		TMPDIR = "/unittest/tmp"
+		SERVERDIR = "/unittest/server"
+		BOOTTOOL_SRC = os.path.join(SERVERDIR, bootloader.BOOTTOOL_SRC)
+		BOOTTOOL_SRC = os.path.abspath(BOOTTOOL_SRC)
+		BOOTTOOL_DST = os.path.join(TMPDIR, "boottool")
+		# set up the recording
+		host = self.create_mock_sshhost()
+		host.get_tmp_dir.expect_call().and_return(TMPDIR)
+		utils.get_server_dir.expect_call().and_return(SERVERDIR)
+		host.send_file.expect_call(BOOTTOOL_SRC, TMPDIR)
+		# run the test
+		loader = bootloader.Bootloader(host)
+		loader.install_boottool()
+		# assert the playback is correct
+		self.god.check_playback()
+		# assert the final dest is correct
+		self.assertEquals(loader.boottool_path, BOOTTOOL_DST)
+
+
+	def test_get_path_automatically_installs(self):
+		BOOTTOOL_DST = "/unittest/tmp/boottool"
+		host = self.create_mock_sshhost()
+		loader = bootloader.Bootloader(host)
+		# mock out loader.install_boottool
+		mock_install = \
+			self.create_install_boottool_mock(loader, BOOTTOOL_DST)
+		# set up the recording
+		mock_install.expect_call()
+		# run the test
+		self.assertEquals(loader.boottool_path, BOOTTOOL_DST)
+		self.god.check_playback()
+
+
+	def test_install_is_only_called_once(self):
+		BOOTTOOL_DST = "/unittest/tmp/boottool"
+		host = self.create_mock_sshhost()
+		loader = bootloader.Bootloader(host)
+		# mock out loader.install_boottool
+		mock_install = \
+			self.create_install_boottool_mock(loader, BOOTTOOL_DST)
+		# set up the recording
+		mock_install.expect_call()
+		# run the test
+		self.assertEquals(loader.boottool_path, BOOTTOOL_DST)
+		self.god.check_playback()
+		self.assertEquals(loader.boottool_path, BOOTTOOL_DST)
+		self.god.check_playback()
+
+
+class test_bootloader_methods(unittest.TestCase):
+	def setUp(self):
+		self.god = mock.mock_god()
+		self.host = self.god.create_mock_class(ssh_host.SSHHost,
+						       "SSHHost")
+		# creates a bootloader with _run_boottool mocked out
+		self.loader = bootloader.Bootloader(self.host)
+		self.god.stub_function(self.loader, "_run_boottool")
+
+
+	def tearDown(self):
+		self.god.unstub_all()
+
+
+	def expect_run_boottool(self, arg, result):
+		result = common_utils.CmdResult(stdout=result, exit_status=0)
+		self.loader._run_boottool.expect_call(arg).and_return(result)
+
+
+	def test_get_type(self):
+		# set up the recording
+		self.expect_run_boottool("--bootloader-probe", "lilo\n")
+		# run the test
+		self.assertEquals(self.loader.get_type(), "lilo")
+		self.god.check_playback()
+
+
+	def test_get_arch(self):
+		# set up the recording
+		self.expect_run_boottool("--arch-probe", "x86_64\n")
+		# run the test
+		self.assertEquals(self.loader.get_architecture(), "x86_64")
+		self.god.check_playback()
+
+
+	def test_get_default(self):
+		# set up the recording
+		self.expect_run_boottool("--default", "0\n")
+		# run the test
+		self.assertEquals(self.loader.get_default(), "0")
+		self.god.check_playback()
+
+
+	def test_get_titles(self):
+		# set up the recording
+		self.expect_run_boottool(mock.regex_comparator(
+		    r"^--info all \|"), "title #1\ntitle #2\n")
+		# run the test
+		self.assertEquals(self.loader.get_titles(),
+				  ["title #1", "title #2"])
+		self.god.check_playback()
+
+
+	def test_get_info_single_result(self):
+		RESULT = (
+		"index\t: 5\n"
+		"args\t: ro single\n"
+		"boot\t: (hd0,0)\n"
+		"initrd\t: /boot/initrd.img-2.6.15-23-386\n"
+		"kernel\t: /boot/vmlinuz-2.6.15-23-386\n"
+		"root\t: UUID=07D7-0714\n"
+		"savedefault\t:   \n"
+		"title\t: Distro, kernel 2.6.15-23-386\n"
+		)
+		# set up the recording
+		self.expect_run_boottool("--info=5", RESULT)
+		# run the test
+		info = self.loader.get_info(5)
+		self.god.check_playback()
+		expected_info = {"index": "5", "args": "ro single",
+				 "boot": "(hd0,0)",
+				 "initrd": "/boot/initrd.img-2.6.15-23-386",
+				 "kernel": "/boot/vmlinuz-2.6.15-23-386",
+				 "root": "UUID=07D7-0714", "savedefault": "",
+				 "title": "Distro, kernel 2.6.15-23-386"}
+		self.assertEquals(expected_info, info)
+
+
+	def test_get_info_missing_result(self):
+		# set up the recording
+		self.expect_run_boottool("--info=4", "")
+		# run the test
+		info = self.loader.get_info(4)
+		self.god.check_playback()
+		self.assertEquals({}, info)
+
+
+	def test_get_info_multiple_results(self):
+		RESULT = (
+		"index\t: 5\n"
+		"args\t: ro single\n"
+		"boot\t: (hd0,0)\n"
+		"initrd\t: /boot/initrd.img-2.6.15-23-386\n"
+		"kernel\t: /boot/vmlinuz-2.6.15-23-386\n"
+		"root\t: UUID=07D7-0714\n"
+		"savedefault\t:   \n"
+		"title\t: Distro, kernel 2.6.15-23-386\n"
+		"\n"
+		"index\t: 7\n"
+		"args\t: ro single\n"
+		"boot\t: (hd0,0)\n"
+		"initrd\t: /boot/initrd.img-2.6.15-23-686\n"
+		"kernel\t: /boot/vmlinuz-2.6.15-23-686\n"
+		"root\t: UUID=07D7-0714\n"
+		"savedefault\t:   \n"
+		"title\t: Distro, kernel 2.6.15-23-686\n"
+		)
+		# set up the recording
+		self.expect_run_boottool("--info=all", RESULT)
+		# run the test
+		info = self.loader.get_all_info()
+		self.god.check_playback()
+		expected_info = [{"index": "5", "args": "ro single",
+				  "boot": "(hd0,0)",
+				  "initrd": "/boot/initrd.img-2.6.15-23-386",
+				  "kernel": "/boot/vmlinuz-2.6.15-23-386",
+				  "root": "UUID=07D7-0714", "savedefault": "",
+				  "title": "Distro, kernel 2.6.15-23-386"},
+				 {"index": "7", "args": "ro single",
+				  "boot": "(hd0,0)",
+				  "initrd": "/boot/initrd.img-2.6.15-23-686",
+				  "kernel": "/boot/vmlinuz-2.6.15-23-686",
+				  "root": "UUID=07D7-0714", "savedefault": "",
+				  "title": "Distro, kernel 2.6.15-23-686"}]
+		self.assertEquals(expected_info, info)
+
+
+	def test_set_default(self):
+		# set up the recording
+		self.loader._run_boottool.expect_call("--set-default=41")
+		# run the test
+		self.loader.set_default(41)
+		self.god.check_playback()
+
+
+	def test_add_args(self):
+		# set up the recording
+		self.loader._run_boottool.expect_call(
+		    "--update-kernel=10 --args=\"some kernel args\"")
+		# run the test
+		self.loader.add_args(10, "some kernel args")
+		self.god.check_playback()
+
+
+	def test_remove_args(self):
+		# set up the recording
+		self.loader._run_boottool.expect_call(
+		    "--update-kernel=12 --remove-args=\"some kernel args\"")
+		# run the test
+		self.loader.remove_args(12, "some kernel args")
+		self.god.check_playback()
+
+
+	def test_add_kernel_basic(self):
+		self.loader.get_titles = self.god.create_mock_function(
+		    "get_titles")
+		# set up the recording
+		self.loader.get_titles.expect_call().and_return(["notmylabel"])
+		self.loader._run_boottool.expect_call(
+		    "--add-kernel \"/unittest/kernels/vmlinuz\" "
+		    "--title \"mylabel\" --make-default")
+		# run the test
+		self.loader.add_kernel("/unittest/kernels/vmlinuz",
+				       "mylabel")
+		self.god.check_playback()
+
+
+	def test_add_kernel_adds_root(self):
+		self.loader.get_titles = self.god.create_mock_function(
+		    "get_titles")
+		# set up the recording
+		self.loader.get_titles.expect_call().and_return(["notmylabel"])
+		self.loader._run_boottool.expect_call(
+		    "--add-kernel \"/unittest/kernels/vmlinuz\" "
+		    "--title \"mylabel\" --root \"/unittest/root\" "
+		    "--make-default")
+		# run the test
+		self.loader.add_kernel("/unittest/kernels/vmlinuz",
+				       "mylabel", root="/unittest/root")
+		self.god.check_playback()
+
+
+	def test_add_kernel_adds_args(self):
+		self.loader.get_titles = self.god.create_mock_function(
+		    "get_titles")
+		# set up the recording
+		self.loader.get_titles.expect_call().and_return(["notmylabel"])
+		self.loader._run_boottool.expect_call(
+		    "--add-kernel \"/unittest/kernels/vmlinuz\" "
+		    "--title \"mylabel\" --args \"my kernel args\" "
+		    "--make-default")
+		# run the test
+		self.loader.add_kernel("/unittest/kernels/vmlinuz",
+				       "mylabel", args="my kernel args")
+		self.god.check_playback()
+
+
+	def test_add_kernel_adds_initrd(self):
+		self.loader.get_titles = self.god.create_mock_function(
+		    "get_titles")
+		# set up the recording
+		self.loader.get_titles.expect_call().and_return(["notmylabel"])
+		self.loader._run_boottool.expect_call(
+		    "--add-kernel \"/unittest/kernels/vmlinuz\" "
+		    "--title \"mylabel\" --initrd \"/unittest/initrd\" "
+		    "--make-default")
+		# run the test
+		self.loader.add_kernel("/unittest/kernels/vmlinuz",
+				       "mylabel", initrd="/unittest/initrd")
+		self.god.check_playback()
+
+
+	def test_add_kernel_disables_make_default(self):
+		self.loader.get_titles = self.god.create_mock_function(
+		    "get_titles")
+		# set up the recording
+		self.loader.get_titles.expect_call().and_return(["notmylabel"])
+		self.loader._run_boottool.expect_call(
+		    "--add-kernel \"/unittest/kernels/vmlinuz\" "
+		    "--title \"mylabel\"")
+		# run the test
+		self.loader.add_kernel("/unittest/kernels/vmlinuz",
+				       "mylabel", default=False)
+		self.god.check_playback()
+
+
+	def test_add_kernel_removes_old(self):
+		self.loader.get_titles = self.god.create_mock_function(
+		    "get_titles")
+		# set up the recording
+		self.loader.get_titles.expect_call().and_return(["mylabel"])
+		self.loader._run_boottool.expect_call(
+		    "--remove-kernel \"mylabel\"")
+		self.loader._run_boottool.expect_call(
+		    "--add-kernel \"/unittest/kernels/vmlinuz\" "
+		    "--title \"mylabel\" --make-default")
+		# run the test
+		self.loader.add_kernel("/unittest/kernels/vmlinuz",
+				       "mylabel")
+		self.god.check_playback()
+
+
+	def test_remove_kernel(self):
+		# set up the recording
+		self.loader._run_boottool.expect_call("--remove-kernel=14")
+		# run the test
+		self.loader.remove_kernel(14)
+		self.god.check_playback()
+
+
+	def test_boot_once(self):
+		# set up the recording
+		self.loader._run_boottool.expect_call(
+		    "--boot-once --title=autotest")
+		# run the test
+		self.loader.boot_once("autotest")
+		self.god.check_playback()
+
+
+if __name__ == "__main__":
+	unittest.main()
diff --git a/server/hosts/common.py b/server/hosts/common.py
new file mode 100644
index 0000000..41607e1
--- /dev/null
+++ b/server/hosts/common.py
@@ -0,0 +1,8 @@
+import os, sys
+dirname = os.path.dirname(sys.modules[__name__].__file__)
+autotest_dir = os.path.abspath(os.path.join(dirname, "..", ".."))
+client_dir = os.path.join(autotest_dir, "client")
+sys.path.insert(0, client_dir)
+import setup_modules
+sys.path.pop(0)
+setup_modules.setup(base_path=autotest_dir, root_module_name="autotest_lib")