Merge pull request #537 from apple/release-5.2
Merge Release-5.2 into master
This commit is contained in:
commit
ea8a288a20
|
@ -28,7 +28,7 @@ from bindingtester import util
|
|||
from bindingtester.tests import Test, Instruction, InstructionSet, ResultSpecification
|
||||
from bindingtester.tests import test_util, directory_util
|
||||
|
||||
from bindingtester.tests.directory_util import DirListEntry
|
||||
from bindingtester.tests.directory_state_tree import DirectoryStateTreeNode
|
||||
|
||||
fdb.api_version(FDB_API_VERSION)
|
||||
|
||||
|
@ -48,12 +48,12 @@ class DirectoryTest(Test):
|
|||
def ensure_default_directory_subspace(self, instructions, path):
|
||||
directory_util.create_default_directory_subspace(instructions, path, self.random)
|
||||
|
||||
child = self.root.add_child((path,), path, self.root, DirListEntry(True, True))
|
||||
child = self.root.add_child(path, DirectoryStateTreeNode(True, True, has_known_prefix=True))
|
||||
self.dir_list.append(child)
|
||||
self.dir_index = directory_util.DEFAULT_DIRECTORY_INDEX
|
||||
|
||||
def generate_layer(self):
|
||||
if random.random < 0.7:
|
||||
if random.random() < 0.7:
|
||||
return ''
|
||||
else:
|
||||
choice = random.randint(0, 3)
|
||||
|
@ -114,29 +114,34 @@ class DirectoryTest(Test):
|
|||
instructions.push_args(layer)
|
||||
instructions.push_args(*test_util.with_length(path))
|
||||
instructions.append('DIRECTORY_OPEN')
|
||||
# print '%d. Selected %s, dir=%s, has_known_prefix=%s, dir_list_len=%d' \
|
||||
# % (len(instructions), 'DIRECTORY_OPEN', repr(self.dir_index), False, len(self.dir_list))
|
||||
self.dir_list.append(self.dir_list[0].add_child(path, default_path, self.root, DirListEntry(True, True, has_known_prefix=False)))
|
||||
self.dir_list.append(self.root.add_child(path, DirectoryStateTreeNode(True, True, has_known_prefix=False)))
|
||||
# print('%d. Selected %s, dir=%s, dir_id=%s, has_known_prefix=%s, dir_list_len=%d' \
|
||||
# % (len(instructions), 'DIRECTORY_OPEN', repr(self.dir_index), self.dir_list[-1].dir_id, False, len(self.dir_list)-1))
|
||||
|
||||
instructions.setup_complete()
|
||||
|
||||
for i in range(args.num_ops):
|
||||
if random.random() < 0.5:
|
||||
self.dir_index = random.randrange(0, len(self.dir_list))
|
||||
while True:
|
||||
self.dir_index = random.randrange(0, len(self.dir_list))
|
||||
if not self.dir_list[self.dir_index].state.is_partition or not self.dir_list[self.dir_index].state.deleted:
|
||||
break
|
||||
|
||||
instructions.push_args(self.dir_index)
|
||||
instructions.append('DIRECTORY_CHANGE')
|
||||
|
||||
dir_entry = self.dir_list[self.dir_index]
|
||||
|
||||
choices = op_choices[:]
|
||||
if self.dir_list[self.dir_index].is_directory:
|
||||
if dir_entry.state.is_directory:
|
||||
choices += directory
|
||||
if self.dir_list[self.dir_index].is_subspace:
|
||||
if dir_entry.state.is_subspace:
|
||||
choices += subspace
|
||||
|
||||
op = random.choice(choices)
|
||||
dir_entry = self.dir_list[self.dir_index]
|
||||
|
||||
# print '%d. Selected %s, dir=%s, has_known_prefix=%s, dir_list_len=%d' \
|
||||
# % (len(instructions), op, repr(self.dir_index), repr(dir_entry.has_known_prefix), len(self.dir_list))
|
||||
# print('%d. Selected %s, dir=%d, dir_id=%d, has_known_prefix=%d, dir_list_len=%d' \
|
||||
# % (len(instructions), op, self.dir_index, dir_entry.dir_id, dir_entry.state.has_known_prefix, len(self.dir_list)))
|
||||
|
||||
if op.endswith('_DATABASE') or op.endswith('_SNAPSHOT'):
|
||||
root_op = op[0:-9]
|
||||
|
@ -151,24 +156,26 @@ class DirectoryTest(Test):
|
|||
|
||||
elif root_op == 'DIRECTORY_CREATE_SUBSPACE':
|
||||
path = generate_path()
|
||||
instructions.push_args(generate_prefix(allow_empty=False, is_partition=True))
|
||||
instructions.push_args(generate_prefix(require_unique=False, is_partition=True))
|
||||
instructions.push_args(*test_util.with_length(path))
|
||||
instructions.append(op)
|
||||
self.dir_list.append(DirListEntry(False, True))
|
||||
self.dir_list.append(DirectoryStateTreeNode(False, True, has_known_prefix=True))
|
||||
|
||||
elif root_op == 'DIRECTORY_CREATE_LAYER':
|
||||
indices = []
|
||||
|
||||
prefixes = [generate_prefix(require_unique=args.concurrency==1, is_partition=True) for i in range(2)]
|
||||
for i in range(2):
|
||||
instructions.push_args(generate_prefix(allow_empty=False, is_partition=True))
|
||||
instructions.push_args(prefixes[i])
|
||||
instructions.push_args(*test_util.with_length(generate_path()))
|
||||
instructions.append('DIRECTORY_CREATE_SUBSPACE')
|
||||
indices.append(len(self.dir_list))
|
||||
self.dir_list.append(DirListEntry(False, True))
|
||||
self.dir_list.append(DirectoryStateTreeNode(False, True, has_known_prefix=True))
|
||||
|
||||
instructions.push_args(random.choice([0, 1]))
|
||||
instructions.push_args(*indices)
|
||||
instructions.append(op)
|
||||
self.dir_list.append(DirListEntry(True, False, False))
|
||||
self.dir_list.append(DirectoryStateTreeNode.get_layer(prefixes[0]))
|
||||
|
||||
elif root_op == 'DIRECTORY_CREATE_OR_OPEN':
|
||||
# Because allocated prefixes are non-deterministic, we cannot have overlapping
|
||||
|
@ -183,14 +190,18 @@ class DirectoryTest(Test):
|
|||
if not op.endswith('_DATABASE') and args.concurrency == 1:
|
||||
test_util.blocking_commit(instructions)
|
||||
|
||||
self.dir_list.append(dir_entry.add_child(path, default_path, self.root, DirListEntry(True, True, False)))
|
||||
child_entry = dir_entry.get_descendent(path)
|
||||
if child_entry is None:
|
||||
child_entry = DirectoryStateTreeNode(True, True)
|
||||
|
||||
child_entry.state.has_known_prefix = False
|
||||
self.dir_list.append(dir_entry.add_child(path, child_entry))
|
||||
|
||||
elif root_op == 'DIRECTORY_CREATE':
|
||||
layer = self.generate_layer()
|
||||
is_partition = layer == 'partition'
|
||||
|
||||
allow_empty_prefix = random.random() < 0.8
|
||||
prefix = generate_prefix(allow_empty=allow_empty_prefix, is_partition=is_partition)
|
||||
prefix = generate_prefix(require_unique=is_partition and args.concurrency==1, is_partition=is_partition, min_length=0)
|
||||
|
||||
# Because allocated prefixes are non-deterministic, we cannot have overlapping
|
||||
# transactions that allocate/remove these prefixes in a comparison test
|
||||
|
@ -209,40 +220,59 @@ class DirectoryTest(Test):
|
|||
if not op.endswith('_DATABASE') and args.concurrency == 1: # and allow_empty_prefix:
|
||||
test_util.blocking_commit(instructions)
|
||||
|
||||
self.dir_list.append(dir_entry.add_child(path, default_path, self.root, DirListEntry(True, True, bool(prefix))))
|
||||
child_entry = dir_entry.get_descendent(path)
|
||||
if child_entry is None:
|
||||
child_entry = DirectoryStateTreeNode(True, True, has_known_prefix=bool(prefix))
|
||||
elif not bool(prefix):
|
||||
child_entry.state.has_known_prefix = False
|
||||
|
||||
if is_partition:
|
||||
child_entry.state.is_partition = True
|
||||
|
||||
self.dir_list.append(dir_entry.add_child(path, child_entry))
|
||||
|
||||
elif root_op == 'DIRECTORY_OPEN':
|
||||
path = generate_path()
|
||||
instructions.push_args(self.generate_layer())
|
||||
instructions.push_args(*test_util.with_length(path))
|
||||
instructions.append(op)
|
||||
self.dir_list.append(dir_entry.add_child(path, default_path, self.root, DirListEntry(True, True)))
|
||||
|
||||
child_entry = dir_entry.get_descendent(path)
|
||||
if child_entry is None:
|
||||
self.dir_list.append(DirectoryStateTreeNode(False, False, has_known_prefix=False))
|
||||
else:
|
||||
self.dir_list.append(dir_entry.add_child(path, child_entry))
|
||||
|
||||
elif root_op == 'DIRECTORY_MOVE':
|
||||
old_path = generate_path()
|
||||
new_path = generate_path()
|
||||
instructions.push_args(*(test_util.with_length(old_path) + test_util.with_length(new_path)))
|
||||
instructions.append(op)
|
||||
# This could probably be made to sometimes set has_known_prefix to true
|
||||
self.dir_list.append(dir_entry.add_child(new_path, default_path, self.root, DirListEntry(True, True, False)))
|
||||
|
||||
child_entry = dir_entry.get_descendent(old_path)
|
||||
if child_entry is None:
|
||||
self.dir_list.append(DirectoryStateTreeNode(False, False, has_known_prefix=False))
|
||||
else:
|
||||
self.dir_list.append(dir_entry.add_child(new_path, child_entry))
|
||||
|
||||
# Make sure that the default directory subspace still exists after moving the specified directory
|
||||
if dir_entry.is_directory and not dir_entry.is_subspace and old_path == (u'',):
|
||||
if dir_entry.state.is_directory and not dir_entry.state.is_subspace and old_path == (u'',):
|
||||
self.ensure_default_directory_subspace(instructions, default_path)
|
||||
|
||||
elif root_op == 'DIRECTORY_MOVE_TO':
|
||||
new_path = generate_path()
|
||||
instructions.push_args(*test_util.with_length(new_path))
|
||||
instructions.append(op)
|
||||
self.dir_list.append(dir_entry.root.add_child(new_path, default_path, self.root,
|
||||
DirListEntry(True, True, dir_entry.has_known_prefix)))
|
||||
|
||||
child_entry = dir_entry.get_descendent(())
|
||||
if child_entry is None:
|
||||
self.dir_list.append(DirectoryStateTreeNode(False, False, has_known_prefix=False))
|
||||
else:
|
||||
self.dir_list.append(dir_entry.add_child(new_path, child_entry))
|
||||
|
||||
# Make sure that the default directory subspace still exists after moving the current directory
|
||||
self.ensure_default_directory_subspace(instructions, default_path)
|
||||
|
||||
# FIXME: There is currently a problem with removing partitions. In these generated tests, it's possible
|
||||
# for a removed partition to resurrect itself and insert keys into the database using its allocated
|
||||
# prefix. The result is non-deterministic HCA errors.
|
||||
elif root_op == 'DIRECTORY_REMOVE' or root_op == 'DIRECTORY_REMOVE_IF_EXISTS':
|
||||
# Because allocated prefixes are non-deterministic, we cannot have overlapping
|
||||
# transactions that allocate/remove these prefixes in a comparison test
|
||||
|
@ -254,12 +284,14 @@ class DirectoryTest(Test):
|
|||
if count == 1:
|
||||
path = generate_path()
|
||||
instructions.push_args(*test_util.with_length(path))
|
||||
instructions.push_args(count)
|
||||
|
||||
instructions.push_args(count)
|
||||
instructions.append(op)
|
||||
|
||||
dir_entry.delete(path)
|
||||
|
||||
# Make sure that the default directory subspace still exists after removing the specified directory
|
||||
if path == () or (dir_entry.is_directory and not dir_entry.is_subspace and path == (u'',)):
|
||||
if path == () or (dir_entry.state.is_directory and not dir_entry.state.is_subspace and path == (u'',)):
|
||||
self.ensure_default_directory_subspace(instructions, default_path)
|
||||
|
||||
elif root_op == 'DIRECTORY_LIST' or root_op == 'DIRECTORY_EXISTS':
|
||||
|
@ -278,7 +310,7 @@ class DirectoryTest(Test):
|
|||
instructions.append('DIRECTORY_STRIP_PREFIX')
|
||||
|
||||
elif root_op == 'DIRECTORY_UNPACK_KEY' or root_op == 'DIRECTORY_CONTAINS':
|
||||
if not dir_entry.has_known_prefix or random.random() < 0.2 or root_op == 'DIRECTORY_UNPACK_KEY':
|
||||
if not dir_entry.state.has_known_prefix or random.random() < 0.2 or root_op == 'DIRECTORY_UNPACK_KEY':
|
||||
t = self.random.random_tuple(5)
|
||||
instructions.push_args(*test_util.with_length(t))
|
||||
instructions.append('DIRECTORY_PACK_KEY')
|
||||
|
@ -292,7 +324,7 @@ class DirectoryTest(Test):
|
|||
instructions.push_args(*test_util.with_length(t))
|
||||
instructions.append(op)
|
||||
if root_op == 'DIRECTORY_OPEN_SUBSPACE':
|
||||
self.dir_list.append(DirListEntry(False, True, dir_entry.has_known_prefix))
|
||||
self.dir_list.append(DirectoryStateTreeNode(False, True, dir_entry.state.has_known_prefix))
|
||||
else:
|
||||
test_util.to_front(instructions, 1)
|
||||
instructions.append('DIRECTORY_STRIP_PREFIX')
|
||||
|
@ -308,16 +340,18 @@ class DirectoryTest(Test):
|
|||
for i, dir_entry in enumerate(self.dir_list):
|
||||
instructions.push_args(i)
|
||||
instructions.append('DIRECTORY_CHANGE')
|
||||
if dir_entry.is_directory:
|
||||
if dir_entry.state.is_directory:
|
||||
instructions.push_args(self.directory_log.key())
|
||||
instructions.append('DIRECTORY_LOG_DIRECTORY')
|
||||
if dir_entry.has_known_prefix and dir_entry.is_subspace:
|
||||
# print '%d. Logging subspace: %d' % (i, dir_entry.dir_id)
|
||||
if dir_entry.state.has_known_prefix and dir_entry.state.is_subspace:
|
||||
# print('%d. Logging subspace: %d' % (i, dir_entry.dir_id))
|
||||
instructions.push_args(self.subspace_log.key())
|
||||
instructions.append('DIRECTORY_LOG_SUBSPACE')
|
||||
if (i + 1) % 100 == 0:
|
||||
test_util.blocking_commit(instructions)
|
||||
|
||||
test_util.blocking_commit(instructions)
|
||||
|
||||
instructions.push_args(self.stack_subspace.key())
|
||||
instructions.append('LOG_STACK')
|
||||
|
||||
|
@ -365,11 +399,15 @@ def generate_path(min_length=0):
|
|||
return path
|
||||
|
||||
|
||||
def generate_prefix(allow_empty=True, is_partition=False):
|
||||
if allow_empty and random.random() < 0.8:
|
||||
def generate_prefix(require_unique=False, is_partition=False, min_length=1):
|
||||
fixed_prefix = 'abcdefg'
|
||||
if not require_unique and min_length == 0 and random.random() < 0.8:
|
||||
return None
|
||||
elif is_partition or random.random() < 0.5:
|
||||
length = random.randint(0 if allow_empty else 1, 5)
|
||||
elif require_unique or is_partition or min_length > len(fixed_prefix) or random.random() < 0.5:
|
||||
if require_unique:
|
||||
min_length = max(min_length, 16)
|
||||
|
||||
length = random.randint(min_length, min_length+5)
|
||||
if length == 0:
|
||||
return ''
|
||||
|
||||
|
@ -379,6 +417,6 @@ def generate_prefix(allow_empty=True, is_partition=False):
|
|||
else:
|
||||
return ''.join(chr(random.randrange(ord('\x02'), ord('\x14'))) for i in range(0, length))
|
||||
else:
|
||||
prefix = 'abcdefg'
|
||||
generated = prefix[0:random.randrange(0 if allow_empty else 1, len(prefix))]
|
||||
prefix = fixed_prefix
|
||||
generated = prefix[0:random.randrange(min_length, len(prefix))]
|
||||
return generated
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
import sys
|
||||
|
||||
class TreeNodeState:
|
||||
def __init__(self, node, dir_id, is_directory, is_subspace, has_known_prefix, root, is_partition):
|
||||
self.dir_id = dir_id
|
||||
self.is_directory = is_directory
|
||||
self.is_subspace = is_subspace
|
||||
self.has_known_prefix = has_known_prefix
|
||||
self.root = root
|
||||
self.is_partition = is_partition
|
||||
|
||||
self.parents = { node }
|
||||
self.children = {}
|
||||
self.deleted = False
|
||||
|
||||
# Represents an element of the directory hierarchy. As a result of various operations (e.g. moves) that
|
||||
# may or may not have succeeded, a node can represent multiple possible states.
|
||||
class DirectoryStateTreeNode:
|
||||
# A cache of directory layers. We mustn't have multiple entries for the same layer
|
||||
layers = {}
|
||||
|
||||
# Because our operations may be applied to the default directory in the case that
|
||||
# the current directory failed to open/create, we compute the result of each operation
|
||||
# as if it was performed on the current directory and the default directory.
|
||||
default_directory = None
|
||||
|
||||
# Used for debugging
|
||||
dir_id = 0
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
cls.dir_id = 0
|
||||
cls.layers = {}
|
||||
cls.default_directory = None
|
||||
|
||||
@classmethod
|
||||
def set_default_directory(cls, default_directory):
|
||||
cls.default_directory = default_directory
|
||||
|
||||
@classmethod
|
||||
def get_layer(cls, node_subspace_prefix):
|
||||
if node_subspace_prefix not in DirectoryStateTreeNode.layers:
|
||||
DirectoryStateTreeNode.layers[node_subspace_prefix] = DirectoryStateTreeNode(True, False, has_known_prefix=False)
|
||||
|
||||
return DirectoryStateTreeNode.layers[node_subspace_prefix]
|
||||
|
||||
def __init__(self, is_directory, is_subspace, has_known_prefix=True, root=None, is_partition=False):
|
||||
self.state = TreeNodeState(self, DirectoryStateTreeNode.dir_id + 1, is_directory, is_subspace, has_known_prefix,
|
||||
root or self, is_partition)
|
||||
DirectoryStateTreeNode.dir_id += 1
|
||||
|
||||
def __repr__(self):
|
||||
return '{DirEntry %d: %d}' % (self.state.dir_id, self.state.has_known_prefix)
|
||||
|
||||
def _get_descendent(self, subpath, default):
|
||||
if not subpath:
|
||||
if default is not None:
|
||||
self._merge(default)
|
||||
return self
|
||||
|
||||
default_child = None
|
||||
if default is not None:
|
||||
default_child = default.state.children.get(subpath[0])
|
||||
|
||||
self_child = self.state.children.get(subpath[0])
|
||||
|
||||
if self_child is None:
|
||||
if default_child is None:
|
||||
return None
|
||||
else:
|
||||
return default_child._get_descendent(subpath[1:], None)
|
||||
|
||||
return self_child._get_descendent(subpath[1:], default_child)
|
||||
|
||||
def get_descendent(self, subpath):
|
||||
return self._get_descendent(subpath, DirectoryStateTreeNode.default_directory)
|
||||
|
||||
def add_child(self, subpath, child):
|
||||
child.state.root = self.state.root
|
||||
if DirectoryStateTreeNode.default_directory:
|
||||
# print('Adding child %r to default directory at %r' % (child, subpath))
|
||||
child = DirectoryStateTreeNode.default_directory._add_child_impl(subpath, child)
|
||||
# print('Added %r' % child)
|
||||
|
||||
# print('Adding child %r to directory at %r' % (child, subpath))
|
||||
c = self._add_child_impl(subpath, child)
|
||||
|
||||
# print('Added %r' % c)
|
||||
return c
|
||||
|
||||
def _add_child_impl(self, subpath, child):
|
||||
# print('%d, %d. Adding child %r (recursive): %r' % (self.state.dir_id, child.state.dir_id, child, subpath))
|
||||
if len(subpath) == 0:
|
||||
# print('%d, %d. Setting child: %d, %d' % (self.state.dir_id, child.state.dir_id, self.state.has_known_prefix, child.state.has_known_prefix))
|
||||
self._merge(child)
|
||||
return self
|
||||
else:
|
||||
if not subpath[0] in self.state.children:
|
||||
# print('%d, %d. Path %r was absent from %r (%r)' % (self.state.dir_id, child.state.dir_id, subpath[0:1], self, self.state.children))
|
||||
subdir = DirectoryStateTreeNode(True, True, root=self.state.root)
|
||||
self.state.children[subpath[0]] = subdir
|
||||
else:
|
||||
subdir = self.state.children[subpath[0]]
|
||||
# print('%d, %d. Path was present' % (self.state.dir_id, child.state.dir_id))
|
||||
|
||||
if len(subpath) > 1:
|
||||
subdir.state.has_known_prefix = False
|
||||
|
||||
return subdir._add_child_impl(subpath[1:], child)
|
||||
|
||||
def _merge(self, other):
|
||||
if self.state.dir_id == other.state.dir_id:
|
||||
return
|
||||
|
||||
self.dir_id = other.dir_id
|
||||
self.state.dir_id = min(other.state.dir_id, self.state.dir_id)
|
||||
self.state.is_directory = self.state.is_directory and other.state.is_directory
|
||||
self.state.is_subspace = self.state.is_subspace and other.state.is_subspace
|
||||
self.state.has_known_prefix = self.state.has_known_prefix and other.state.has_known_prefix
|
||||
self.state.deleted = self.state.deleted or other.state.deleted
|
||||
self.state.is_partition = self.state.is_partition or other.state.is_partition
|
||||
|
||||
other_children = other.state.children.copy()
|
||||
other_parents = other.state.parents.copy()
|
||||
|
||||
for node in other_parents:
|
||||
node.state = self.state
|
||||
self.state.parents.add(node)
|
||||
|
||||
for c in other_children:
|
||||
if c not in self.state.children:
|
||||
self.state.children[c] = other_children[c]
|
||||
else:
|
||||
self.state.children[c]._merge(other_children[c])
|
||||
|
||||
def _delete_impl(self):
|
||||
if not self.state.deleted:
|
||||
self.state.deleted = True
|
||||
for c in self.state.children.values():
|
||||
c._delete_impl()
|
||||
|
||||
def delete(self, path):
|
||||
child = self.get_descendent(path)
|
||||
if child:
|
||||
child._delete_impl()
|
||||
|
||||
def validate_dir(dir, root):
|
||||
if dir.state.is_directory:
|
||||
assert dir.state.root == root
|
||||
else:
|
||||
assert dir.state.root == dir
|
||||
|
||||
def run_test():
|
||||
all_entries = []
|
||||
|
||||
root = DirectoryStateTreeNode.get_layer('\xfe')
|
||||
all_entries.append(root)
|
||||
|
||||
default_dir = root.add_child(('default',), DirectoryStateTreeNode(True, True, has_known_prefix=True))
|
||||
DirectoryStateTreeNode.set_default_directory(default_dir)
|
||||
all_entries.append(default_dir)
|
||||
|
||||
all_entries.append(default_dir.add_child(('1',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
all_entries.append(default_dir.add_child(('1', '1'), DirectoryStateTreeNode(True, False, has_known_prefix=True)))
|
||||
all_entries.append(default_dir.add_child(('2',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
all_entries.append(default_dir.add_child(('3',), DirectoryStateTreeNode(True, True, has_known_prefix=False)))
|
||||
all_entries.append(default_dir.add_child(('5',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
all_entries.append(default_dir.add_child(('3', '1'), DirectoryStateTreeNode(True, True, has_known_prefix=False)))
|
||||
all_entries.append(default_dir.add_child(('1', '3'), DirectoryStateTreeNode(True, True, has_known_prefix=False)))
|
||||
|
||||
entry = all_entries[-1]
|
||||
child_entries = []
|
||||
child_entries.append(entry.add_child(('1',), DirectoryStateTreeNode(True, False, has_known_prefix=True)))
|
||||
child_entries.append(entry.add_child(('2',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
child_entries.append(entry.add_child(('3',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
child_entries.append(entry.add_child(('4',), DirectoryStateTreeNode(True, False, has_known_prefix=False)))
|
||||
child_entries.append(entry.add_child(('5',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
|
||||
all_entries.append(root.add_child(('1', '2'), DirectoryStateTreeNode(True, True, has_known_prefix=False)))
|
||||
all_entries.append(root.add_child(('2',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
all_entries.append(root.add_child(('3',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
all_entries.append(root.add_child(('1', '3',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
|
||||
# This directory was merged with the default, but both have readable prefixes
|
||||
entry = root.get_descendent(('2',))
|
||||
assert entry.state.has_known_prefix
|
||||
|
||||
entry = all_entries[-1]
|
||||
all_entries.append(entry.add_child(('1',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
all_entries.append(entry.add_child(('2',), DirectoryStateTreeNode(True, True, has_known_prefix=False)))
|
||||
all_entries.append(entry.add_child(('3',), DirectoryStateTreeNode(True, False, has_known_prefix=True)))
|
||||
|
||||
entry_to_move = all_entries[-1]
|
||||
|
||||
all_entries.append(entry.add_child(('5',), DirectoryStateTreeNode(True, False, has_known_prefix=True)))
|
||||
child_entries.append(entry.add_child(('6',), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
|
||||
all_entries.extend(child_entries)
|
||||
|
||||
# This directory has an unknown prefix
|
||||
entry = root.get_descendent(('1', '2'))
|
||||
assert not entry.state.has_known_prefix
|
||||
|
||||
# This directory was default created and should have an unknown prefix
|
||||
# It will merge with the default directory's child, which is not a subspace
|
||||
entry = root.get_descendent(('1',))
|
||||
assert not entry.state.has_known_prefix
|
||||
assert not entry.state.is_subspace
|
||||
|
||||
# Multiple merges will have made this prefix unreadable
|
||||
entry = root.get_descendent(('2',))
|
||||
assert not entry.state.has_known_prefix
|
||||
|
||||
# Merge with default directory's child that has an unknown prefix
|
||||
entry = root.get_descendent(('3',))
|
||||
assert not entry.state.has_known_prefix
|
||||
|
||||
# Merge with default directory's child that has an unknown prefix and merged children
|
||||
entry = root.get_descendent(('1', '3'))
|
||||
assert set(entry.state.children.keys()) == {'1', '2', '3', '4', '5', '6'}
|
||||
|
||||
# This child entry should be the combination of ['default', '3'], ['default', '1', '3'], and ['1', '3']
|
||||
entry = entry.get_descendent(('3',))
|
||||
assert not entry.state.has_known_prefix
|
||||
assert not entry.state.is_subspace
|
||||
|
||||
# Verify the merge of the children
|
||||
assert not child_entries[0].state.has_known_prefix
|
||||
assert not child_entries[0].state.is_subspace
|
||||
|
||||
assert not child_entries[1].state.has_known_prefix
|
||||
assert child_entries[1].state.is_subspace
|
||||
|
||||
assert not child_entries[2].state.has_known_prefix
|
||||
assert not child_entries[2].state.is_subspace
|
||||
|
||||
assert not child_entries[3].state.has_known_prefix
|
||||
assert not child_entries[3].state.is_subspace
|
||||
|
||||
assert child_entries[4].state.has_known_prefix
|
||||
assert not child_entries[4].state.is_subspace
|
||||
|
||||
assert child_entries[5].state.has_known_prefix
|
||||
assert child_entries[5].state.is_subspace
|
||||
|
||||
entry = root.add_child(('3',), entry_to_move)
|
||||
all_entries.append(entry)
|
||||
|
||||
# Test moving an entry
|
||||
assert not entry.state.has_known_prefix
|
||||
assert not entry.state.is_subspace
|
||||
assert entry.state.children.keys() == ['1']
|
||||
|
||||
for e in all_entries:
|
||||
validate_dir(e, root)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(run_test())
|
||||
|
|
@ -27,6 +27,7 @@ from bindingtester import FDB_API_VERSION
|
|||
from bindingtester import util
|
||||
|
||||
from bindingtester.tests import test_util
|
||||
from bindingtester.tests.directory_state_tree import DirectoryStateTreeNode
|
||||
|
||||
fdb.api_version(FDB_API_VERSION)
|
||||
|
||||
|
@ -34,82 +35,26 @@ DEFAULT_DIRECTORY_INDEX = 4
|
|||
DEFAULT_DIRECTORY_PREFIX = 'default'
|
||||
DIRECTORY_ERROR_STRING = 'DIRECTORY_ERROR'
|
||||
|
||||
|
||||
class DirListEntry:
|
||||
dir_id = 0 # Used for debugging
|
||||
|
||||
def __init__(self, is_directory, is_subspace, has_known_prefix=True, path=(), root=None):
|
||||
self.root = root or self
|
||||
self.path = path
|
||||
self.is_directory = is_directory
|
||||
self.is_subspace = is_subspace
|
||||
self.has_known_prefix = has_known_prefix
|
||||
self.children = {}
|
||||
|
||||
self.dir_id = DirListEntry.dir_id + 1
|
||||
DirListEntry.dir_id += 1
|
||||
|
||||
def __repr__(self):
|
||||
return 'DirEntry %d %r: %d' % (self.dir_id, self.path, self.has_known_prefix)
|
||||
|
||||
def add_child(self, subpath, default_path, root, child):
|
||||
if default_path in root.children:
|
||||
# print 'Adding child %r to default directory %r at %r' % (child, root.children[DirectoryTest.DEFAULT_DIRECTORY_PATH].path, subpath)
|
||||
c = root.children[default_path]._add_child_impl(subpath, child)
|
||||
child.has_known_prefix = c.has_known_prefix and child.has_known_prefix
|
||||
# print 'Added %r' % c
|
||||
|
||||
# print 'Adding child %r to directory %r at %r' % (child, self.path, subpath)
|
||||
c = self._add_child_impl(subpath, child)
|
||||
# print 'Added %r' % c
|
||||
return c
|
||||
|
||||
def _add_child_impl(self, subpath, child):
|
||||
# print '%d, %d. Adding child (recursive): %s %s' % (self.dir_id, child.dir_id, repr(self.path), repr(subpath))
|
||||
if len(subpath) == 0:
|
||||
self.has_known_prefix = self.has_known_prefix and child.has_known_prefix
|
||||
# print '%d, %d. Setting child: %d' % (self.dir_id, child.dir_id, self.has_known_prefix)
|
||||
self._merge_children(child)
|
||||
|
||||
return self
|
||||
else:
|
||||
if not subpath[0] in self.children:
|
||||
# print '%d, %d. Path %s was absent (%s)' % (self.dir_id, child.dir_id, repr(self.path + subpath[0:1]), repr(self.children))
|
||||
subdir = DirListEntry(True, True, path=self.path + subpath[0:1], root=self.root)
|
||||
subdir.has_known_prefix = len(subpath) == 1
|
||||
self.children[subpath[0]] = subdir
|
||||
else:
|
||||
subdir = self.children[subpath[0]]
|
||||
subdir.has_known_prefix = False
|
||||
# print '%d, %d. Path was present' % (self.dir_id, child.dir_id)
|
||||
|
||||
return subdir._add_child_impl(subpath[1:], child)
|
||||
|
||||
def _merge_children(self, other):
|
||||
for c in other.children:
|
||||
if c not in self.children:
|
||||
self.children[c] = other.children[c]
|
||||
else:
|
||||
self.children[c].has_known_prefix = self.children[c].has_known_prefix and other.children[c].has_known_prefix
|
||||
self.children[c]._merge_children(other.children[c])
|
||||
|
||||
|
||||
def setup_directories(instructions, default_path, random):
|
||||
dir_list = [DirListEntry(True, False, True)]
|
||||
# Clients start with the default directory layer in the directory list
|
||||
DirectoryStateTreeNode.reset()
|
||||
dir_list = [DirectoryStateTreeNode.get_layer('\xfe')]
|
||||
|
||||
instructions.push_args(0, '\xfe')
|
||||
instructions.append('DIRECTORY_CREATE_SUBSPACE')
|
||||
dir_list.append(DirListEntry(False, True))
|
||||
dir_list.append(DirectoryStateTreeNode(False, True))
|
||||
|
||||
instructions.push_args(0, '')
|
||||
instructions.append('DIRECTORY_CREATE_SUBSPACE')
|
||||
dir_list.append(DirListEntry(False, True))
|
||||
dir_list.append(DirectoryStateTreeNode(False, True))
|
||||
|
||||
instructions.push_args(1, 2, 1)
|
||||
instructions.append('DIRECTORY_CREATE_LAYER')
|
||||
dir_list.append(DirListEntry(True, False, True))
|
||||
dir_list.append(DirectoryStateTreeNode.get_layer('\xfe'))
|
||||
|
||||
create_default_directory_subspace(instructions, default_path, random)
|
||||
dir_list.append(DirListEntry(True, True, True))
|
||||
dir_list.append(dir_list[0].add_child((default_path,), DirectoryStateTreeNode(True, True, has_known_prefix=True)))
|
||||
DirectoryStateTreeNode.set_default_directory(dir_list[-1])
|
||||
|
||||
instructions.push_args(DEFAULT_DIRECTORY_INDEX)
|
||||
instructions.append('DIRECTORY_SET_ERROR_INDEX')
|
||||
|
|
|
@ -170,7 +170,10 @@ class AsyncDirectoryExtension {
|
|||
.thenAccept(children -> inst.push(Tuple.fromItems(children).pack()));
|
||||
}
|
||||
else if(op == DirectoryOperation.DIRECTORY_EXISTS) {
|
||||
return inst.popParam()
|
||||
// In Java, DirectoryLayer.exists can return true without doing any reads.
|
||||
// Other bindings will always do a read, so we get a read version now to be compatible with that behavior.
|
||||
return inst.readTcx.readAsync(tr -> tr.getReadVersion())
|
||||
.thenComposeAsync(v -> inst.popParam())
|
||||
.thenComposeAsync(count -> DirectoryUtil.popPaths(inst, StackUtils.getInt(count)))
|
||||
.thenComposeAsync(path -> {
|
||||
if(path.size() == 0)
|
||||
|
|
|
@ -317,8 +317,8 @@ public class AsyncStackTester {
|
|||
if(t != null) {
|
||||
inst.context.newTransaction(oldTr); // Other bindings allow reuse of non-retryable transactions, so we need to emulate that behavior.
|
||||
}
|
||||
else {
|
||||
inst.setTransaction(oldTr, tr);
|
||||
else if(!inst.setTransaction(oldTr, tr)) {
|
||||
tr.close();
|
||||
}
|
||||
}).thenApply(v -> null);
|
||||
|
||||
|
|
|
@ -92,6 +92,7 @@ abstract class Context implements Runnable, AutoCloseable {
|
|||
|
||||
private static synchronized Transaction getTransaction(String trName) {
|
||||
Transaction tr = transactionMap.get(trName);
|
||||
assert tr != null : "Null transaction";
|
||||
addTransactionReference(tr);
|
||||
return tr;
|
||||
}
|
||||
|
@ -117,7 +118,15 @@ abstract class Context implements Runnable, AutoCloseable {
|
|||
}
|
||||
|
||||
private static synchronized boolean updateTransaction(String trName, Transaction oldTr, Transaction newTr) {
|
||||
if(transactionMap.replace(trName, oldTr, newTr)) {
|
||||
boolean added;
|
||||
if(oldTr == null) {
|
||||
added = (transactionMap.putIfAbsent(trName, newTr) == null);
|
||||
}
|
||||
else {
|
||||
added = transactionMap.replace(trName, oldTr, newTr);
|
||||
}
|
||||
|
||||
if(added) {
|
||||
addTransactionReference(newTr);
|
||||
releaseTransaction(oldTr);
|
||||
return true;
|
||||
|
|
|
@ -160,6 +160,11 @@ class DirectoryExtension {
|
|||
int count = StackUtils.getInt(inst.popParam().get());
|
||||
List<List<String>> path = DirectoryUtil.popPaths(inst, count).get();
|
||||
boolean exists;
|
||||
|
||||
// In Java, DirectoryLayer.exists can return true without doing any reads.
|
||||
// Other bindings will always do a read, so we get a read version now to be compatible with that behavior.
|
||||
inst.readTcx.read(tr -> tr.getReadVersion().join());
|
||||
|
||||
if(path.size() == 0)
|
||||
exists = directory().exists(inst.readTcx).get();
|
||||
else
|
||||
|
|
|
@ -72,16 +72,21 @@ class Instruction extends Stack {
|
|||
readTcx = isDatabase ? context.db : readTr;
|
||||
}
|
||||
|
||||
void setTransaction(Transaction newTr) {
|
||||
boolean setTransaction(Transaction newTr) {
|
||||
if(!isDatabase) {
|
||||
context.updateCurrentTransaction(newTr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void setTransaction(Transaction oldTr, Transaction newTr) {
|
||||
boolean setTransaction(Transaction oldTr, Transaction newTr) {
|
||||
if(!isDatabase) {
|
||||
context.updateCurrentTransaction(oldTr, newTr);
|
||||
return context.updateCurrentTransaction(oldTr, newTr);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void releaseTransaction() {
|
||||
|
|
|
@ -284,7 +284,10 @@ public class StackTester {
|
|||
FDBException err = new FDBException("Fake testing error", filteredError ? 1020 : errorCode);
|
||||
|
||||
try {
|
||||
inst.setTransaction(inst.tr.onError(err).join());
|
||||
Transaction tr = inst.tr.onError(err).join();
|
||||
if(!inst.setTransaction(tr)) {
|
||||
tr.close();
|
||||
}
|
||||
}
|
||||
catch(Throwable t) {
|
||||
inst.context.newTransaction(); // Other bindings allow reuse of non-retryable transactions, so we need to emulate that behavior.
|
||||
|
|
|
@ -102,71 +102,71 @@ class DirectoryExtension():
|
|||
new_dir = self.dir_list[self.dir_index]
|
||||
clazz = new_dir.__class__.__name__
|
||||
new_path = repr(new_dir._path) if hasattr(new_dir, '_path') else "<na>"
|
||||
print('changed directory to %d (%s @%s)' % (self.dir_index, clazz, new_path))
|
||||
print('changed directory to %d (%s @%r)' % (self.dir_index, clazz, new_path))
|
||||
elif inst.op == six.u('DIRECTORY_SET_ERROR_INDEX'):
|
||||
self.error_index = inst.pop()
|
||||
elif inst.op == six.u('DIRECTORY_CREATE_OR_OPEN'):
|
||||
path = self.pop_tuples(inst.stack)
|
||||
layer = inst.pop()
|
||||
log_op('create_or_open %s: layer=%s' % (repr(directory.get_path() + path), repr(layer)))
|
||||
log_op('create_or_open %r: layer=%r' % (directory.get_path() + path, layer))
|
||||
d = directory.create_or_open(inst.tr, path, layer or b'')
|
||||
self.append_dir(inst, d)
|
||||
elif inst.op == six.u('DIRECTORY_CREATE'):
|
||||
path = self.pop_tuples(inst.stack)
|
||||
layer, prefix = inst.pop(2)
|
||||
log_op('create %s: layer=%s, prefix=%s' % (repr(directory.get_path() + path), repr(layer), repr(prefix)))
|
||||
log_op('create %r: layer=%r, prefix=%r' % (directory.get_path() + path, layer, prefix))
|
||||
self.append_dir(inst, directory.create(inst.tr, path, layer or b'', prefix))
|
||||
elif inst.op == six.u('DIRECTORY_OPEN'):
|
||||
path = self.pop_tuples(inst.stack)
|
||||
layer = inst.pop()
|
||||
log_op('open %s: layer=%s' % (repr(directory.get_path() + path), repr(layer)))
|
||||
log_op('open %r: layer=%r' % (directory.get_path() + path, layer))
|
||||
self.append_dir(inst, directory.open(inst.tr, path, layer or b''))
|
||||
elif inst.op == six.u('DIRECTORY_MOVE'):
|
||||
old_path, new_path = self.pop_tuples(inst.stack, 2)
|
||||
log_op('move %s to %s' % (repr(directory.get_path() + old_path), repr(directory.get_path() + new_path)))
|
||||
log_op('move %r to %r' % (directory.get_path() + old_path, directory.get_path() + new_path))
|
||||
self.append_dir(inst, directory.move(inst.tr, old_path, new_path))
|
||||
elif inst.op == six.u('DIRECTORY_MOVE_TO'):
|
||||
new_absolute_path = self.pop_tuples(inst.stack)
|
||||
log_op('move %s to %s' % (repr(directory.get_path()), repr(new_absolute_path)))
|
||||
log_op('move %r to %r' % (directory.get_path(), new_absolute_path))
|
||||
self.append_dir(inst, directory.move_to(inst.tr, new_absolute_path))
|
||||
elif inst.op == six.u('DIRECTORY_REMOVE'):
|
||||
count = inst.pop()
|
||||
if count == 0:
|
||||
log_op('remove %s' % repr(directory.get_path()))
|
||||
log_op('remove %r' % (directory.get_path(),))
|
||||
directory.remove(inst.tr)
|
||||
else:
|
||||
path = self.pop_tuples(inst.stack)
|
||||
log_op('remove %s' % repr(directory.get_path() + path))
|
||||
log_op('remove %r' % (directory.get_path() + path,))
|
||||
directory.remove(inst.tr, path)
|
||||
elif inst.op == six.u('DIRECTORY_REMOVE_IF_EXISTS'):
|
||||
count = inst.pop()
|
||||
if count == 0:
|
||||
log_op('remove_if_exists %s' % repr(directory.get_path()))
|
||||
log_op('remove_if_exists %r' % (directory.get_path(),))
|
||||
directory.remove_if_exists(inst.tr)
|
||||
else:
|
||||
path = self.pop_tuples(inst.stack)
|
||||
log_op('remove_if_exists %s' % repr(directory.get_path() + path))
|
||||
log_op('remove_if_exists %r' % (directory.get_path() + path,))
|
||||
directory.remove_if_exists(inst.tr, path)
|
||||
elif inst.op == six.u('DIRECTORY_LIST'):
|
||||
count = inst.pop()
|
||||
if count == 0:
|
||||
result = directory.list(inst.tr)
|
||||
log_op('list %s' % (repr(directory.get_path())))
|
||||
log_op('list %r' % (directory.get_path(),))
|
||||
else:
|
||||
path = self.pop_tuples(inst.stack)
|
||||
result = directory.list(inst.tr, path)
|
||||
log_op('list %s' % (repr(directory.get_path() + path)))
|
||||
log_op('list %r' % (directory.get_path() + path,))
|
||||
|
||||
inst.push(fdb.tuple.pack(tuple(result)))
|
||||
elif inst.op == six.u('DIRECTORY_EXISTS'):
|
||||
count = inst.pop()
|
||||
if count == 0:
|
||||
result = directory.exists(inst.tr)
|
||||
log_op('exists %s: %d' % (repr(directory.get_path()), result))
|
||||
log_op('exists %r: %d' % (directory.get_path(), result))
|
||||
else:
|
||||
path = self.pop_tuples(inst.stack)
|
||||
result = directory.exists(inst.tr, path)
|
||||
log_op('exists %s: %d' % (repr(directory.get_path() + path), result))
|
||||
log_op('exists %r: %d' % (directory.get_path() + path, result))
|
||||
|
||||
if result:
|
||||
inst.push(1)
|
||||
|
@ -177,7 +177,7 @@ class DirectoryExtension():
|
|||
inst.push(directory.pack(key_tuple))
|
||||
elif inst.op == six.u('DIRECTORY_UNPACK_KEY'):
|
||||
key = inst.pop()
|
||||
log_op('unpack %s in subspace with prefix %s' % (repr(key), repr(directory.rawPrefix)))
|
||||
log_op('unpack %r in subspace with prefix %r' % (key, directory.rawPrefix))
|
||||
tup = directory.unpack(key)
|
||||
for t in tup:
|
||||
inst.push(t)
|
||||
|
@ -215,7 +215,7 @@ class DirectoryExtension():
|
|||
elif inst.op == six.u('DIRECTORY_STRIP_PREFIX'):
|
||||
s = inst.pop()
|
||||
if not s.startswith(directory.key()):
|
||||
raise Exception('String %s does not start with raw prefix %s' % (s, directory.key()))
|
||||
raise Exception('String %r does not start with raw prefix %r' % (s, directory.key()))
|
||||
|
||||
inst.push(s[len(directory.key()):])
|
||||
else:
|
||||
|
|
|
@ -91,7 +91,7 @@ class Stack:
|
|||
else:
|
||||
raw[i] = (raw[i][0], val)
|
||||
except fdb.FDBError as e:
|
||||
# print('ERROR: %s' % repr(e))
|
||||
# print('ERROR: %r' % e)
|
||||
raw[i] = (raw[i][0], fdb.tuple.pack((b'ERROR', str(e.code).encode('ascii'))))
|
||||
|
||||
if count is None:
|
||||
|
@ -543,7 +543,7 @@ class Tester:
|
|||
else:
|
||||
raise Exception("Unknown op %s" % inst.op)
|
||||
except fdb.FDBError as e:
|
||||
# print('ERROR: %s' % repr(e))
|
||||
# print('ERROR: %r' % e)
|
||||
inst.stack.push(idx, fdb.tuple.pack((b"ERROR", str(e.code).encode('ascii'))))
|
||||
|
||||
# print(" to %s" % self.stack)
|
||||
|
|
|
@ -31,7 +31,8 @@ extensions = [
|
|||
'sphinx.ext.todo',
|
||||
'sphinx.ext.ifconfig',
|
||||
'brokenrole',
|
||||
'relativelink'
|
||||
'relativelink',
|
||||
'rubydomain'
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
sphinx==1.5.6
|
||||
sphinx-bootstrap-theme==0.4.8
|
||||
pygments-style-solarized
|
||||
sphinxcontrib-rubydomain==0.1dev-20100804
|
Loading…
Reference in New Issue