Merge pull request #537 from apple/release-5.2

Merge Release-5.2 into master
This commit is contained in:
A.J. Beamon 2018-06-27 15:55:58 -07:00 committed by GitHub
commit ea8a288a20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 404 additions and 135 deletions

View File

@ -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

View File

@ -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())

View File

@ -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')

View File

@ -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)

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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() {

View File

@ -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.

View File

@ -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:

View File

@ -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)

View File

@ -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.

View File

@ -2,3 +2,4 @@
sphinx==1.5.6
sphinx-bootstrap-theme==0.4.8
pygments-style-solarized
sphinxcontrib-rubydomain==0.1dev-20100804