Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utility functions to get name of GNU tar executable in OS-agnostic manner #276

Merged
merged 24 commits into from
Oct 10, 2023
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
2 changes: 2 additions & 0 deletions install/install-mac-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ brew install awscli;

# pkg-config is required for building the wheel
brew install pkg-config;

brew install gnu-tar
27 changes: 27 additions & 0 deletions python/python/bystro/utils/tar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Utility functions for safely invoking tar in cross-OS manner."""
import shutil
import sys


def _get_executable_name(name: str) -> str:
executable_name = shutil.which(name)
if executable_name is None:
err_msg = f"executable `{name}` not found on system"
raise OSError(err_msg)
return executable_name


def _get_gnu_tar_executable_name() -> str:
"""Find the name of the GNU tar executable on user's instance."""
# Macs use bsdtar by default. bsdtar is not fully compatible with
# GNU tar, the implementation we use. So if we're on a mac, we'll
# want to use 'gtar' instead of 'tar'.
poneill marked this conversation as resolved.
Show resolved Hide resolved
if sys.platform == "linux":
return _get_executable_name("tar")
if sys.platform == "darwin":
return _get_executable_name("gtar")
err_msg = f"Operating system must be linux or macosx, got {sys.platform} instead."
raise OSError(err_msg)


GNU_TAR_EXECUTABLE_NAME = _get_gnu_tar_executable_name()
70 changes: 70 additions & 0 deletions python/python/bystro/utils/tests/test_tar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Test functions for utils module."""
import shutil
import subprocess
import sys
from subprocess import CalledProcessError
from typing import Any, Callable, NoReturn
from unittest.mock import Mock

import pytest
from bystro.utils.tar import (
GNU_TAR_EXECUTABLE_NAME,
_get_gnu_tar_executable_name,
)


def test_GNU_TAR_EXECUTABLE_NAME():
assert GNU_TAR_EXECUTABLE_NAME in ["/usr/bin/tar", "/opt/homebrew/bin/gtar"]


# These tests check that we correctly infer the name of the GNU tar
# executable regardless of the operating system we find ourselves
# running on, or that we raise an appropriate error if we can't
# determine the correct executable. The
# _mock_subprocess_run_... methods mocks all interaction with
# subprocess.run under various OS scenarios and the tests ensure that
# we return the correct GNU tar executable name or raise the appropriate
# exception.


def _mock_subprocess(stdout_string: str) -> Callable[[Any, Any], Mock]:
"""Create a subprocess.run mock that returns stdout_string."""
mock = Mock()
mock.stdout = stdout_string

def f(*args: list[str], **kwargs: dict[str, Any]) -> Mock:
del args, kwargs
return mock

return f


def test_get_gnu_tar_executable_macosx_gnu_tar_installed(monkeypatch):
monkeypatch.setattr(subprocess, "run", _mock_subprocess("/opt/homebrew/bin/gtar\n"))
monkeypatch.setattr(sys, "platform", "darwin")
monkeypatch.setattr(shutil, "which", lambda _name: "/opt/homebrew/bin/gtar")
assert "/opt/homebrew/bin/gtar" == _get_gnu_tar_executable_name()


def test_get_gnu_tar_executable_macosx_gnu_tar_not_installed(monkeypatch):
def raise_error(args: list[str], kwargs: dict[str, Any]) -> NoReturn:
del args, kwargs
raise CalledProcessError(cmd=["which", "gtar"], returncode=1)

monkeypatch.setattr(shutil, "which", lambda _name: None)
monkeypatch.setattr(sys, "platform", "darwin")
with pytest.raises(OSError, match="executable `gtar` not found on system"):
_get_gnu_tar_executable_name()


def test_get_gnu_tar_executable_linux(monkeypatch):
monkeypatch.setattr(subprocess, "run", _mock_subprocess("/usr/bin/tar\n"))
monkeypatch.setattr(sys, "platform", "linux")
monkeypatch.setattr(shutil, "which", lambda _name: "/usr/bin/tar")
assert "/usr/bin/tar" == _get_gnu_tar_executable_name()


def test_get_gnu_tar_executable_unknown_os(monkeypatch):
monkeypatch.setattr(sys, "platform", "some unknown OS")
with pytest.raises(OSError, match="got some unknown OS instead"):
_get_gnu_tar_executable_name()