Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile--alpine.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ ARG PYTHON_VERSION=3.12
# --------------------------------------------- base1
FROM python:${PYTHON_VERSION}-alpine AS base1

# --------------------------------------------- base1
RUN apk add --no-cache coreutils

# --------------------------------------------- base2
FROM base1 AS base2

RUN apk add python3-dev build-base musl-dev linux-headers
Expand Down
14 changes: 14 additions & 0 deletions src/local_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,3 +732,17 @@ def is_abs_path(self, path: str) -> bool:
def get_basename(self, path: str) -> str:
assert type(path) is str
return os.path.basename(path)

def get_abs_path(self, path: str) -> str:
assert type(path) is str

normalized = os.path.normpath(path)
assert type(normalized) is str

# We expand the tilde locally so that the behavior matches the server
expanded = os.path.expanduser(normalized)
assert type(expanded) is str

r = os.path.abspath(expanded)
assert type(r) is str
return r
4 changes: 4 additions & 0 deletions src/os_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,7 @@ def is_abs_path(self, path: str) -> bool:
def get_basename(self, path: str) -> str:
assert type(path) is str
raise NotImplementedError()

def get_abs_path(self, path: str) -> str:
assert type(path) is str
raise NotImplementedError()
39 changes: 39 additions & 0 deletions src/remote_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def cmdline(self):


class RemoteOperations(OsOperations):
_C_EOL = "\n"

#
# Target system is Linux only.
#
Expand Down Expand Up @@ -992,6 +994,24 @@ def get_basename(self, path: str) -> str:
assert type(path) is str
return __class__._get_basename(path)

def get_abs_path(self, path: str) -> str:
assert type(path) is str

cleaned_path = __class__._normpath(path)
assert type(cleaned_path) is str

#
# "-m" is used to ignore not exist parts of path
#
r = self.exec_command(
["realpath", "-m", cleaned_path],
encoding=get_default_encoding(),
)
assert type(r) is str
r = __class__._strip_last_eol(r)
assert type(r) is str
return r

@staticmethod
def _build_cmdline(
cmd,
Expand Down Expand Up @@ -1104,6 +1124,25 @@ def _get_basename(path: str) -> str:
assert type(path) is str
return posixpath.basename(path)

@staticmethod
def _normpath(path: str) -> str:
assert type(path) is str
return posixpath.normpath(path)

@staticmethod
def _strip_last_eol(text: str) -> str:
assert type(text) is str
assert type(__class__._C_EOL) is str
assert __class__._C_EOL == "\n"

if not text.endswith(__class__._C_EOL):
return text

r = text[:-(len(__class__._C_EOL))]
assert type(r) is str
assert r + __class__._C_EOL == text
return r


def normalize_error(error):
if isinstance(error, bytes):
Expand Down
125 changes: 125 additions & 0 deletions tests/test_os_ops_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -1548,6 +1548,131 @@ def test_is_abs_path__no(self, os_ops: OsOperations):
assert actual_value is False
return

# --------------------------------------------------------------------
def test_get_abs_path(self, os_ops: OsOperations):
assert isinstance(os_ops, OsOperations)

def LOCAL__check(value, expected) -> bool:
logging.info("Source path: [{}]".format(value))
actual = os_ops.get_abs_path(value)
if actual == expected:
logging.info("Result is OK: [{}].".format(
actual,
))
else:
logging.error("Result is BAD: [{}]. Expected: [{}].".format(
actual,
expected,
))
logging.info("")
return False

logging.info("------------- test empty string")
cwd = os_ops.cwd()
LOCAL__check("", cwd)

logging.info("------------- test cwd")
LOCAL__check(".", cwd)

path = os_ops.build_path(cwd, ".")
LOCAL__check(path, cwd)

cwd = os_ops.cwd()
expected_r = os_ops.build_path(cwd, "abc")
LOCAL__check("abc", expected_r)

cwd = os_ops.cwd()
expected_r = os_ops.build_path(cwd, "abc")
LOCAL__check("./abc", expected_r)

cwd = os_ops.cwd()
expected_r = os_ops.build_path(os_ops.get_dirname(cwd), "abc")
LOCAL__check("../abc", expected_r)

cwd = os_ops.cwd()
expected_r = os_ops.build_path(cwd, "abc1.txt")
LOCAL__check("abc1.txt", expected_r)

logging.info("------------- test cwd parent")
cwd = os_ops.cwd()
expected_r = os_ops.get_dirname(cwd)
LOCAL__check("..", expected_r)

logging.info("------------- test file")
file = os_ops.mkstemp()
LOCAL__check(file, file)
os_ops.remove_file(file)

logging.info("------------- test dir")
dir = os_ops.mkdtemp()
LOCAL__check(dir, dir)

dirname = os_ops.get_basename(dir)
path = os_ops.build_path(dir, "..", dirname)
LOCAL__check(path, dir)

dirname = os_ops.get_basename(dir)
path = os_ops.build_path(dir, "..", dirname, "abc.txt")
expected_r = os_ops.build_path(dir, "abc.txt")
LOCAL__check(path, expected_r)

os_ops.rmdir(dir)

logging.info("------------- unknown path")
expected_r = os_ops.build_path(cwd, "abc/file.txt")
LOCAL__check("./abc/file.txt", expected_r)

logging.info("------------- home dir")
LOCAL__check("/~", "/~")

logging.info("------------- test root over-traversal")
# Из любого места системы (даже глубокого) 15 переходов вверх выведут в корень
many_dots = os_ops.build_path(*([".."] * 15))
LOCAL__check(many_dots, "/")

# Корень + еще раз вверх + папка
path = os_ops.build_path("/", "..", "abc")
LOCAL__check(path, "/abc")

logging.info("------------- test multiple slashes")

# TODO: Double slash at the beginning. In POSIX it sometimes
# has a special meaning, let's check it out.
# os.path.abs returns "//abc"
# r = os_ops.get_abs_path("//abc")
# LOCAL__check("//abc", "/abc")

# Slashes in the middle of a relative path
expected_r = os_ops.build_path(cwd, "abc", "def")
LOCAL__check("abc///def", expected_r)

# Relative path ending with a slash
expected_r = os_ops.build_path(cwd, "abc")
LOCAL__check("abc/", expected_r)

logging.info("------------- test raw tilde")
exec_r = os_ops.exec_command(["sh", "-c", "cd ~;pwd"], encoding="utf-8")
assert type(exec_r) is str
expected_r = exec_r.rstrip()
LOCAL__check("~", expected_r)

# Tilda with a ROOT user
LOCAL__check("~root/abc", "/root/abc")

logging.info("------------- test spaces and special chars")
# Folder with quotes, and spaces.
weird_name = "my folder VAR 'single' \"double\""
expected_r = os_ops.build_path(cwd, weird_name)
LOCAL__check(weird_name, expected_r)

# TODO: Folder with dollar signs, quotes, and spaces.
# weird_name = "my folder $VAR 'single' \"double\""
# expected_r = os_ops.build_path(cwd, weird_name)
# LOCAL__check(weird_name, expected_r)

logging.info("OK. GO HOME!")
return

# --------------------------------------------------------------------
@dataclasses.dataclass
class tagGetBaseNameData:
Expand Down