def test_item_predecessors(self): dg = DependencyGraph(PATH_TO_ITEM.values(), layer_target='t-34') self.assertEqual( _fs_root_phases(FilesystemRootItem(from_target='t-34')), list(dg.ordered_phases()), ) with TempSubvolumes(sys.argv[0]) as temp_subvolumes: subvol = temp_subvolumes.create('subvol') phases_provide = PhasesProvideItem(from_target='t', subvol=subvol) ns = dg._prep_item_predecessors(phases_provide) path_to_item = {'/': phases_provide, **PATH_TO_ITEM} self.assertEqual( ns.item_to_predecessors, { path_to_item[k]: {path_to_item[v] for v in vs} for k, vs in { '/a/b/c': {'/'}, '/a/d/e': {'/a/b/c'}, '/a/b/c/F': {'/a/b/c'}, '/a/d/e/G': {'/a/d/e'}, }.items() }) self.assertEqual( ns.predecessor_to_items, { path_to_item[k]: {path_to_item[v] for v in vs} for k, vs in { '/': {'/a/b/c'}, '/a/b/c': {'/a/d/e', '/a/b/c/F'}, '/a/b/c/F': set(), '/a/d/e': {'/a/d/e/G'}, '/a/d/e/G': set(), }.items() }) self.assertEqual(ns.items_without_predecessors, {path_to_item['/']})
def __init__( self, iter_items: {'Iterator of ImageItems'}, layer_target: str, ): # Without deduping, dependency diamonds would cause a lot of # redundant work below. `_prep_item_predecessors` mutates this. self.items = set() # While deduplicating `ImageItem`s, let's also split out the phases. self.order_to_phase_items = {} for item in iter_items: if item.phase_order() is None: self.items.add(item) else: self.order_to_phase_items.setdefault( item.phase_order(), [], ).append(item) # If there is no MAKE_SUBVOL item, create an empty subvolume. make_subvol_items = self.order_to_phase_items.setdefault( PhaseOrder.MAKE_SUBVOL, [FilesystemRootItem(from_target=layer_target)], ) assert len(make_subvol_items) == 1, make_subvol_items # If we have a foreign layer, it must be the only item, besides the # mandatory `MAKE_SUBVOL` added above. foreign = self.order_to_phase_items.get(PhaseOrder.FOREIGN_LAYER) if foreign: assert len(foreign) == 1, foreign assert not self.items, self.items assert set(self.order_to_phase_items.keys()) == { PhaseOrder.FOREIGN_LAYER, PhaseOrder.MAKE_SUBVOL, }, self.order_to_phase_items
def test_gen_dependency_graph(self): dg = DependencyGraph(PATH_TO_ITEM.values(), layer_target='t-72') self.assertEqual( _fs_root_phases(FilesystemRootItem(from_target='t-72')), list(dg.ordered_phases()), ) with TempSubvolumes(sys.argv[0]) as temp_subvolumes: subvol = temp_subvolumes.create('subvol') self.assertIn( tuple( dg.gen_dependency_order_items( PhasesProvideItem(from_target='t', subvol=subvol), )), { tuple(PATH_TO_ITEM[p] for p in paths) for paths in [ # A few orders are valid, don't make the test fragile. ['/a/b/c', '/a/b/c/F', '/a/d/e', '/a/d/e/G'], ['/a/b/c', '/a/d/e', '/a/b/c/F', '/a/d/e/G'], ['/a/b/c', '/a/d/e', '/a/d/e/G', '/a/b/c/F'], ] }, )
def test_cycle_detection(self): def requires_provides_directory_class(requires_dir, provides_dir): @dataclass(init=False, frozen=True) class RequiresProvidesDirectory(ImageItem): def requires(self): yield require_directory(requires_dir) def provides(self): yield ProvidesDirectory(path=provides_dir) return RequiresProvidesDirectory # `dg_ok`: dependency-sorting will work without a cycle first = FilesystemRootItem(from_target='') second = requires_provides_directory_class('/', 'a')(from_target='') third = MakeDirsItem(from_target='', into_dir='a', path_to_make='b/c') dg_ok = DependencyGraph([second, first, third], layer_target='t') self.assertEqual(_fs_root_phases(first), list(dg_ok.ordered_phases())) # `dg_bad`: changes `second` to get a cycle dg_bad = DependencyGraph([ requires_provides_directory_class('a/b', 'a')(from_target=''), first, third, ], layer_target='t') self.assertEqual(_fs_root_phases(first), list(dg_bad.ordered_phases())) with TempSubvolumes(sys.argv[0]) as temp_subvolumes: subvol = temp_subvolumes.create('subvol') provides_root = PhasesProvideItem(from_target='t', subvol=subvol) self.assertEqual( [second, third], list(dg_ok.gen_dependency_order_items(provides_root)), ) with self.assertRaisesRegex(AssertionError, '^Cycle in '): list(dg_bad.gen_dependency_order_items(provides_root))
def __init__( self, iter_items: {'Iterator of ImageItems'}, layer_target: str, ): # Without deduping, dependency diamonds would cause a lot of # redundant work below. `_prep_item_predecessors` mutates this. self.items = set() # While deduplicating `ImageItem`s, let's also split out the phases. self.order_to_phase_items = {} for item in iter_items: if item.phase_order() is None: self.items.add(item) else: self.order_to_phase_items.setdefault( item.phase_order(), [], ).append(item) # If there is no MAKE_SUBVOL item, create an empty subvolume. make_subvol_items = self.order_to_phase_items.setdefault( PhaseOrder.MAKE_SUBVOL, [FilesystemRootItem(from_target=layer_target)], ) assert len(make_subvol_items) == 1, make_subvol_items
def test_phase_order(self): class FakeRemovePaths: get_phase_builder = 'kittycat' def phase_order(self): return PhaseOrder.REMOVE_PATHS first = FilesystemRootItem(from_target='') second = FakeRemovePaths() third = MakeDirsItem(from_target='', into_dir='/', path_to_make='a/b') dg = DependencyGraph([second, first, third], layer_target='t') self.assertEqual( _fs_root_phases(first) + [ (FakeRemovePaths.get_phase_builder, (second, )), ], list(dg.ordered_phases()), ) with TempSubvolumes(sys.argv[0]) as temp_subvolumes: subvol = temp_subvolumes.create('subvol') self.assertEqual([third], list( dg.gen_dependency_order_items( PhasesProvideItem(from_target='t', subvol=subvol), )))
# We rely on Buck setting the environment via the `env =` directive. assert T_HELLO_WORLD_TAR in TARGET_TO_PATH, 'You must use `buck test`' def mangle(feature_target): return feature_target + ( '_IF_YOU_REFER_TO_THIS_RULE_YOUR_DEPENDENCIES_WILL_BE_BROKEN_' 'SO_DO_NOT_DO_THIS_EVER_PLEASE_KTHXBAI') # This should be a faithful transcription of the `image_feature` # specifications in `test/TARGETS`. The IDs currently have no semantics, # existing only to give names to specific items. ID_TO_ITEM = { '/': FilesystemRootItem(from_target=None), # From `feature_dirs`: 'foo/bar': MakeDirsItem(from_target=T_DIRS, into_dir='/', path_to_make='/foo/bar'), 'foo/bar/baz': MakeDirsItem(from_target=T_DIRS, into_dir='/foo/bar', path_to_make='baz'), # From `feature_bad_dir`: 'foo/borf/beep': MakeDirsItem( from_target=T_BAD_DIR, into_dir='/foo', path_to_make='borf/beep', user_group='uuu:ggg', mode='mmm',