Skip to content

Commit

Permalink
Added: rm builders + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
drakes00 committed Sep 28, 2024
1 parent 60d36e2 commit 624577d
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 49 deletions.
Binary file modified .coverage
Binary file not shown.
Binary file added remake/.coverage
Binary file not shown.
47 changes: 34 additions & 13 deletions remake/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Builder():
"""Generic builder class."""
_action = None
_shouldRebuild = None
_destructive = None

def __init__(
self,
Expand All @@ -33,12 +34,14 @@ def __init__(
shouldRebuildFun: Callable[[VirtualTarget | pathlib.Path,
list[VirtualDep | pathlib.Path]],
bool] | None = None,
destructive: bool = False,
):
if isinstance(action, str):
self._action = action.split(" ")
else:
self._action = action
self._shouldRebuild = shouldRebuildFun
self._destructive = destructive
if not ephemeral:
self._register()

Expand Down Expand Up @@ -95,6 +98,11 @@ def shouldRebuild(self):
"""Returns buider's custom shouldRebuild function."""
return self._shouldRebuild

@property
def isDestructive(self):
"""Returns True if the builder is destructive (will remove target instead of creating it)."""
return self._destructive


# ==================================================
# = File Operations =
Expand All @@ -116,7 +124,7 @@ def _FILE_OPS_shouldRebuild(target, deps):
return shouldRebuild(target, [dep])


# Expects either :
# Expects either:
# - 2 arguments (source, destination):
# - If source is a file and dest does not exists -> Ok, rename as dest
# - If source is a file and dest exists and is a file -> Ok, override
Expand Down Expand Up @@ -151,11 +159,10 @@ def _cp(deps, targets, _):
elif dep.is_dir() and not target.exists():
shutil.copytree(dep, target)


cp = Builder(action=_cp, shouldRebuildFun=_FILE_OPS_shouldRebuild)


# Expects either :
# Expects either:
# - 2 arguments (source, destination):
# - If source is a file and dest does not exists -> Ok, rename as dest
# - If source is a file and dest exists and is a file -> Ok, override
Expand All @@ -175,15 +182,34 @@ def _mv(deps, targets, _):
for dep in deps:
shutil.move(dep, targets[0])


mv = Builder(action=_mv, shouldRebuildFun=_FILE_OPS_shouldRebuild)


def _rm(deps, targets, _):
raise NotImplementedError

def _FILE_OPS_rmShouldRebuild(target, _):
if isinstance(target, VirtualTarget):
return True
else:
return target.exists()


# Expects:
# - One or more arguments to be removed:
# - If the argument does not exists -> KO, but continue for others
# - If the argument is a file and exists -> Ok, remove it
# - If the argument is a dir and exists -> Ok, but only if the recursive flag is set
def _rm(deps, targets, _, recursive=None):
for target in targets:
if target.is_file():
target.unlink()
elif target.is_dir():
if recursive:
shutil.rmtree(target)
else:
target.rmdir()
else:
pass

rm = Builder(action=_rm)
rm = Builder(action=_rm, shouldRebuildFun=_FILE_OPS_rmShouldRebuild, destructive=True)

# ==================================================
# = Archives =
Expand All @@ -197,7 +223,6 @@ def _tar(deps, targets, _, compression=""):
for dep in deps:
tar.add(dep.relative_to(cwd))


tar = Builder(action=_tar)


Expand All @@ -212,7 +237,6 @@ def _zip(deps, targets, _):
else:
zip.write(dep.relative_to(cwd))


zip = Builder(action=_zip)

# ==================================================
Expand All @@ -227,7 +251,6 @@ def _tex2pdf(deps, _, _2, cmd="pdflatex"):
subprocess.run([cmd, latexFile], check=True)
subprocess.run([cmd, latexFile], check=True)


tex2pdf = Builder(action=_tex2pdf)

# ==================================================
Expand All @@ -238,14 +261,12 @@ def _tex2pdf(deps, _, _2, cmd="pdflatex"):
def _gcc(_, targets, _2, cflags=""):
subprocess.run(["gcc", cflags, "-o", targets[0]], check=True)


gcc = Builder(action=_gcc)


def _clang(_, targets, _2, cflags=""):
subprocess.run(["clang", cflags, "-o", targets[0]], check=True)


clang = Builder(action=_clang)

# ==================================================
Expand Down
15 changes: 11 additions & 4 deletions remake/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,18 @@ def apply(self, console: Console | Progress | None = None) -> bool:
else:
self._builder.action(self._deps, self._targets, console, **self._kwargs)

# If we are not in dry run mode, ensure targets were made after the rule is applied.
# If we are not in dry run mode,
if not isDryRun():
for target in self._targets:
if not isinstance(target, VirtualTarget) and not (os.path.isfile(target) or os.path.isdir(target)):
raise FileNotFoundError(f"Target {target} not created by rule `{self.actionName}`")
if self._builder.isDestructive:
# If builder is destructive, ensure targets are properly destroyed.
for target in self._targets:
if not isinstance(target, VirtualTarget) and (os.path.isfile(target) or os.path.isdir(target)):
raise FileNotFoundError(f"Target {target} not destroyed by rule `{self.actionName}`")
else:
# If builder is creative, ensure targets were made after the rule is applied.
for target in self._targets:
if not isinstance(target, VirtualTarget) and not (os.path.isfile(target) or os.path.isdir(target)):
raise FileNotFoundError(f"Target {target} not created by rule `{self.actionName}`")

return True

Expand Down
150 changes: 118 additions & 32 deletions tests/test_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import zipfile
from pathlib import Path

from ward import test, fixture, raises, xfail
from ward import test, fixture, raises, skip

from remake import Builder, Rule, VirtualDep, VirtualTarget
from remake import getCurrentContext
Expand Down Expand Up @@ -430,14 +430,98 @@ def _doMove(src, dst):
_doMove(source, destination)


@test("Basic remove operations.")
@xfail
def test_removeFileOperations():
"""Basic remove operations."""
raise NotImplementedError
# === Remove ===
# Files (single, multiple, non-existant)
# Directorie (single, multiple, non-existant, not empty)
@test("Basic file remove operations.")
def test_10_removeFileOperations(_=setupTestCopyMove):
"""Basic file remove operations."""
def _doRemove(targets):
getCurrentContext().clearRules()
Rule(targets=targets, builder=rm).apply()
getCurrentContext().clearRules()

test_file_1 = Path("test_file_1.txt")
test_file_2 = Path("test_file_2.txt")
test_dir_1 = Path("test_dir_1")
test_dir_2 = Path("test_dir_2")
test_file_3 = test_dir_1 / "test_file_3.txt"

# Single file
target = test_file_1
_doRemove(target)
assert target.exists() is False

# Multiple files
targets = [test_file_2, test_file_3]
_doRemove(targets)
for target in targets:
assert target.exists() is False
assert test_dir_1.exists()

# Attempt to remove a non-existent file
target = Path("nonexistent_file.txt")
assert target.exists() is False
_doRemove(target)
assert target.exists() is False

# Attempt to remove files from a non-existent directory
target = Path("nonexistent_dir/nonexistent_file.txt")
assert target.exists() is False
_doRemove(target)
assert target.exists() is False


@test("Basic directory remove operations.")
def test_XX_removeDirectoryOperations(_=setupTestCopyMove):
"""Basic directory remove operations."""
def _doRemove(targets, recursive=False):
getCurrentContext().clearRules()
Rule(targets=targets, builder=rm, recursive=recursive).apply()
getCurrentContext().clearRules()

test_dir_1 = Path("test_dir_1")
test_dir_2 = Path("test_dir_2")
test_dir_3 = Path("test_dir_3")
test_dir_4 = Path("test_dir_4")
test_file_1 = Path("test_file_1.txt")
test_file_2 = Path("test_file_2.txt")

# Single empty directory
target = test_dir_2
_doRemove(target)
assert target.exists() is False

# Multiple empty directories
targets = [test_dir_3, test_dir_4]
_doRemove(targets)
assert all([_.exists() is False for _ in targets])

# Multiple files and empty directories
targets = [test_file_2, test_dir_4]
_doRemove(targets)
assert all([_.exists() is False for _ in targets])

# Attempt to remove a non-existent directory
target = Path("nonexistent_dir")
_doRemove(target)
assert target.exists() is False

# Attempt to remove a non-empty directory
target = test_dir_1
with raises(OSError):
_doRemove(target)
assert target.exists() is True

# Force remove a non-empty directory
target = test_dir_1
_doRemove(target, recursive=True)
assert target.exists() is False

# Partially existing multiple targets
existing_target = test_dir_1
non_existing_target = Path("nonexistent_dir")
assert non_existing_target.exists() is False
targets = [existing_target, non_existing_target]
_doRemove(targets)
assert all([_.exists() is False for _ in targets])


@test("Basic tar operations - single file")
Expand Down Expand Up @@ -476,37 +560,38 @@ def _doTar(src, dst):


@test("Basic tar operations - single file with rename")
@xfail
@skip
def test_11_tarSingleFileRename(_=setupTestCopyMove):
"""Creates a tar archive from a single file with a custom name in the archive."""
# Currently no way to specify a name in the archive.
raise NotImplementedError
# def _doTar(src, dst):
# getCurrentContext().clearRules()
# Rule(deps=src, targets=dst, builder=tar).apply()
# getCurrentContext().clearRules()

#
# test_file_1 = Path("test_file_1.txt")
# test_file_1_arcname = Path("awesome_file_1.txt")
# test_archive = Path("archive.tar")

#
# # Create the source file
# with open(test_file_1, "w", encoding="utf-8") as f:
# f.write("This is a test file.")

# # Will fail here, currently no way to pass no name inside rule structure.
# _doTar((test_file_1, "custom_name_in_tar.txt"), test_archive)

#
# _doTar((test_file_1, test_file_1_arcname), test_archive)
#
# # Verify the archive exists
# assert test_archive.exists() is True

#
# # Extract the archive and verify content
# with tarfile.open(test_archive, encoding="utf-8") as tarball:
# tarball.extractall()
# with open("custom_name.txt", "r", encoding="utf-8") as f:
# with open(test_file_1_arcname, "r", encoding="utf-8") as f:
# content = f.read()
# assert content == "This is a test file."

#
# # Clean up
# os.remove("custom_name.txt")
# os.remove(test_file_1_arcname)
# os.remove(test_archive)


Expand Down Expand Up @@ -714,37 +799,38 @@ def _doZip(src, dst):


@test("Basic zip operations - single file with rename")
@xfail
@skip
def test_18_zipSingleFileRename(_=setupTestCopyMove):
"""Creates a zip archive from a single file with a custom name in the archive."""
# Currently no way to specify a name in the archive.
raise NotImplementedError
# def _doZip(src, dst):
# getCurrentContext().clearRules()
# Rule(deps=src, targets=dst, builder=zip).apply()
# getCurrentContext().clearRules()

#
# test_file_1 = Path("test_file_1.txt")
# test_file_1_arcname = Path("awesome_file_1.txt")
# test_archive = Path("archive.zip")

#
# # Create the source file
# with open(test_file_1, "w", encoding="utf-8") as f:
# f.write("This is a test file.")

# # Will fail here, currently no way to pass no name inside rule structure.
# _doZip((test_file_1, "custom_name_in_zip.txt"), test_archive)

#
# _doZip((test_file_1, test_file_1_arcname), test_archive)
#
# # Verify the archive exists
# assert test_archive.exists() is True

#
# # Extract the archive and verify content
# with zipfile.ZipFile(test_archive) as zipball:
# with zipfile.open(test_archive, encoding="utf-8") as zipball:
# zipball.extractall()
# with open("custom_name.txt", "r", encoding="utf-8") as f:
# with open(test_file_1_arcname, "r", encoding="utf-8") as f:
# content = f.read()
# assert content == "This is a test file."

#
# # Clean up
# os.remove("custom_name.txt")
# os.remove(test_file_1_arcname)
# os.remove(test_archive)


Expand Down

0 comments on commit 624577d

Please sign in to comment.