[App] testing `lightning` in `lightning-app` package (#15286)
* hot fix * update * update * update * cmd * pkg * update * Apply suggestions from code review * update * update * Apply suggestions from code review * update * update * update * update * update * update * update * update * update * update * update * update * update * update * cleaning * Apply suggestions from code review * update * update Co-authored-by: Jirka <jirka.borovec@seznam.cz> Co-authored-by: Jirka Borovec <Borda@users.noreply.github.com>
This commit is contained in:
parent
777a12aa69
commit
bd658441bd
|
@ -58,8 +58,8 @@ jobs:
|
|||
name: "template_streamlit_ui"
|
||||
'App: template_react_ui':
|
||||
name: "template_react_ui"
|
||||
'App: template_jupyterlab': # TODO: clarify where these files lives
|
||||
name: "template_jupyterlab"
|
||||
# 'App: template_jupyterlab': # TODO: clarify where these files lives
|
||||
# name: "template_jupyterlab"
|
||||
'App: idle_timeout':
|
||||
name: "idle_timeout"
|
||||
'App: collect_failures':
|
||||
|
@ -70,10 +70,10 @@ jobs:
|
|||
name: "drive"
|
||||
'App: payload':
|
||||
name: "payload"
|
||||
# 'App: commands_and_api':
|
||||
# name: "commands_and_api"
|
||||
'App: quick_start':
|
||||
name: "quick_start"
|
||||
'App: commands_and_api':
|
||||
name: "commands_and_api"
|
||||
# 'App: quick_start': # TODO: Failed during installation
|
||||
# name: "quick_start"
|
||||
timeoutInMinutes: "30"
|
||||
cancelTimeoutInMinutes: "2"
|
||||
# values: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/phases?view=azure-devops&tabs=yaml#workspace
|
||||
|
@ -101,12 +101,10 @@ jobs:
|
|||
path: $(pip_cache_dir)
|
||||
displayName: Cache pip
|
||||
|
||||
- bash: git restore . && python -m pip install -e .[ui] --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
|
||||
env:
|
||||
PACKAGE_NAME: app
|
||||
displayName: 'Install lightning app'
|
||||
- bash: git restore . && python -m pip install -e . --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
|
||||
displayName: 'Install Lightning'
|
||||
|
||||
- bash: python -m pip install -r requirements/app/test.txt --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
|
||||
- bash: python -m pip install -r requirements/app/test.txt -r requirements/app/ui.txt --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
|
||||
env:
|
||||
PACKAGE_NAME: pytorch
|
||||
displayName: 'Install dependencies'
|
||||
|
@ -116,14 +114,14 @@ jobs:
|
|||
python -m playwright install # --with-deps
|
||||
displayName: 'Install Playwright system dependencies'
|
||||
|
||||
- bash: |
|
||||
rm -rf examples/app_template_jupyterlab || true
|
||||
git clone https://github.com/Lightning-AI/LAI-lightning-template-jupyterlab-App examples/app_template_jupyterlab
|
||||
cp examples/app_template_jupyterlab/tests/test_template_jupyterlab.py tests/tests_app_examples/test_template_jupyterlab.py
|
||||
condition: eq(variables['name'], 'template_jupyterlab')
|
||||
displayName: 'Clone Template Jupyter Lab Repo'
|
||||
# - bash: |
|
||||
# rm -rf examples/app_template_jupyterlab || true
|
||||
# git clone https://github.com/Lightning-AI/LAI-lightning-template-jupyterlab-App examples/app_template_jupyterlab
|
||||
# cp examples/app_template_jupyterlab/tests/test_template_jupyterlab.py tests/tests_app_examples/test_template_jupyterlab.py
|
||||
# condition: eq(variables['name'], 'template_jupyterlab')
|
||||
# displayName: 'Clone Template Jupyter Lab Repo'
|
||||
|
||||
- bash: |
|
||||
- bash: |
|
||||
rm -rf examples/app_template_react_ui || true
|
||||
git clone https://github.com/Lightning-AI/lightning-template-react examples/app_template_react_ui
|
||||
condition: eq(variables['name'], 'template_react_ui')
|
||||
|
|
|
@ -48,4 +48,4 @@ class Flow(L.LightningFlow):
|
|||
self._exit("Application End!")
|
||||
|
||||
|
||||
app = L.LightningApp(Flow(), debug=True)
|
||||
app = L.LightningApp(Flow())
|
||||
|
|
|
@ -27,8 +27,10 @@ from lightning_app.utilities.app_logs import _app_logs_reader
|
|||
from lightning_app.utilities.cloud import _get_project
|
||||
from lightning_app.utilities.enum import CacheCallsKeys
|
||||
from lightning_app.utilities.imports import _is_playwright_available, requires
|
||||
from lightning_app.utilities.log import get_logfile
|
||||
from lightning_app.utilities.logs_socket_api import _LightningLogsSocketAPI
|
||||
from lightning_app.utilities.network import _configure_session, LightningClient
|
||||
from lightning_app.utilities.packaging.lightning_utils import get_dist_path_if_editable_install
|
||||
from lightning_app.utilities.proxies import ProxyWorkRun
|
||||
|
||||
if _is_playwright_available():
|
||||
|
@ -222,6 +224,10 @@ def run_app_in_cloud(
|
|||
basename = app_folder.split("/")[-1]
|
||||
PR_NUMBER = os.getenv("PR_NUMBER", None)
|
||||
|
||||
is_editable_mode = get_dist_path_if_editable_install("lightning")
|
||||
if not is_editable_mode and PR_NUMBER is not None:
|
||||
raise Exception("Lightning requires to be installed in editable mode in the CI.")
|
||||
|
||||
TEST_APP_NAME = os.getenv("TEST_APP_NAME", basename)
|
||||
os.environ["TEST_APP_NAME"] = TEST_APP_NAME
|
||||
|
||||
|
@ -253,29 +259,35 @@ def run_app_in_cloud(
|
|||
env_copy["LIGHTNING_DEBUG"] = "1"
|
||||
shutil.copytree(app_folder, tmpdir, dirs_exist_ok=True)
|
||||
# TODO - add -no-cache to the command line.
|
||||
process = Popen(
|
||||
(
|
||||
[
|
||||
sys.executable,
|
||||
"-m",
|
||||
"lightning",
|
||||
"run",
|
||||
"app",
|
||||
app_name,
|
||||
"--cloud",
|
||||
"--name",
|
||||
name,
|
||||
"--open-ui",
|
||||
"false",
|
||||
]
|
||||
+ extra_args
|
||||
),
|
||||
cwd=tmpdir,
|
||||
env=env_copy,
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stderr,
|
||||
)
|
||||
process.wait()
|
||||
stdout_path = get_logfile(f"run_app_in_cloud_{name}")
|
||||
with open(stdout_path, "w") as stdout:
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"lightning",
|
||||
"run",
|
||||
"app",
|
||||
app_name,
|
||||
"--cloud",
|
||||
"--name",
|
||||
name,
|
||||
"--open-ui",
|
||||
"false",
|
||||
]
|
||||
process = Popen((cmd + extra_args), cwd=tmpdir, env=env_copy, stdout=stdout, stderr=sys.stderr)
|
||||
process.wait()
|
||||
|
||||
if is_editable_mode:
|
||||
# Added to ensure the current code is properly uploaded.
|
||||
# Otherwise, it could result in un-tested PRs.
|
||||
pkg_found = False
|
||||
with open(stdout_path) as fo:
|
||||
for line in fo.readlines():
|
||||
if "Packaged Lightning with your application" in line:
|
||||
pkg_found = True
|
||||
print(line) # TODO: use logging
|
||||
assert pkg_found
|
||||
os.remove(stdout_path)
|
||||
|
||||
# 5. Print your application name
|
||||
print(f"The Lightning App Name is: [bold magenta]{name}[/bold magenta]")
|
||||
|
|
|
@ -86,7 +86,7 @@ def _app_logs_reader(
|
|||
th.start()
|
||||
|
||||
# Print logs from queue when log event is available
|
||||
flow = "Your app has started. View it in your browser"
|
||||
flow = "Your app has started."
|
||||
work = "USER_RUN_WORK"
|
||||
start_timestamps = {}
|
||||
|
||||
|
@ -94,6 +94,7 @@ def _app_logs_reader(
|
|||
try:
|
||||
while True:
|
||||
log_event: _LogEvent = read_queue.get(timeout=None if follow else 1.0)
|
||||
|
||||
token = flow if log_event.component_name == "flow" else work
|
||||
if token in log_event.message:
|
||||
start_timestamps[log_event.component_name] = log_event.timestamp
|
||||
|
|
|
@ -84,6 +84,9 @@ class CloudCompute:
|
|||
if self._internal_id is None:
|
||||
self._internal_id = "default" if self.name == "default" else uuid4().hex[:7]
|
||||
|
||||
# Internal arguments for now.
|
||||
self.preemptible = False
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
_verify_mount_root_dirs_are_unique(self.mounts)
|
||||
return {"type": __CLOUD_COMPUTE_IDENTIFIER__, **asdict(self)}
|
||||
|
|
|
@ -56,6 +56,7 @@ def _prepare_wheel(path):
|
|||
["rm", "-r", "dist"], stdout=logfile, stderr=logfile, bufsize=0, close_fds=True, cwd=path
|
||||
) as proc:
|
||||
proc.wait()
|
||||
|
||||
with subprocess.Popen(
|
||||
["python", "setup.py", "sdist"],
|
||||
stdout=logfile,
|
||||
|
@ -85,26 +86,30 @@ def get_dist_path_if_editable_install(project_name) -> str:
|
|||
for path_item in sys.path:
|
||||
if not os.path.isdir(path_item):
|
||||
continue
|
||||
|
||||
egg_info = os.path.join(path_item, project_name + ".egg-info")
|
||||
if os.path.isdir(egg_info):
|
||||
return path_item
|
||||
return ""
|
||||
|
||||
|
||||
def _prepare_lightning_wheels_and_requirements(root: Path) -> Optional[Callable]:
|
||||
def _prepare_lightning_wheels_and_requirements(root: Path, package_name: str = "lightning") -> Optional[Callable]:
|
||||
"""This function determines if lightning is installed in editable mode (for developers) and packages the
|
||||
current lightning source along with the app.
|
||||
|
||||
For normal users who install via PyPi or Conda, then this function does not do anything.
|
||||
"""
|
||||
|
||||
if not get_dist_path_if_editable_install("lightning-app"):
|
||||
if not get_dist_path_if_editable_install(package_name):
|
||||
return
|
||||
|
||||
# this is patch for installing `lightning-app` as standalone package
|
||||
if package_name == "lightning_app":
|
||||
os.environ["PACKAGE_NAME"] = "app"
|
||||
|
||||
# Packaging the Lightning codebase happens only inside the `lightning` repo.
|
||||
git_dir_name = get_dir_name() if check_github_repository() else None
|
||||
|
||||
is_lightning = git_dir_name and git_dir_name == "lightning_app"
|
||||
is_lightning = git_dir_name and git_dir_name == package_name
|
||||
|
||||
if (PACKAGE_LIGHTNING is None and not is_lightning) or PACKAGE_LIGHTNING == "0":
|
||||
return
|
||||
|
@ -112,7 +117,8 @@ def _prepare_lightning_wheels_and_requirements(root: Path) -> Optional[Callable]
|
|||
download_frontend(_PROJECT_ROOT)
|
||||
_prepare_wheel(_PROJECT_ROOT)
|
||||
|
||||
logger.info(f"Packaged Lightning with your application. Version: {version}")
|
||||
# todo: check why logging.info is missing in outputs
|
||||
print(f"Packaged Lightning with your application. Version: {version}")
|
||||
|
||||
tar_name = _copy_tar(_PROJECT_ROOT, root)
|
||||
|
||||
|
@ -125,7 +131,8 @@ def _prepare_lightning_wheels_and_requirements(root: Path) -> Optional[Callable]
|
|||
if launcher_project_path:
|
||||
from lightning_launcher.__version__ import __version__ as launcher_version
|
||||
|
||||
logger.info(f"Packaged Lightning Launcher with your application. Version: {launcher_version}")
|
||||
# todo: check why logging.info is missing in outputs
|
||||
print(f"Packaged Lightning Launcher with your application. Version: {launcher_version}")
|
||||
_prepare_wheel(launcher_project_path)
|
||||
tar_name = _copy_tar(launcher_project_path, root)
|
||||
tar_files.append(os.path.join(root, tar_name))
|
||||
|
@ -135,7 +142,8 @@ def _prepare_lightning_wheels_and_requirements(root: Path) -> Optional[Callable]
|
|||
if lightning_cloud_project_path:
|
||||
from lightning_cloud.__version__ import __version__ as cloud_version
|
||||
|
||||
logger.info(f"Packaged Lightning Cloud with your application. Version: {cloud_version}")
|
||||
# todo: check why logging.info is missing in outputs
|
||||
print(f"Packaged Lightning Cloud with your application. Version: {cloud_version}")
|
||||
_prepare_wheel(lightning_cloud_project_path)
|
||||
tar_name = _copy_tar(lightning_cloud_project_path, root)
|
||||
tar_files.append(os.path.join(root, tar_name))
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from lightning_utilities.core.imports import module_available
|
||||
|
||||
from lightning_app.testing.helpers import RunIf
|
||||
from lightning_app.utilities.packaging import lightning_utils
|
||||
|
@ -9,17 +11,27 @@ from lightning_app.utilities.packaging.lightning_utils import (
|
|||
_verify_lightning_version,
|
||||
)
|
||||
|
||||
# TODO: this is very sensitive test, need to be done
|
||||
# def test_prepare_lightning_wheels_and_requirement(tmpdir):
|
||||
# """This test ensures the lightning source gets packaged inside the lightning repo."""
|
||||
#
|
||||
# cleanup_handle = _prepare_lightning_wheels_and_requirements(tmpdir)
|
||||
# from lightning_app.__version__ import version
|
||||
#
|
||||
# tar_name = f"lightning-app-{version}.tar.gz"
|
||||
# assert sorted(os.listdir(tmpdir))[0] == tar_name
|
||||
# cleanup_handle()
|
||||
# assert os.listdir(tmpdir) == []
|
||||
|
||||
# TODO: Resolve this sensitive test.
|
||||
@pytest.mark.skipif(True, reason="Currently broken")
|
||||
def test_prepare_lightning_wheels_and_requirement(tmpdir):
|
||||
"""This test ensures the lightning source gets packaged inside the lightning repo."""
|
||||
|
||||
package_name = "lightning" if module_available("lightning") else "lightning-app"
|
||||
|
||||
if package_name == "lightning":
|
||||
from lightning.__version__ import version
|
||||
|
||||
tar_name = f"lightning-{version}.tar.gz"
|
||||
else:
|
||||
from lightning_app.__version__ import version
|
||||
|
||||
tar_name = f"lightning-app-{version}.tar.gz"
|
||||
|
||||
cleanup_handle = _prepare_lightning_wheels_and_requirements(tmpdir, package_name=package_name)
|
||||
assert sorted(os.listdir(tmpdir))[0] == tar_name
|
||||
cleanup_handle()
|
||||
assert os.listdir(tmpdir) == []
|
||||
|
||||
|
||||
def _mocked_get_dist_path_if_editable_install(*args, **kwargs):
|
||||
|
|
|
@ -17,7 +17,7 @@ def test_boring_app_example_cloud() -> None:
|
|||
) as (
|
||||
_,
|
||||
view_page,
|
||||
fetch_logs,
|
||||
_,
|
||||
name,
|
||||
):
|
||||
|
||||
|
@ -34,5 +34,5 @@ def test_boring_app_example_cloud() -> None:
|
|||
|
||||
assert result.exit_code == 0
|
||||
assert result.exception is None
|
||||
assert any("http://0.0.0.0:8080" in line for line in lines)
|
||||
assert any("http://0.0.0.0:1111" in line for line in lines)
|
||||
print("Succeeded App!")
|
||||
|
|
|
@ -21,16 +21,14 @@ def test_commands_and_api_example_cloud() -> None:
|
|||
# 1: Collect the app_id
|
||||
app_id = admin_page.url.split("/")[-1]
|
||||
|
||||
# 2: Connect to the App
|
||||
Popen(f"python -m lightning connect {app_id} -y", shell=True).wait()
|
||||
|
||||
# 3: Send the first command with the client
|
||||
cmd = "python -m lightning command with client --name=this"
|
||||
Popen(cmd, shell=True).wait()
|
||||
|
||||
# 4: Send the second command without a client
|
||||
cmd = "python -m lightning command without client --name=is"
|
||||
Popen(cmd, shell=True).wait()
|
||||
# 2: Connect to the App and send the first & second command with the client
|
||||
# Requires to be run within the same process.
|
||||
cmd_1 = f"python -m lightning connect {app_id} -y"
|
||||
cmd_2 = "python -m lightning command with client --name=this"
|
||||
cmd_3 = "python -m lightning command without client --name=is"
|
||||
cmd_4 = "lightning disconnect"
|
||||
process = Popen(" && ".join([cmd_1, cmd_2, cmd_3, cmd_4]), shell=True)
|
||||
process.wait()
|
||||
|
||||
# This prevents some flakyness in the CI. Couldn't reproduce it locally.
|
||||
sleep(5)
|
||||
|
@ -47,6 +45,3 @@ def test_commands_and_api_example_cloud() -> None:
|
|||
if "['this', 'is', 'awesome']" in log:
|
||||
has_logs = True
|
||||
sleep(1)
|
||||
|
||||
# 7: Disconnect from the App
|
||||
Popen("lightning disconnect", shell=True).wait()
|
||||
|
|
|
@ -18,7 +18,7 @@ def test_drive_example_cloud() -> None:
|
|||
|
||||
has_logs = False
|
||||
while not has_logs:
|
||||
for log in fetch_logs():
|
||||
for log in fetch_logs(["flow"]):
|
||||
if "Application End!" in log:
|
||||
has_logs = True
|
||||
sleep(1)
|
||||
|
|
Loading…
Reference in New Issue