Commit 5e4c2e48 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Make sure that text identifier creation is deterministic.

Text identifiers were calculated with the hash() builtin. The problem is
that hash() returns different values depending on Python's major
version or 32 vs 64 bit. With Python >=3.3, the seed for the hash
function is always different (for security).

Instead of relying on hash(), use the sha1() hash to obtain a text
identifier.
parent 2d3816bd
......@@ -166,11 +166,11 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand(["Foo"], self.todolist, self.out, self.error)
command.execute()
command = AddCommand.AddCommand(["Bar after:tpi"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar after:7ui"], self.todolist, self.out, self.error)
command.execute()
self.assertEquals(self.todolist.todo('tpi').source(), "{} Foo p:1".format(self.today))
self.assertEquals(self.todolist.todo('b0n').source(), "{} Bar id:1".format(self.today))
self.assertEquals(self.todolist.todo('7ui').source(), "{} Foo p:1".format(self.today))
self.assertEquals(self.todolist.todo('8to').source(), "{} Bar id:1".format(self.today))
def test_add_dep9(self):
"""
......@@ -184,13 +184,13 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand(["Foo +Project"], self.todolist, lambda t: t, self.error)
command.execute()
command = AddCommand.AddCommand(["Bar before:eqk"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar before:kh0"], self.todolist, self.out, self.error)
command.execute()
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out, self.error)
command.execute()
self.assertEquals(self.output, "|5dh| {today} Bar p:1 +Project\n|5dh| {today} Bar +Project\n".format(today=self.today))
self.assertEquals(self.output, "|kbn| {today} Bar p:1 +Project\n|kbn| {today} Bar +Project\n".format(today=self.today))
def test_add_dep10(self):
"""
......@@ -204,13 +204,13 @@ class AddCommandTest(CommandTest):
command = AddCommand.AddCommand(["Foo @Context"], self.todolist, lambda t: t, self.error)
command.execute()
command = AddCommand.AddCommand(["Bar before:x2k"], self.todolist, self.out, self.error)
command = AddCommand.AddCommand(["Bar before:2a2"], self.todolist, self.out, self.error)
command.execute()
command = ListCommand.ListCommand(["Bar"], self.todolist, self.out, self.error)
command.execute()
self.assertEquals(self.output, "|5dc| {today} Bar p:1 @Context\n|5dc| {today} Bar @Context\n".format(today=self.today))
self.assertEquals(self.output, "|wb3| {today} Bar p:1 @Context\n|wb3| {today} Bar @Context\n".format(today=self.today))
def test_add_reldate1(self):
command = AddCommand.AddCommand(["Foo due:today"], self.todolist, self.out, self.error)
......
......@@ -112,7 +112,7 @@ class DeleteCommandTest(CommandTest):
""" Test deletion with textual IDs. """
config("test/data/todolist-uid.conf")
command = DeleteCommand(["b0n"], self.todolist, self.out, self.error)
command = DeleteCommand(["8to"], self.todolist, self.out, self.error)
command.execute()
self.assertEquals(str(self.todolist), "Foo")
......
......@@ -151,7 +151,7 @@ class ListCommandTest(CommandTest):
command.execute()
self.assertFalse(self.todolist.is_dirty())
self.assertEquals(self.output, "|6iu| (C) Foo @Context2 Not@Context +Project1 Not+Project\n|til| (C) Drink beer @ home\n| c5| (C) 13 + 29 = 42\n|xvb| (D) Bar @Context1 +Project2\n")
self.assertEquals(self.output, "|t5c| (C) Foo @Context2 Not@Context +Project1 Not+Project\n|wa5| (C) Drink beer @ home\n|z63| (C) 13 + 29 = 42\n|mfg| (D) Bar @Context1 +Project2\n")
self.assertEquals(self.errors, "")
def test_list17(self):
......
......@@ -43,10 +43,10 @@ class SortCommandTest(CommandTest):
""" Check that order does not influence the UID of a todo. """
config("test/data/todolist-uid.conf")
todo1 = self.todolist.todo('tpi')
todo1 = self.todolist.todo('7ui')
command = SortCommand(["text"], self.todolist, self.out, self.error)
command.execute()
todo2 = self.todolist.todo('tpi')
todo2 = self.todolist.todo('7ui')
self.assertEquals(todo1.source(), todo2.source())
......
......@@ -198,15 +198,15 @@ class TodoListTester(TopydoTest):
def test_uid1(self):
config("test/data/todolist-uid.conf")
self.assertEquals(self.todolist.todo('6iu').source(), "(C) Foo @Context2 Not@Context +Project1 Not+Project")
self.assertEquals(self.todolist.todo('t5c').source(), "(C) Foo @Context2 Not@Context +Project1 Not+Project")
def test_uid2(self):
""" Changing the priority should not change the identifier. """
config("test/data/todolist-uid.conf")
todo = self.todolist.todo('6iu')
todo = self.todolist.todo('t5c')
self.todolist.set_priority(todo, 'B')
self.assertEquals(self.todolist.todo('6iu').source(), "(B) Foo @Context2 Not@Context +Project1 Not+Project")
self.assertEquals(self.todolist.todo('t5c').source(), "(B) Foo @Context2 Not@Context +Project1 Not+Project")
def test_uid3(self):
"""
......@@ -220,10 +220,10 @@ class TodoListTester(TopydoTest):
""" Make sure that item has new text ID after append. """
config("test/data/todolist-uid.conf")
todo = self.todolist.todo('6iu')
todo = self.todolist.todo('t5c')
self.todolist.append(todo, "A")
self.assertNotEquals(self.todolist.number(todo), '6iu')
self.assertNotEquals(self.todolist.number(todo), 't5c')
class TodoListDependencyTester(TopydoTest):
def setUp(self):
......
......@@ -19,6 +19,8 @@ Module that calculates identifiers for each item in a list, based on the hash
value of each item.
"""
from hashlib import sha1
_TABLE_SIZES = {
# we choose a large table size to reduce the chance of collisions.
3: 46649, # largest prime under zzz_36
......@@ -41,7 +43,7 @@ def _to_base36(p_value):
return base36 or alphabet[0]
def hash_list_values(p_list, p_hash=hash):
def hash_list_values(p_list, p_key=lambda i: i):
"""
Calculates a unique value for each item in the list, these can be used as
identifiers.
......@@ -61,8 +63,15 @@ def hash_list_values(p_list, p_hash=hash):
if len(p_list) < _TABLE_SIZES[3] * 0.01 else _TABLE_SIZES[4]
for item in p_list:
hash_value = p_hash(item) % size
# obtain the to-be-hashed value
raw_value = p_key(item)
# hash
hasher = sha1()
hasher.update(raw_value)
hash_value = int(hasher.hexdigest(), 16) % size
# resolve possible collisions
while hash_value in used:
hash_value = (hash_value + 1) % size
......
......@@ -249,7 +249,7 @@ class TodoListBase(object):
self._todo_id_map = {}
self._id_todo_map = {}
uids = hash_list_values(self._todos, lambda t: hash(t.text()))
uids = hash_list_values(self._todos, lambda t: t.text())
for (todo, uid) in uids:
self._todo_id_map[todo] = uid
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment