From b00e0e85e1d9d6dba77eee34f408762c2306437a Mon Sep 17 00:00:00 2001 From: William Ting Date: Mon, 28 May 2012 09:21:31 -1000 Subject: [PATCH] Fix #123, new installations were not initializing database properly. Also added unit test coverage to check database initialization, saving, and loading. Unit tests also revealed that migration code was not working properly (starts database from scratch instead of copying existing entries over). --- bin/autojump | 25 +++++++------ tests/runtests.py | 95 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/bin/autojump b/bin/autojump index 1d3604b..d88516e 100755 --- a/bin/autojump +++ b/bin/autojump @@ -81,6 +81,7 @@ class Database: else: import math self.data[path] = math.sqrt((self.data[path]**2)+(increment**2)) + self.save() def decay(self): """ @@ -102,34 +103,34 @@ class Database: """ Try to open the database file, recovering from backup if needed. """ - try: - with open(self.filename, 'r') as aj_file: - for line in aj_file.readlines(): + if os.path.exists(self.filename): + with open(self.filename, 'r') as f: + for line in f.readlines(): weight, path = line[:-1].split("\t", 1) path = decode(path, 'utf-8') self.data[path] = float(weight) - except (IOError, EOFError): - if not error_recovery and os.path.exists(self.filename + ".bak"): + elif os.path.exists(self.filename + '.bak'): + if not error_recovery: print('Problem with autojump database,\ trying to recover from backup...', file=sys.stderr) shutil.copy(self.filename + '.bak', self.filename) return self.load(True) else: - # TODO: migration code, will be removed in v22+ - # autojump_py last used in v17 + # migration code, autojump_py last used in v17 self.filename = get_db_file('autojump_py') if os.path.exists(self.filename): try: # fix to get optimised pickle in python < 3 import cPickle as pickle except ImportError: import pickle + try: - with open(self.filename, 'rb') as aj_file: + with open(self.filename, 'rb') as f: # encoding is only specified for Python 2 compatibility if sys.version_info[0] > 2: - self.data = pickle.load(aj_file, encoding="utf-8") + self.data = pickle.load(f, encoding="utf-8") else: - self.data = pickle.load(aj_file) + self.data = pickle.load(f) unicode_dict = {} for k, v in self.data.items(): print(k) @@ -163,7 +164,8 @@ class Database: def save(self): """ - Save database atomically and preserve backup. + Save database atomically and preserve backup, creating new database if + needed. """ # check file existence and permissions if ((not os.path.exists(self.filename)) or @@ -247,7 +249,6 @@ def options(): if(ARGS.add != os.path.expanduser("~")): db = Database(DB_FILE) db.add(decode(ARGS.add)) - db.save() return True if (ARGS.purge): diff --git a/tests/runtests.py b/tests/runtests.py index f25a44a..0381c08 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -6,10 +6,23 @@ from __future__ import division import autojump +import contextlib +import random import os +import shutil +import sys import tempfile import unittest +@contextlib.contextmanager +def no_stderr(): + savestderr = sys.stderr + class DevNull(object): + def write(self, _): pass + sys.stderr = DevNull() + yield + sys.stderr = savestderr + # test suite class TestAutojump(unittest.TestCase): @@ -18,6 +31,8 @@ class TestAutojump(unittest.TestCase): self.fd, self.fname = tempfile.mkstemp() self.db = autojump.Database(self.fname) + random.seed() + def tearDown(self): os.remove(self.fname) if os.path.isfile(self.fname + ".bak"): @@ -42,26 +57,86 @@ class TestAutojump(unittest.TestCase): self.db.decay() self.assertTrue(self.db.get_weight('/1') < 10) - def test_db_load_empty(self): - pass - - def test_db_load_backup(self): - pass - - def test_db_load_migrate(self): - pass - def test_db_load_existing(self): self.db = autojump.Database('tests/database.txt') self.assertTrue(len(self.db) > 0) + def test_db_load_empty(self): + # setup + _, fname = tempfile.mkstemp() + db = autojump.Database(fname) + + try: + # test + self.assertEquals(len(self.db), 0) + finally: + # teardown + os.remove(fname) + + def test_db_load_backup(self): + # setup + fname = '/tmp/autojump_test_db_load_backup_' + str(random.randint(0,32678)) + db = autojump.Database(fname) + db.add('/1') + os.rename(fname, fname + '.bak') + + try: + # test + with no_stderr(): + db = autojump.Database(fname) + self.assertTrue(len(db.data) > 0) + self.assertTrue(os.path.isfile(fname)) + finally: + # teardown + os.remove(fname) + os.remove(fname + '.bak') + + def test_db_load_migrate(self): + ORIG_CONFIG_DIR = autojump.CONFIG_DIR + try: + # setup + CONFIG_DIR = '/tmp/autojump_test_db_load_migrate_' + str(random.randint(0,32678)) + os.mkdir(CONFIG_DIR) + autojump.CONFIG_DIR = CONFIG_DIR + fname = CONFIG_DIR + '/autojump_py' + db = autojump.Database(fname) + db.add('/1') + shutil.copy(fname, fname + '.bak') + db.add('/2') + + # test + missing_fname = '/tmp/autojump_test_db_load_missing_' + str(random.randint(0,32678)) + '.txt' + db = autojump.Database(missing_fname) + + db.add('/3') + self.assertEquals(len(db.data), 3) + + finally: + # teardown + shutil.rmtree(CONFIG_DIR) + os.remove(missing_fname) + os.remove(missing_fname + '.bak') + + autojump.CONFIG_DIR = ORIG_CONFIG_DIR + def test_db_purge(self): self.db.add('/1') self.db.purge() self.assertEquals(len(self.db), 0) def test_db_save(self): - pass + # setup + fname = '/tmp/autojump_test_db_save_' + str(random.randint(0,32678)) + '.txt' + db = autojump.Database(fname) + + try: + # test + db.save() + self.assertTrue(os.path.isfile(fname)) + finally: + # teardown + os.remove(fname) + os.remove(fname + '.bak') def test_db_trim(self): self.db.add('/1')