def test_build_generics_symlink_dir(tmp_path): """Respects a symlinked dir.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() somedir = tmp_path / 'somedir' somedir.mkdir() somefile = somedir / 'sanity check' somefile.touch() the_symlink = tmp_path / 'thelink' the_symlink.symlink_to(somedir) builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) builder.handle_generic_paths() built_symlink = build_dir / 'thelink' assert built_symlink.is_symlink() assert built_symlink.resolve() == build_dir / 'somedir' real_link = os.readlink(str(built_symlink)) assert real_link == 'somedir' # as a sanity check, the file inside the linked dir should exist assert (build_dir / 'thelink' / 'sanity check').exists()
def test_build_generics_symlink_deep(tmp_path): """Correctly re-links a symlink across deep dirs.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() dir1 = tmp_path / 'dir1' dir1.mkdir() dir2 = tmp_path / 'dir2' dir2.mkdir() original_target = dir1 / 'file.real' original_target.touch() the_symlink = dir2 / 'file.link' the_symlink.symlink_to(original_target) builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) builder.handle_generic_paths() built_symlink = build_dir / 'dir2' / 'file.link' assert built_symlink.is_symlink() assert built_symlink.resolve() == build_dir / 'dir1' / 'file.real' real_link = os.readlink(str(built_symlink)) assert real_link == '../dir1/file.real'
def test_build_generics_ignored_file(tmp_path, caplog, config): """Don't include ignored filed.""" caplog.set_level(logging.DEBUG) build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() # create two files (and the needed entrypoint) file1 = tmp_path / "file1.txt" file1.touch() file2 = tmp_path / "file2.txt" file2.touch() entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() builder = Builder( { "from": tmp_path, "entrypoint": entrypoint, "requirement": [], }, config, ) # set it up to ignore file 2 and make it work builder.ignore_rules.extend_patterns(["file2.*"]) builder.handle_generic_paths() assert (build_dir / "file1.txt").exists() assert not (build_dir / "file2.txt").exists() expected = "Ignoring file because of rules: 'file2.txt'" assert expected in [rec.message for rec in caplog.records]
def test_build_generics_symlink_deep(tmp_path, config): """Correctly re-links a symlink across deep dirs.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() dir1 = tmp_path / "dir1" dir1.mkdir() dir2 = tmp_path / "dir2" dir2.mkdir() original_target = dir1 / "file.real" original_target.touch() the_symlink = dir2 / "file.link" the_symlink.symlink_to(original_target) builder = Builder( { "from": tmp_path, "entrypoint": entrypoint, "requirement": [], }, config, ) builder.handle_generic_paths() built_symlink = build_dir / "dir2" / "file.link" assert built_symlink.is_symlink() assert built_symlink.resolve() == build_dir / "dir1" / "file.real" real_link = os.readlink(str(built_symlink)) assert real_link == "../dir1/file.real"
def test_build_generics_symlink_file_outside(tmp_path, caplog, config): """Ignores (with warning) a symlink pointing a file outside projects dir.""" caplog.set_level(logging.WARNING) project_dir = tmp_path / "test-project" project_dir.mkdir() build_dir = project_dir / BUILD_DIRNAME build_dir.mkdir() entrypoint = project_dir / "crazycharm.py" entrypoint.touch() outside_project = tmp_path / "dangerous.txt" outside_project.touch() the_symlink = project_dir / "external-file" the_symlink.symlink_to(outside_project) builder = Builder( { "from": project_dir, "entrypoint": entrypoint, "requirement": [], }, config, ) builder.handle_generic_paths() assert not (build_dir / "external-file").exists() expected = "Ignoring symlink because targets outside the project: 'external-file'" assert expected in [rec.message for rec in caplog.records]
def test_build_generics_symlink_dir(tmp_path, config): """Respects a symlinked dir.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() somedir = tmp_path / "somedir" somedir.mkdir() somefile = somedir / "sanity check" somefile.touch() the_symlink = tmp_path / "thelink" the_symlink.symlink_to(somedir) builder = Builder( { "from": tmp_path, "entrypoint": entrypoint, "requirement": [], }, config, ) builder.handle_generic_paths() built_symlink = build_dir / "thelink" assert built_symlink.is_symlink() assert built_symlink.resolve() == build_dir / "somedir" real_link = os.readlink(str(built_symlink)) assert real_link == "somedir" # as a sanity check, the file inside the linked dir should exist assert (build_dir / "thelink" / "sanity check").exists()
def test_build_generics_symlink_file(tmp_path, config): """Respects a symlinked file.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() the_symlink = tmp_path / "somehook.py" the_symlink.symlink_to(entrypoint) builder = Builder( { "from": tmp_path, "entrypoint": entrypoint, "requirement": [], }, config, ) builder.handle_generic_paths() built_symlink = build_dir / "somehook.py" assert built_symlink.is_symlink() assert built_symlink.resolve() == build_dir / "crazycharm.py" real_link = os.readlink(str(built_symlink)) assert real_link == "crazycharm.py"
def test_build_generics_symlink_directory_outside(tmp_path, caplog): """Ignores (with warning) a symlink pointing a dir outside projects dir.""" caplog.set_level(logging.WARNING) project_dir = tmp_path / 'test-project' project_dir.mkdir() build_dir = project_dir / BUILD_DIRNAME build_dir.mkdir() entrypoint = project_dir / 'crazycharm.py' entrypoint.touch() outside_project = tmp_path / 'dangerous' outside_project.mkdir() the_symlink = project_dir / 'external-dir' the_symlink.symlink_to(outside_project) builder = Builder({ 'from': project_dir, 'entrypoint': entrypoint, 'requirement': [], }) builder.handle_generic_paths() assert not (build_dir / 'external-dir').exists() expected = "Ignoring symlink because targets outside the project: 'external-dir'" assert expected in [rec.message for rec in caplog.records]
def test_build_generics_ignored_dir(tmp_path, caplog): """Don't include ignored dir.""" caplog.set_level(logging.DEBUG) build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() # create two files (and the needed entrypoint) dir1 = tmp_path / 'dir1' dir1.mkdir() dir2 = tmp_path / 'dir2' dir2.mkdir() entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) # set it up to ignore dir 2 and make it work builder.ignore_rules.extend_patterns(['dir2']) builder.handle_generic_paths() assert (build_dir / 'dir1').exists() assert not (build_dir / 'dir2').exists() expected = "Ignoring directory because of rules: 'dir2'" assert expected in [rec.message for rec in caplog.records]
def test_build_generics_different_filetype(tmp_path, caplog, monkeypatch): """Ignores whatever is not a regular file, symlink or dir.""" caplog.set_level(logging.DEBUG) # change into the tmp path and do everything locally, because otherwise the socket path # will be too long for mac os monkeypatch.chdir(tmp_path) build_dir = pathlib.Path(BUILD_DIRNAME) build_dir.mkdir() entrypoint = pathlib.Path('crazycharm.py') entrypoint.touch() # create a socket sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.bind('test-socket') builder = Builder({ 'from': tmp_path, 'entrypoint': tmp_path / entrypoint, 'requirement': [], }) builder.handle_generic_paths() assert not (build_dir / 'test-socket').exists() expected = "Ignoring file because of type: 'test-socket'" assert expected in [rec.message for rec in caplog.records]
def test_build_generics_simple_files(tmp_path): """Check transferred metadata and simple entrypoint, also return proper linked entrypoint.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() metadata = tmp_path / CHARM_METADATA metadata.touch() entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) linked_entrypoint = builder.handle_generic_paths() # check files are there, are files, and are really hard links (so no # check for permissions needed) built_metadata = build_dir / CHARM_METADATA assert built_metadata.is_file() assert built_metadata.stat().st_ino == metadata.stat().st_ino built_entrypoint = build_dir / 'crazycharm.py' assert built_entrypoint.is_file() assert built_entrypoint.stat().st_ino == entrypoint.stat().st_ino assert linked_entrypoint == built_entrypoint
def test_build_generics_simple_dir(tmp_path): """Check transferred any directory, with proper permissions.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() somedir = tmp_path / 'somedir' somedir.mkdir(mode=0o700) builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) builder.handle_generic_paths() built_dir = build_dir / 'somedir' assert built_dir.is_dir() assert built_dir.stat().st_mode & 0xFFF == 0o700
def test_build_generics_symlink_file(tmp_path): """Respects a symlinked file.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() the_symlink = tmp_path / 'somehook.py' the_symlink.symlink_to(entrypoint) builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) builder.handle_generic_paths() built_symlink = build_dir / 'somehook.py' assert built_symlink.is_symlink() assert built_symlink.resolve() == build_dir / 'crazycharm.py' real_link = os.readlink(str(built_symlink)) assert real_link == 'crazycharm.py'
def test_build_generics_simple_dir(tmp_path, config): """Check transferred any directory, with proper permissions.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() somedir = tmp_path / "somedir" somedir.mkdir(mode=0o700) builder = Builder( { "from": tmp_path, "entrypoint": entrypoint, "requirement": [], }, config, ) builder.handle_generic_paths() built_dir = build_dir / "somedir" assert built_dir.is_dir() assert built_dir.stat().st_mode & 0xFFF == 0o700
def _test_build_generics_tree(tmp_path, caplog, config, *, expect_hardlinks): caplog.set_level(logging.DEBUG) build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() # create this structure: # ├─ crazycharm.py (entrypoint) # ├─ file1.txt # ├─ dir1 # │ └─ dir3 (ignored!) # └─ dir2 # ├─ file2.txt # ├─ file3.txt (ignored!) # ├─ dir4 (ignored!) # │ └─ file4.txt # └─ dir5 entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() file1 = tmp_path / "file1.txt" file1.touch() dir1 = tmp_path / "dir1" dir1.mkdir() dir3 = dir1 / "dir3" dir3.mkdir() dir2 = tmp_path / "dir2" dir2.mkdir() file2 = dir2 / "file2.txt" file2.touch() file3 = dir2 / "file3.txt" file3.touch() dir4 = dir2 / "dir4" dir4.mkdir() file4 = dir4 / "file4.txt" file4.touch() dir5 = dir2 / "dir5" dir5.mkdir() builder = Builder( { "from": tmp_path, "entrypoint": entrypoint, "requirement": [], }, config, ) # set it up to ignore some stuff and make it work builder.ignore_rules.extend_patterns([ "dir1/dir3", "dir2/file3.txt", "dir2/dir4", ]) builder.handle_generic_paths() assert (build_dir / "crazycharm.py").exists() assert (build_dir / "file1.txt").exists() assert (build_dir / "dir1").exists() assert not (build_dir / "dir1" / "dir3").exists() assert (build_dir / "dir2").exists() assert (build_dir / "dir2" / "file2.txt").exists() assert not (build_dir / "dir2" / "file3.txt").exists() assert not (build_dir / "dir2" / "dir4").exists() assert (build_dir / "dir2" / "dir5").exists() for (p1, p2) in [ (build_dir / "crazycharm.py", entrypoint), (build_dir / "file1.txt", file1), (build_dir / "dir2" / "file2.txt", file2), ]: if expect_hardlinks: # they're hard links assert p1.samefile(p2) else: # they're *not* hard links assert not p1.samefile(p2) # but they're essentially the same assert filecmp.cmp(str(p1), str(p2), shallow=False) assert p1.stat().st_mode == p2.stat().st_mode assert p1.stat().st_size == p2.stat().st_size assert p1.stat().st_atime == pytest.approx(p2.stat().st_atime) assert p1.stat().st_mtime == pytest.approx(p2.stat().st_mtime)
def _test_build_generics_tree(tmp_path, caplog, *, expect_hardlinks): caplog.set_level(logging.DEBUG) build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() # create this structure: # ├─ crazycharm.py (entrypoint) # ├─ file1.txt # ├─ dir1 # │ └─ dir3 (ignored!) # └─ dir2 # ├─ file2.txt # ├─ file3.txt (ignored!) # ├─ dir4 (ignored!) # │ └─ file4.txt # └─ dir5 entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() file1 = tmp_path / 'file1.txt' file1.touch() dir1 = tmp_path / 'dir1' dir1.mkdir() dir3 = dir1 / 'dir3' dir3.mkdir() dir2 = tmp_path / 'dir2' dir2.mkdir() file2 = dir2 / 'file2.txt' file2.touch() file3 = dir2 / 'file3.txt' file3.touch() dir4 = dir2 / 'dir4' dir4.mkdir() file4 = dir4 / 'file4.txt' file4.touch() dir5 = dir2 / 'dir5' dir5.mkdir() builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) # set it up to ignore some stuff and make it work builder.ignore_rules.extend_patterns([ 'dir1/dir3', 'dir2/file3.txt', 'dir2/dir4', ]) builder.handle_generic_paths() assert (build_dir / 'crazycharm.py').exists() assert (build_dir / 'file1.txt').exists() assert (build_dir / 'dir1').exists() assert not (build_dir / 'dir1' / 'dir3').exists() assert (build_dir / 'dir2').exists() assert (build_dir / 'dir2' / 'file2.txt').exists() assert not (build_dir / 'dir2' / 'file3.txt').exists() assert not (build_dir / 'dir2' / 'dir4').exists() assert (build_dir / 'dir2' / 'dir5').exists() for (p1, p2) in [ (build_dir / 'crazycharm.py', entrypoint), (build_dir / 'file1.txt', file1), (build_dir / 'dir2' / 'file2.txt', file2), ]: if expect_hardlinks: # they're hard links assert p1.samefile(p2) else: # they're *not* hard links assert not p1.samefile(p2) # but they're essentially the same assert filecmp.cmp(str(p1), str(p2), shallow=False) assert p1.stat().st_mode == p2.stat().st_mode assert p1.stat().st_size == p2.stat().st_size assert p1.stat().st_atime == pytest.approx(p2.stat().st_atime) assert p1.stat().st_mtime == pytest.approx(p2.stat().st_mtime)
def test_build_generics_tree(tmp_path, caplog): """Manages ok a deep tree, including internal ignores.""" caplog.set_level(logging.DEBUG) build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() # create this structure: # ├─ crazycharm.py (entrypoint) # ├─ file1.txt # ├─ dir1 # │ └─ dir3 (ignored!) # └─ dir2 # ├─ file2.txt # ├─ file3.txt (ignored!) # ├─ dir4 (ignored!) # │ └─ file4.txt # └─ dir5 entrypoint = tmp_path / 'crazycharm.py' entrypoint.touch() file1 = tmp_path / 'file1.txt' file1.touch() dir1 = tmp_path / 'dir1' dir1.mkdir() dir3 = dir1 / 'dir3' dir3.mkdir() dir2 = tmp_path / 'dir2' dir2.mkdir() file2 = dir2 / 'file2.txt' file2.touch() file3 = dir2 / 'file3.txt' file3.touch() dir4 = dir2 / 'dir4' dir4.mkdir() file4 = dir4 / 'file4.txt' file4.touch() dir5 = dir2 / 'dir5' dir5.mkdir() builder = Builder({ 'from': tmp_path, 'entrypoint': entrypoint, 'requirement': [], }) # set it up to ignore some stuff and make it work builder.ignore_rules.extend_patterns([ 'dir1/dir3', 'dir2/file3.txt', 'dir2/dir4', ]) builder.handle_generic_paths() assert (build_dir / 'crazycharm.py').exists() assert (build_dir / 'file1.txt').exists() assert (build_dir / 'dir1').exists() assert not (build_dir / 'dir1' / 'dir3').exists() assert (build_dir / 'dir2').exists() assert (build_dir / 'dir2' / 'file2.txt').exists() assert not (build_dir / 'dir2' / 'file3.txt').exists() assert not (build_dir / 'dir2' / 'dir4').exists() assert (build_dir / 'dir2' / 'dir5').exists()