370 lines
15 KiB
Python
370 lines
15 KiB
Python
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
import pytest
|
|
from click.testing import CliRunner
|
|
|
|
from lightning_app.cli import cmd_install, lightning_cli
|
|
from lightning_app.cli.cmd_install import _install_app
|
|
from lightning_app.testing.helpers import RunIf
|
|
|
|
|
|
@mock.patch("lightning_app.cli.cmd_install.subprocess", mock.MagicMock())
|
|
def test_valid_org_app_name():
|
|
runner = CliRunner()
|
|
|
|
# assert a bad app name should fail
|
|
fake_app = "fakeuser/impossible/name"
|
|
result = runner.invoke(lightning_cli.install_app, [fake_app])
|
|
assert "app name format must have organization/app-name" in result.output
|
|
|
|
# assert a good name (but unavailable name) should work
|
|
fake_app = "fakeuser/ALKKLJAUHREKJ21234KLAKJDLF"
|
|
result = runner.invoke(lightning_cli.install_app, [fake_app])
|
|
assert f"app: '{fake_app}' is not available on ⚡ Lightning AI ⚡" in result.output
|
|
assert result.exit_code
|
|
|
|
# assert a good (and availablea name) works
|
|
# This should be an app that's always in the gallery
|
|
real_app = "lightning/invideo"
|
|
result = runner.invoke(lightning_cli.install_app, [real_app])
|
|
assert "Press enter to continue:" in result.output
|
|
|
|
|
|
@pytest.mark.skip(reason="need to figure out how to authorize git clone from the private repo")
|
|
def test_valid_unpublished_app_name():
|
|
runner = CliRunner()
|
|
|
|
# assert warning of non official app given
|
|
real_app = "https://github.com/Lightning-AI/install-app"
|
|
try:
|
|
subprocess.check_output(f"lightning install app {real_app}", shell=True, stderr=subprocess.STDOUT)
|
|
# this condition should never be hit
|
|
assert False
|
|
except subprocess.CalledProcessError as e:
|
|
assert "WARNING" in str(e.output)
|
|
|
|
# assert aborted install
|
|
result = runner.invoke(lightning_cli.install_app, [real_app], input="q")
|
|
assert "Installation aborted!" in result.output
|
|
|
|
# assert a bad app name should fail
|
|
fake_app = "https://github.com/Lightning-AI/install-appdd"
|
|
result = runner.invoke(lightning_cli.install_app, [fake_app, "--yes"])
|
|
assert "Looks like the github url was not found" in result.output
|
|
|
|
# assert a good (and availablea name) works
|
|
result = runner.invoke(lightning_cli.install_app, [real_app])
|
|
assert "Press enter to continue:" in result.output
|
|
|
|
|
|
@pytest.mark.skip(reason="need to figure out how to authorize git clone from the private repo")
|
|
def test_app_install(tmpdir):
|
|
"""Tests unpublished app install."""
|
|
|
|
cwd = os.getcwd()
|
|
os.chdir(tmpdir)
|
|
|
|
real_app = "https://github.com/Lightning-AI/install-app"
|
|
test_app_pip_name = "install-app"
|
|
|
|
# install app and verify it's in the env
|
|
subprocess.check_output(f"lightning install app {real_app} --yes", shell=True)
|
|
new_env_output = subprocess.check_output("pip freeze", shell=True)
|
|
assert test_app_pip_name in str(new_env_output), f"{test_app_pip_name} should be in the env"
|
|
|
|
os.chdir(cwd)
|
|
|
|
|
|
@mock.patch("lightning_app.cli.cmd_install.subprocess", mock.MagicMock())
|
|
def test_valid_org_component_name():
|
|
runner = CliRunner()
|
|
|
|
# assert a bad name should fail
|
|
fake_component = "fakeuser/impossible/name"
|
|
result = runner.invoke(lightning_cli.install_component, [fake_component])
|
|
assert "component name format must have organization/component-name" in result.output
|
|
|
|
# assert a good name (but unavailable name) should work
|
|
fake_component = "fakeuser/ALKKLJAUHREKJ21234KLAKJDLF"
|
|
result = runner.invoke(lightning_cli.install_component, [fake_component])
|
|
assert f"component: '{fake_component}' is not available on ⚡ Lightning AI ⚡" in result.output
|
|
|
|
# assert a good (and availablea name) works
|
|
fake_component = "lightning/lit-slack-messenger"
|
|
result = runner.invoke(lightning_cli.install_component, [fake_component])
|
|
assert "Press enter to continue:" in result.output
|
|
|
|
|
|
def test_unpublished_component_url_parsing():
|
|
runner = CliRunner()
|
|
|
|
# assert a bad name should fail (no git@)
|
|
fake_component = "https://github.com/Lightning-AI/LAI-slack-messenger"
|
|
result = runner.invoke(lightning_cli.install_component, [fake_component])
|
|
assert "Error, your github url must be in the following format" in result.output
|
|
|
|
# assert a good (and availablea name) works
|
|
sha = "14f333456ffb6758bd19458e6fa0bf12cf5575e1"
|
|
real_component = f"git+https://github.com/Lightning-AI/LAI-slack-messenger.git@{sha}"
|
|
result = runner.invoke(lightning_cli.install_component, [real_component])
|
|
assert "Press enter to continue:" in result.output
|
|
|
|
|
|
@pytest.mark.skip(reason="need to figure out how to authorize pip install from the private repo")
|
|
@pytest.mark.parametrize(
|
|
"real_component, test_component_pip_name",
|
|
[
|
|
("lightning/lit-slack-messenger", "lit-slack"),
|
|
(
|
|
"git+https://github.com/Lightning-AI/LAI-slack-messenger.git@14f333456ffb6758bd19458e6fa0bf12cf5575e1",
|
|
"lit-slack",
|
|
),
|
|
],
|
|
)
|
|
def test_component_install(real_component, test_component_pip_name):
|
|
"""Tests both published and unpublished component installs."""
|
|
# uninstall component just in case and verify it's not in the pip output
|
|
env_output = subprocess.check_output(f"pip uninstall {test_component_pip_name} --yes && pip freeze", shell=True)
|
|
assert test_component_pip_name not in str(env_output), f"{test_component_pip_name} should not be in the env"
|
|
|
|
# install component and verify it's in the env
|
|
new_env_output = subprocess.check_output(
|
|
f"lightning install component {real_component} --yes && pip freeze", shell=True
|
|
)
|
|
assert test_component_pip_name in str(new_env_output), f"{test_component_pip_name} should be in the env"
|
|
|
|
# clean up for test
|
|
subprocess.run(f"pip uninstall {test_component_pip_name} --yes", shell=True)
|
|
env_output = subprocess.check_output("pip freeze", shell=True)
|
|
assert test_component_pip_name not in str(
|
|
env_output
|
|
), f"{test_component_pip_name} should not be in the env after cleanup"
|
|
|
|
|
|
def test_prompt_actions():
|
|
# TODO: each of these installs must check that a package is installed in the environment correctly
|
|
app_to_use = "lightning/invideo"
|
|
|
|
runner = CliRunner()
|
|
|
|
# assert that the user can cancel the command with any letter other than y
|
|
result = runner.invoke(lightning_cli.install_app, [app_to_use], input="b")
|
|
assert "Installation aborted!" in result.output
|
|
|
|
# assert that the install happens with --yes
|
|
# result = runner.invoke(lightning_cli.install_app, [app_to_use, "--yes"])
|
|
# assert result.exit_code == 0
|
|
|
|
# assert that the install happens with y
|
|
# result = runner.invoke(lightning_cli.install_app, [app_to_use], input='y')
|
|
# assert result.exit_code == 0
|
|
|
|
# # assert that the install happens with yes
|
|
# result = runner.invoke(lightning_cli.install_app, [app_to_use], input='yes')
|
|
# assert result.exit_code == 0
|
|
|
|
# assert that the install happens with pressing enter
|
|
# result = runner.invoke(lightning_cli.install_app, [app_to_use])
|
|
|
|
# TODO: how to check the output when the user types ctrl+c?
|
|
# result = runner.invoke(lightning_cli.install_app, [app_to_use], input='')
|
|
|
|
|
|
@mock.patch("lightning_app.cli.cmd_install.subprocess", mock.MagicMock())
|
|
def test_version_arg_component(tmpdir, monkeypatch):
|
|
monkeypatch.chdir(tmpdir)
|
|
runner = CliRunner()
|
|
|
|
# Version does not exist
|
|
component_name = "lightning/lit-slack-messenger"
|
|
version_arg = "NOT-EXIST"
|
|
result = runner.invoke(lightning_cli.install_component, [component_name, f"--version={version_arg}"])
|
|
assert f"component: 'Version {version_arg} for {component_name}' is not" in str(result.exception)
|
|
assert result.exit_code == 1
|
|
|
|
# Version exists
|
|
# This somwehow fail in test but not when you actually run it
|
|
version_arg = "0.0.1"
|
|
runner = CliRunner()
|
|
result = runner.invoke(lightning_cli.install_component, [component_name, f"--version={version_arg}", "--yes"])
|
|
assert result.exit_code == 0
|
|
|
|
|
|
@mock.patch("lightning_app.cli.cmd_install.subprocess", mock.MagicMock())
|
|
@mock.patch("lightning_app.cli.cmd_install.os.chdir", mock.MagicMock())
|
|
def test_version_arg_app(tmpdir):
|
|
|
|
# Version does not exist
|
|
app_name = "lightning/invideo"
|
|
version_arg = "NOT-EXIST"
|
|
runner = CliRunner()
|
|
result = runner.invoke(lightning_cli.install_app, [app_name, f"--version={version_arg}"])
|
|
assert f"app: 'Version {version_arg} for {app_name}' is not" in str(result.exception)
|
|
assert result.exit_code == 1
|
|
|
|
# Version exists
|
|
version_arg = "0.0.2"
|
|
runner = CliRunner()
|
|
result = runner.invoke(lightning_cli.install_app, [app_name, f"--version={version_arg}", "--yes"])
|
|
assert result.exit_code == 0
|
|
|
|
|
|
@mock.patch("lightning_app.cli.cmd_install.subprocess", mock.MagicMock())
|
|
@mock.patch("lightning_app.cli.cmd_install.os.chdir", mock.MagicMock())
|
|
@mock.patch("lightning_app.cli.cmd_install._show_install_app_prompt")
|
|
def test_install_resolve_latest_version(mock_show_install_app_prompt, tmpdir):
|
|
|
|
app_name = "lightning/invideo"
|
|
runner = CliRunner()
|
|
with mock.patch("lightning_app.cli.cmd_install.requests.get") as get_api_mock:
|
|
get_api_mock.return_value.json.return_value = {
|
|
"apps": [
|
|
{
|
|
"canDownloadSourceCode": True,
|
|
"version": "0.0.2",
|
|
"name": "lightning/invideo",
|
|
},
|
|
{
|
|
"canDownloadSourceCode": True,
|
|
"version": "0.0.4",
|
|
"name": "lightning/invideo",
|
|
},
|
|
{
|
|
"canDownloadSourceCode": True,
|
|
"version": "0.0.5",
|
|
"name": "another_app",
|
|
},
|
|
]
|
|
}
|
|
runner.invoke(lightning_cli.install_app, [app_name, "--yes"]) # no version specified so latest is installed
|
|
assert mock_show_install_app_prompt.called
|
|
assert mock_show_install_app_prompt.call_args[0][0]["version"] == "0.0.4"
|
|
|
|
|
|
def test_proper_url_parsing():
|
|
|
|
name = "lightning/invideo"
|
|
|
|
# make sure org/app-name name is correct
|
|
org, app = cmd_install._validate_name(name, resource_type="app", example="lightning/lit-slack-component")
|
|
assert org == "lightning"
|
|
assert app == "invideo"
|
|
|
|
# resolve registry (orgs can have a private registry through their environment variables)
|
|
registry_url = cmd_install._resolve_app_registry()
|
|
assert registry_url == "https://lightning.ai/v1/apps"
|
|
|
|
# load the component resource
|
|
component_entry = cmd_install._resolve_resource(registry_url, name=name, version_arg="latest", resource_type="app")
|
|
|
|
source_url, git_url, folder_name, git_sha = cmd_install._show_install_app_prompt(
|
|
component_entry, app, org, True, resource_type="app"
|
|
)
|
|
assert folder_name == "LAI-InVideo-search-App"
|
|
# FixMe: this need to be updated after release with updated org rename
|
|
assert source_url == "https://github.com/Lightning-AI/LAI-InVideo-search-App"
|
|
assert "#ref" not in git_url
|
|
assert git_sha
|
|
|
|
|
|
@RunIf(skip_windows=True)
|
|
def test_install_app_shows_error(tmpdir):
|
|
|
|
app_folder_dir = Path(tmpdir / "some_random_directory").absolute()
|
|
app_folder_dir.mkdir()
|
|
|
|
with pytest.raises(SystemExit, match=f"Folder {str(app_folder_dir)} exists, please delete it and try again."):
|
|
_install_app(source_url=mock.ANY, git_url=mock.ANY, folder_name=str(app_folder_dir), overwrite=False)
|
|
|
|
|
|
# def test_env_creation(tmpdir):
|
|
# cwd = os.getcwd()
|
|
# os.chdir(tmpdir)
|
|
|
|
# # install app
|
|
# cmd_install.app("lightning/install-app", True, cwd=tmpdir)
|
|
|
|
# # assert app folder is installed with venv
|
|
# assert "python" in set(os.listdir(os.path.join(tmpdir, "install-app/bin")))
|
|
|
|
# # assert the deps are in the env
|
|
# env_output = subprocess.check_output("source bin/activate && pip freeze", shell=True)
|
|
# non_env_output = subprocess.check_output("pip freeze", shell=True)
|
|
|
|
# # assert envs are not the same
|
|
# assert env_output != non_env_output
|
|
|
|
# # assert the reqs are in the env created and NOT in the non env
|
|
# reqs = open(os.path.join(tmpdir, "install-app/requirements.txt")).read()
|
|
# assert reqs in str(env_output) and reqs not in str(non_env_output)
|
|
|
|
# # setup.py installs numpy
|
|
# assert "numpy" in str(env_output)
|
|
|
|
# # run the python script to make sure the file works (in a folder)
|
|
# app_file = os.path.join(tmpdir, "install-app/src/app.py")
|
|
# app_output = subprocess.check_output(f"source bin/activate && python {app_file}", shell=True)
|
|
# assert "b'printed a\\ndeps loaded\\n'" == str(app_output)
|
|
|
|
# # run the python script to make sure the file works (in root)
|
|
# app_file = os.path.join(tmpdir, "install-app/app_b.py")
|
|
# app_output = subprocess.check_output(f"source bin/activate && python {app_file}", shell=True)
|
|
# assert "b'printed a\\n'" == str(app_output)
|
|
|
|
# # reset dir
|
|
# os.chdir(cwd)
|
|
|
|
|
|
@mock.patch.dict(os.environ, {"LIGHTNING_APP_REGISTRY": "https://TODO/other_non_PL_registry"})
|
|
def test_private_app_registry():
|
|
registry = cmd_install._resolve_app_registry()
|
|
assert registry == "https://TODO/other_non_PL_registry"
|
|
|
|
|
|
def test_public_app_registry():
|
|
registry = cmd_install._resolve_app_registry()
|
|
assert registry == "https://lightning.ai/v1/apps"
|
|
|
|
|
|
def test_public_component_registry():
|
|
registry = cmd_install._resolve_component_registry()
|
|
assert registry == "https://lightning.ai/v1/components"
|
|
|
|
|
|
@mock.patch.dict(os.environ, {"LIGHTNING_COMPONENT_REGISTRY": "https://TODO/other_non_PL_registry"})
|
|
def test_private_component_registry():
|
|
registry = cmd_install._resolve_component_registry()
|
|
assert registry == "https://TODO/other_non_PL_registry"
|
|
|
|
|
|
@mock.patch("lightning_app.cli.cmd_install.subprocess")
|
|
@mock.patch("lightning_app.cli.cmd_install.os.chdir", mock.MagicMock())
|
|
@pytest.mark.parametrize(
|
|
"source_url, git_url, git_sha",
|
|
[
|
|
(
|
|
"https://github.com/PyTorchLightning/lightning-quick-start",
|
|
"https://<github_token>@github.com/PyTorchLightning/lightning-quick-start",
|
|
None,
|
|
),
|
|
(
|
|
"https://github.com/PyTorchLightning/lightning-quick-start",
|
|
"https://<github_token>@github.com/PyTorchLightning/lightning-quick-start",
|
|
"git_sha",
|
|
),
|
|
],
|
|
)
|
|
def test_install_app_process(subprocess_mock, source_url, git_url, git_sha, tmpdir):
|
|
app_folder_dir = Path(tmpdir / "some_random_directory").absolute()
|
|
app_folder_dir.mkdir()
|
|
|
|
_install_app(source_url, git_url, folder_name=str(app_folder_dir), overwrite=True, git_sha=git_sha)
|
|
assert subprocess_mock.check_output.call_args_list[0].args == (["git", "clone", git_url],)
|
|
if git_sha:
|
|
assert subprocess_mock.check_output.call_args_list[1].args == (["git", "checkout", git_sha],)
|
|
assert subprocess_mock.call.call_args_list[0].args == ("pip install -r requirements.txt",)
|
|
assert subprocess_mock.call.call_args_list[1].args == ("pip install -e .",)
|