def test_build_generics_symlink_directory_outside(tmp_path, emitter): """Ignores (with warning) a symlink pointing a dir outside projects dir.""" project_dir = tmp_path / "test-project" project_dir.mkdir() metadata = project_dir / CHARM_METADATA metadata.write_text("name: crazycharm") 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 = CharmBuilder( charmdir=project_dir, builddir=build_dir, entrypoint=entrypoint, ) builder.handle_generic_paths() assert not (build_dir / "external-dir").exists() expected = "Ignoring symlink because targets outside the project: 'external-dir'" emitter.assert_trace(expected)
def test_build_generics_ignored_file(tmp_path, emitter): """Don't include ignored filed.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") # 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 = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) # 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'" emitter.assert_trace(expected)
def test_build_generics_different_filetype(tmp_path, emitter, monkeypatch): """Ignores whatever is not a regular file, symlink or dir.""" # 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) metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") 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 = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=tmp_path / entrypoint, ) builder.handle_generic_paths() assert not (build_dir / "test-socket").exists() expected = "Ignoring file because of type: 'test-socket'" emitter.assert_trace(expected)
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() metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") 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 = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) 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_dir(tmp_path): """Respects a symlinked dir.""" build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") 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 = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) 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_outside(tmp_path, caplog): """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() metadata = project_dir / CHARM_METADATA metadata.write_text("name: crazycharm") 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 = CharmBuilder( charmdir=project_dir, builddir=build_dir, entrypoint=entrypoint, ) 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_ignored_dir(tmp_path, caplog): """Don't include ignored dir.""" caplog.set_level(logging.DEBUG) build_dir = tmp_path / BUILD_DIRNAME build_dir.mkdir() metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") # 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 = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) # 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_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.write_text("name: crazycharm") entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() builder = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) 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() metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") somedir = tmp_path / "somedir" somedir.mkdir(mode=0o700) builder = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) 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() metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") entrypoint = tmp_path / "crazycharm.py" entrypoint.touch() the_symlink = tmp_path / "somehook.py" the_symlink.symlink_to(entrypoint) builder = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) 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_tree(tmp_path, *, expect_hardlinks): 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() metadata = tmp_path / CHARM_METADATA metadata.write_text("name: crazycharm") 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 = CharmBuilder( charmdir=tmp_path, builddir=build_dir, entrypoint=entrypoint, ) # 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)