Skip to content

Commit

Permalink
Doctor command tweaks (#98)
Browse files Browse the repository at this point in the history
* feat(doctor): fix mac brew and add tests
* feat(doctor): add python and python 3
* fix(doctor): Fixed windows npm handling and added exception logging for better debuggability

Co-authored-by: Rob Moore <[email protected]>
  • Loading branch information
mzaatar and robdmoore authored Dec 16, 2022
1 parent 036fde5 commit 8013159
Show file tree
Hide file tree
Showing 15 changed files with 364 additions and 44 deletions.
5 changes: 3 additions & 2 deletions src/algokit/cli/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ def doctor_command(*, copy_to_clipboard: bool) -> None:
service_outputs["Docker Compose"] = doctor_functions.get_docker_compose_info()
service_outputs["Git"] = doctor_functions.get_git_info(os_type)
service_outputs["AlgoKit Python"] = doctor_functions.get_algokit_python_info()
service_outputs["Global Python"] = doctor_functions.get_global_python_info()
service_outputs["Global Python"] = doctor_functions.get_global_python_info("python")
service_outputs["Global Python3"] = doctor_functions.get_global_python_info("python3")
service_outputs["Pipx"] = doctor_functions.get_pipx_info()
service_outputs["Poetry"] = doctor_functions.get_poetry_info()
service_outputs["Node.js"] = doctor_functions.get_node_info()
service_outputs["Npm"] = doctor_functions.get_npm_info()
service_outputs["Npm"] = doctor_functions.get_npm_info(os_type)

critical_services = ["Docker", "Docker Compose", "Git"]
# Print the status details
Expand Down
58 changes: 36 additions & 22 deletions src/algokit/core/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def get_algokit_info() -> ProcessResult:
pipx_venv_location = algokit_pip_line[0].split("=")[1]
algokit_location = f"{pipx_venv_location}/algokit"
return ProcessResult(f"{algokit_version} {algokit_location}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting algokit version failed: {e}", exc_info=True)
return ProcessResult("None found", 1)


Expand All @@ -53,16 +54,18 @@ def get_choco_info() -> ProcessResult:
process_results = proc.run(["choco"]).output.splitlines()[0].split(" v")[1]
major, minor, build = get_version_from_str(process_results)
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting chocolatey version failed: {e}", exc_info=True)
return ProcessResult("None found", 1)


def get_brew_info() -> ProcessResult:
try:
process_results = proc.run(["brew", "-v"]).output.splitlines()[0].split(" ")[1]
process_results = proc.run(["brew", "-v"]).output.splitlines()[0].split(" ")[1].split("-")[0]
major, minor, build = get_version_from_str(process_results)
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting brew version failed: {e}", exc_info=True)
return ProcessResult("None found", 1)


Expand All @@ -86,7 +89,8 @@ def get_docker_info() -> ProcessResult:
process_results = proc.run(["docker", "-v"]).output.splitlines()[0].split(" ")[2].split(",")[0]
major, minor, build = get_version_from_str(process_results)
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting docker version failed: {e}", exc_info=True)
return ProcessResult(
(
"None found.\nDocker required to `run algokit sandbox` command;"
Expand All @@ -109,16 +113,18 @@ def get_docker_compose_info() -> ProcessResult:
),
process_results.exit_code if minimum_version_met else 1,
)
except Exception:
except Exception as e:
logger.debug(f"Getting docker compose version failed: {e}", exc_info=True)
return ProcessResult(f"None found. {DOCKER_COMPOSE_MINIMUM_VERSION_MESSAGE}", 1)


def get_git_info(system: str) -> ProcessResult:
try:
process_results = proc.run(["git", "--version"]).output.splitlines()[0].split(" ")[2]
major, minor, build = map(int, process_results.split("."))
major, minor, build = get_version_from_str(process_results)
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting git version failed: {e}", exc_info=True)
if system == "windows":
return ProcessResult(
(
Expand All @@ -138,28 +144,32 @@ def get_git_info(system: str) -> ProcessResult:
def get_algokit_python_info() -> ProcessResult:
try:
return ProcessResult(
f"{sys_version_info.major}.{sys_version_info.minor}.{sys_version_info.micro} {sys_executable}", 0
f"{sys_version_info.major}.{sys_version_info.minor}.{sys_version_info.micro} (location: {sys_executable})",
0,
)
except Exception:
except Exception as e:
logger.debug(f"Getting AlgoKit python version failed: {e}", exc_info=True)
return ProcessResult("None found.", 1)


def get_global_python_info() -> ProcessResult:
def get_global_python_info(python_command_name: str) -> ProcessResult:
try:
major, minor, build = get_version_from_str(
proc.run(["python3", "--version"]).output.splitlines()[0].split(" ")[1]
proc.run([python_command_name, "--version"]).output.splitlines()[0].split(" ")[1]
)
global_python3_location = shutil.which("python3")
return ProcessResult(f"{major}.{minor}.{build} {global_python3_location}", 0)
except Exception:
global_python3_location = shutil.which(python_command_name)
return ProcessResult(f"{major}.{minor}.{build} (location: {global_python3_location})", 0)
except Exception as e:
logger.debug(f"Getting python version failed: {e}", exc_info=True)
return ProcessResult("None found.", 1)


def get_pipx_info() -> ProcessResult:
try:
major, minor, build = get_version_from_str(proc.run(["pipx", "--version"]).output.splitlines()[0])
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting pipx version failed: {e}", exc_info=True)
return ProcessResult(
"None found.\nPipx is required to install Poetry; install via https://pypa.github.io/pipx/", 1
)
Expand All @@ -170,7 +180,8 @@ def get_poetry_info() -> ProcessResult:
process_results = proc.run(["poetry", "--version"]).output.splitlines()[-1].split("version ")[1].split(")")[0]
major, minor, build = get_version_from_str(process_results)
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting poetry version failed: {e}", exc_info=True)
return ProcessResult(
(
"None found.\nPoetry is required for some Python-based templates; install via `algokit bootstrap` "
Expand All @@ -185,7 +196,8 @@ def get_node_info() -> ProcessResult:
process_results = proc.run(["node", "-v"]).output.splitlines()[0].split("v")[1]
major, minor, build = get_version_from_str(process_results)
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting node version failed: {e}", exc_info=True)
return ProcessResult(
(
"None found.\nNode.js is required for some Node.js-based templates; install via `algokit bootstrap` "
Expand All @@ -195,12 +207,13 @@ def get_node_info() -> ProcessResult:
)


def get_npm_info() -> ProcessResult:
def get_npm_info(system: str) -> ProcessResult:
try:
process_results = proc.run(["npm", "-v"]).output.splitlines()[0]
process_results = proc.run(["npm" if system != "windows" else "npm.cmd", "-v"]).output.splitlines()[0]
major, minor, build = get_version_from_str(process_results)
return ProcessResult(f"{major}.{minor}.{build}", 0)
except Exception:
except Exception as e:
logger.debug(f"Getting npm version failed: {e}", exc_info=True)
return ProcessResult("None found.", 1)


Expand All @@ -211,5 +224,6 @@ def is_minimum_version(system_version: str, minimum_version: str) -> bool:


def get_version_from_str(version: str) -> tuple[int, int, int]:
major, minor, build = map(int, version.split("."))
# take only the first three parts x.y.z of the version to ignore weird version
major, minor, build = map(int, version.split(".")[:3])
return major, minor, build
96 changes: 95 additions & 1 deletion tests/doctor/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def mock_dependencies(mocker: MockerFixture) -> None:
mocked_date.now.return_value = datetime(1990, 12, 31, 10, 9, 8)
# Mock shutil
mocked_shutil = mocker.patch("algokit.core.doctor.shutil")
mocked_shutil.which.return_value = "/Library/Frameworks/Python.framework/Versions/3.11/bin/python3"
mocked_shutil.which.side_effect = mock_shutil_which
# Mock sys - Tuple[int, int, int, str, int]
mocker.patch("algokit.core.doctor.sys_version_info", VersionInfoType(3, 6, 2, "blah", 0))
mocker.patch("algokit.core.doctor.sys_executable", "{current_working_directory}/.venv/bin/python")
Expand All @@ -54,11 +54,21 @@ def mock_happy_values(proc_mock: ProcMock) -> None:
proc_mock.set_output(["docker", "-v"], ["Docker version 20.10.21, build baeda1f"])
proc_mock.set_output(["docker-compose", "-v"], ["Docker Compose version v2.12.2"])
proc_mock.set_output(["git", "--version"], ["git version 2.37.1 (Apple Git-137.1)"])
proc_mock.set_output(["python", "--version"], ["Python 3.10.0"])
proc_mock.set_output(["python3", "--version"], ["Python 3.11.0"])
proc_mock.set_output(["pipx", "--version"], ["1.1.0"])
proc_mock.set_output(["poetry", "--version"], ["blah blah", "", "Poetry (version 1.2.2)"])
proc_mock.set_output(["node", "-v"], ["v18.12.1"])
proc_mock.set_output(["npm", "-v"], ["8.19.2"])
proc_mock.set_output(["npm.cmd", "-v"], ["8.19.2"])


def mock_shutil_which(python_command_name: str) -> str:
if python_command_name == "python":
return "/Library/Frameworks/Python.framework/Versions/3.10/bin/python"
if python_command_name == "python3":
return "/Library/Frameworks/Python.framework/Versions/3.11/bin/python3"
return ""


def make_output_scrubber(**extra_tokens: str) -> Scrubber:
Expand Down Expand Up @@ -145,13 +155,72 @@ def test_doctor_with_git_warning_on_windows(mocker: MockerFixture, proc_mock: Pr


def test_doctor_all_failed_on_mac(mocker: MockerFixture, proc_mock: ProcMock):

mocker.patch("algokit.core.doctor.sys_version_info", "")
mocker.patch("algokit.core.doctor.sys_executable", "")

proc_mock.set_output(["pipx", "list", "--short"], [])
proc_mock.set_output(["pipx", "environment"], [])
proc_mock.set_output(["choco"], [])
proc_mock.set_output(["brew", "-v"], [])
proc_mock.set_output(["docker", "-v"], [])
proc_mock.set_output(["docker-compose", "-v"], [])
proc_mock.set_output(["git", "--version"], [])
proc_mock.set_output(["python", "--version"], [])
proc_mock.set_output(["python3", "--version"], [])
proc_mock.set_output(["pipx", "--version"], [])
proc_mock.set_output(["poetry", "--version"], [])
proc_mock.set_output(["node", "-v"], [])
proc_mock.set_output(["npm", "-v"], [])

result = invoke("doctor")

assert result.exit_code == 1
verify(result.output, scrubber=make_output_scrubber())


def test_doctor_all_failed_on_windows(mocker: MockerFixture, proc_mock: ProcMock):
mocked_os = mocker.patch("algokit.cli.doctor.platform")
mocked_os.system.return_value = "windows"

mocker.patch("algokit.core.doctor.sys_version_info", "")
mocker.patch("algokit.core.doctor.sys_executable", "")

proc_mock.set_output(["pipx", "list", "--short"], [])
proc_mock.set_output(["pipx", "environment"], [])
proc_mock.set_output(["choco"], [])
proc_mock.set_output(["brew", "-v"], [])
proc_mock.set_output(["docker", "-v"], [])
proc_mock.set_output(["docker-compose", "-v"], [])
proc_mock.set_output(["git", "--version"], [])
proc_mock.set_output(["python", "--version"], [])
proc_mock.set_output(["python3", "--version"], [])
proc_mock.set_output(["pipx", "--version"], [])
proc_mock.set_output(["poetry", "--version"], [])
proc_mock.set_output(["node", "-v"], [])
proc_mock.set_output(["npm.cmd", "-v"], [])

result = invoke("doctor")

assert result.exit_code == 1
verify(result.output, scrubber=make_output_scrubber())


def test_doctor_all_failed_on_linux(mocker: MockerFixture, proc_mock: ProcMock):
mocked_os = mocker.patch("algokit.cli.doctor.platform")
mocked_os.system.return_value = "linux"

mocker.patch("algokit.core.doctor.sys_version_info", "")
mocker.patch("algokit.core.doctor.sys_executable", "")

proc_mock.set_output(["pipx", "list", "--short"], [])
proc_mock.set_output(["pipx", "environment"], [])
proc_mock.set_output(["choco"], [])
proc_mock.set_output(["brew", "-v"], [])
proc_mock.set_output(["docker", "-v"], [])
proc_mock.set_output(["docker-compose", "-v"], [])
proc_mock.set_output(["git", "--version"], [])
proc_mock.set_output(["python", "--version"], [])
proc_mock.set_output(["python3", "--version"], [])
proc_mock.set_output(["pipx", "--version"], [])
proc_mock.set_output(["poetry", "--version"], [])
Expand All @@ -162,3 +231,28 @@ def test_doctor_all_failed_on_mac(mocker: MockerFixture, proc_mock: ProcMock):

assert result.exit_code == 1
verify(result.output, scrubber=make_output_scrubber())


def test_doctor_with_weird_values_on_mac(mocker: MockerFixture, proc_mock: ProcMock):
proc_mock.set_output(["brew", "-v"], ["Homebrew 3.6.15-31-g82d89bb"])

result = invoke("doctor")

assert result.exit_code == 0
verify(result.output, scrubber=make_output_scrubber())


def test_doctor_with_weird_values_on_windows(mocker: MockerFixture, proc_mock: ProcMock):
mocked_os = mocker.patch("algokit.cli.doctor.platform")
mocked_os.system.return_value = "windows"

proc_mock.set_output(["git", "--version"], ["git version 2.31.0.windows.1"])
proc_mock.set_output(
["choco"], ["Chocolatey v0.10.15", "choco: Please run 'choco -?' or 'choco <command> -?' for help menu."]
)
proc_mock.set_output(["npm.cmd", "-v"], [" 16.17.0 "])

result = invoke("doctor")

assert result.exit_code == 0
verify(result.output, scrubber=make_output_scrubber())
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
DEBUG: Running 'pipx list --short' in '{current_working_directory}'
DEBUG: Getting algokit version failed: list index out of range
DEBUG: Running 'docker -v' in '{current_working_directory}'
DEBUG: Getting docker version failed: list index out of range
DEBUG: Running 'docker-compose -v' in '{current_working_directory}'
DEBUG: Getting docker compose version failed: list index out of range
DEBUG: Running 'git --version' in '{current_working_directory}'
DEBUG: Getting git version failed: list index out of range
DEBUG: Getting AlgoKit python version failed: 'str' object has no attribute 'major'
DEBUG: Running 'python --version' in '{current_working_directory}'
DEBUG: Getting python version failed: list index out of range
DEBUG: Running 'python3 --version' in '{current_working_directory}'
DEBUG: Getting python version failed: list index out of range
DEBUG: Running 'pipx --version' in '{current_working_directory}'
DEBUG: Getting pipx version failed: list index out of range
DEBUG: Running 'poetry --version' in '{current_working_directory}'
DEBUG: Getting poetry version failed: list index out of range
DEBUG: Running 'node -v' in '{current_working_directory}'
DEBUG: Getting node version failed: list index out of range
DEBUG: Running 'npm -v' in '{current_working_directory}'
DEBUG: Getting npm version failed: list index out of range
Time: 1990-12-31T10:09:08
AlgoKit: None found
OS: Unix/Linux linux_version
Docker: None found.
Docker required to `run algokit sandbox` command; install via https://docs.docker.com/get-docker/
Docker Compose: None found.
Docker Compose 2.5 required to `run algokit sandbox command`; install via https://docs.docker.com/compose/install/
Git: None found.
Git required to run `algokit init`; install via https://github.com/git-guides/install-git
AlgoKit Python: None found.
Global Python: None found.
Global Python3: None found.
Pipx: None found.
Pipx is required to install Poetry; install via https://pypa.github.io/pipx/
Poetry: None found.
Poetry is required for some Python-based templates; install via `algokit bootstrap` within project directory, or via https://python-poetry.org/docs/#installation
Node.js: None found.
Node.js is required for some Node.js-based templates; install via `algokit bootstrap` within project directory, or via https://nodejs.dev/en/learn/how-to-install-nodejs/
Npm: None found.
If you are experiencing a problem with algokit, feel free to submit an issue via https://github.com/algorandfoundation/algokit-cli/issues/new; please include this output, if you want to populate this message in your clipboard, run `algokit doctor -c`
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
DEBUG: Running 'pipx list --short' in '{current_working_directory}'
DEBUG: Getting algokit version failed: list index out of range
DEBUG: Running 'brew -v' in '{current_working_directory}'
DEBUG: Getting brew version failed: list index out of range
DEBUG: Running 'docker -v' in '{current_working_directory}'
DEBUG: Getting docker version failed: list index out of range
DEBUG: Running 'docker-compose -v' in '{current_working_directory}'
DEBUG: Getting docker compose version failed: list index out of range
DEBUG: Running 'git --version' in '{current_working_directory}'
DEBUG: Getting git version failed: list index out of range
DEBUG: Getting AlgoKit python version failed: 'str' object has no attribute 'major'
DEBUG: Running 'python --version' in '{current_working_directory}'
DEBUG: Getting python version failed: list index out of range
DEBUG: Running 'python3 --version' in '{current_working_directory}'
DEBUG: Getting python version failed: list index out of range
DEBUG: Running 'pipx --version' in '{current_working_directory}'
DEBUG: Getting pipx version failed: list index out of range
DEBUG: Running 'poetry --version' in '{current_working_directory}'
DEBUG: Getting poetry version failed: list index out of range
DEBUG: Running 'node -v' in '{current_working_directory}'
DEBUG: Getting node version failed: list index out of range
DEBUG: Running 'npm -v' in '{current_working_directory}'
DEBUG: Getting npm version failed: list index out of range
Time: 1990-12-31T10:09:08
AlgoKit: None found
Brew: None found
Expand All @@ -18,8 +31,9 @@ Docker Compose: None found.
Docker Compose 2.5 required to `run algokit sandbox command`; install via https://docs.docker.com/compose/install/
Git: None found.
Git required to run `algokit init`; install via https://github.com/git-guides/install-git
AlgoKit Python: 3.6.2 {current_working_directory}/.venv/bin/python
AlgoKit Python: None found.
Global Python: None found.
Global Python3: None found.
Pipx: None found.
Pipx is required to install Poetry; install via https://pypa.github.io/pipx/
Poetry: None found.
Expand Down
Loading

1 comment on commit 8013159

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/algokit
   __init__.py15753%6–13, 17–24, 32–34
   __main__.py220%1–3
src/algokit/cli
   bootstrap.py291934%12, 23–62
   init.py1531491%54, 208, 211–213, 224, 262, 299, 308–310, 313–318, 333
src/algokit/core
   click_extensions.py472057%40–43, 50, 56, 67–68, 73–74, 79–80, 91, 104–114
   conf.py27967%10–17, 24, 26
   log_handlers.py68987%44–45, 50–51, 63, 112–116, 125
   proc.py44198%94
   sandbox.py106793%82, 147, 163, 178–180, 195
TOTAL8028889% 

Tests Skipped Failures Errors Time
78 0 💤 0 ❌ 0 🔥 6.756s ⏱️

Please sign in to comment.