From 96cc288f53b847f0020d53c3079821a03921d1fb Mon Sep 17 00:00:00 2001 From: thomas chaton Date: Tue, 13 Sep 2022 16:50:36 +0200 Subject: [PATCH] (hot fix) Resolve Boring App (#14684) * resolve_boring_app * update * update * update * update * update * update * update * update * update * update * update * update * update --- src/lightning_app/CHANGELOG.md | 2 + src/lightning_app/cli/lightning_cli.py | 3 +- src/lightning_app/testing/testing.py | 62 +++++---------------- src/lightning_app/utilities/app_logs.py | 19 ++++--- tests/tests_app_examples/conftest.py | 44 --------------- tests/tests_app_examples/test_boring_app.py | 11 ++-- 6 files changed, 34 insertions(+), 107 deletions(-) diff --git a/src/lightning_app/CHANGELOG.md b/src/lightning_app/CHANGELOG.md index a600c454cb..7927372278 100644 --- a/src/lightning_app/CHANGELOG.md +++ b/src/lightning_app/CHANGELOG.md @@ -25,6 +25,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Resolved a bug where the state change detection using DeepDiff won't worked with Path, Drive objects ([#14465](https://github.com/Lightning-AI/lightning/pull/14465)) +- Resolved a bug where the wrong client was passed to collect cloud logs ([#14684](https://github.com/Lightning-AI/lightning/pull/14684)) + ### Removed - diff --git a/src/lightning_app/cli/lightning_cli.py b/src/lightning_app/cli/lightning_cli.py index c1e4be8cc8..89f342d0e2 100644 --- a/src/lightning_app/cli/lightning_cli.py +++ b/src/lightning_app/cli/lightning_cli.py @@ -32,6 +32,7 @@ from lightning_app.utilities.cli_helpers import _arrow_time_callback, _format_in from lightning_app.utilities.cloud import _get_project from lightning_app.utilities.cluster_logs import _cluster_logs_reader from lightning_app.utilities.login import Auth +from lightning_app.utilities.logs_socket_api import _LightningLogsSocketAPI from lightning_app.utilities.network import LightningClient logger = Logger(__name__) @@ -151,7 +152,7 @@ def logs(app_name: str, components: List[str], follow: bool) -> None: raise click.ClickException(f"Component '{component}' does not exist in app {app_name}.") log_reader = _app_logs_reader( - client=client, + logs_api_client=_LightningLogsSocketAPI(client.api_client), project_id=project.project_id, app_id=apps[app_name].id, component_names=components, diff --git a/src/lightning_app/testing/testing.py b/src/lightning_app/testing/testing.py index 34ea7aa6b6..14bca57fe1 100644 --- a/src/lightning_app/testing/testing.py +++ b/src/lightning_app/testing/testing.py @@ -6,7 +6,6 @@ import subprocess import sys import tempfile import time -import traceback from contextlib import contextmanager from multiprocessing import Process from subprocess import Popen @@ -38,7 +37,6 @@ if _is_playwright_available(): def _on_error_callback(ws_app, *_): - print(traceback.format_exc()) ws_app.close() @@ -210,7 +208,9 @@ def run_cli(args) -> Generator: @requires("playwright") @contextmanager -def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str] = []) -> Generator: +def run_app_in_cloud( + app_folder: str, app_name: str = "app.py", extra_args: List[str] = [], debug: bool = True +) -> Generator: """This utility is used to automate testing e2e application with lightning_app.ai.""" # 1. Validate the provide app_folder is correct. if not os.path.exists(os.path.join(app_folder, "app.py")): @@ -239,7 +239,8 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str with tempfile.TemporaryDirectory() as tmpdir: env_copy = os.environ.copy() env_copy["PACKAGE_LIGHTNING"] = "1" - env_copy["LIGHTNING_DEBUG"] = "1" + if debug: + env_copy["LIGHTNING_DEBUG"] = "1" shutil.copytree(app_folder, tmpdir, dirs_exist_ok=True) # TODO - add -no-cache to the command line. process = Popen( @@ -308,7 +309,7 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str """, [LIGHTNING_CLOUD_PROJECT_ID], ) - admin_page.goto(f"{Config.url}/{Config.username}/apps") + admin_page.goto(f"{Config.url}/{Config.username}/apps", timeout=60 * 1000) # Closing the Create Project dialog. try: @@ -324,15 +325,6 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str print("'Create Project' dialog not visible, skipping.") admin_page.locator(f"text={name}").click() - admin_page.evaluate( - """data => { - window.localStorage.setItem('gridUserId', data[0]); - window.localStorage.setItem('gridUserKey', data[1]); - window.localStorage.setItem('gridUserToken', data[2]); - } - """, - [Config.id, Config.key, token], - ) sleep(5) # Scroll to the bottom of the page. Used to capture all logs. admin_page.evaluate( @@ -361,8 +353,9 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str assert len(lightning_apps) == 1 app_id = lightning_apps[0].id - process = Process(target=print_logs, kwargs={"app_id": app_id}) - process.start() + if debug: + process = Process(target=print_logs, kwargs={"app_id": app_id}) + process.start() while True: try: @@ -403,40 +396,11 @@ def run_app_in_cloud(app_folder: str, app_name: str = "app.py", extra_args: [str except KeyboardInterrupt: pass finally: - has_finished = False - while not has_finished: - try: - button = admin_page.locator('[data-cy="stop"]') - try: - button.wait_for(timeout=3 * 1000) - button.click() - except (playwright._impl._api_types.Error, playwright._impl._api_types.TimeoutError): - pass - context.close() - browser.close() + if debug: + process.kill() - list_lightningapps = client.lightningapp_instance_service_list_lightningapp_instances( - project.project_id - ) - - for lightningapp in list_lightningapps.lightningapps: - if lightningapp.name != name: - continue - try: - res = client.lightningapp_instance_service_delete_lightningapp_instance( - project_id=project.project_id, - id=lightningapp.id, - ) - assert res == {} - except ApiException as e: - print(f"Failed to delete {lightningapp.name}. Exception {e}") - - process.kill() - has_finished = True - except Exception: - pass - - Popen("lightning disconnect", shell=True).wait() + context.close() + browser.close() def wait_for(page, callback: Callable, *args, **kwargs) -> Any: diff --git a/src/lightning_app/utilities/app_logs.py b/src/lightning_app/utilities/app_logs.py index 0fbe359972..0a63949fbd 100644 --- a/src/lightning_app/utilities/app_logs.py +++ b/src/lightning_app/utilities/app_logs.py @@ -1,7 +1,6 @@ import json import queue from dataclasses import dataclass -from datetime import timedelta from threading import Thread from typing import Callable, Iterator, List, Optional @@ -87,18 +86,22 @@ def _app_logs_reader( th.start() # Print logs from queue when log event is available - user_log_start = "<<< BEGIN USER_RUN_FLOW SECTION >>>" - start_timestamp = None + flow = "Your app has started. View it in your browser" + work = "USER_RUN_WORK" + start_timestamps = {} # Print logs from queue when log event is available try: while True: - log_event = read_queue.get(timeout=None if follow else 1.0) - if user_log_start in log_event.message: - start_timestamp = log_event.timestamp + timedelta(seconds=0.5) + 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 - if start_timestamp and log_event.timestamp > start_timestamp: - yield log_event + timestamp = start_timestamps.get(log_event.component_name, None) + if timestamp and log_event.timestamp >= timestamp: + if "launcher" not in log_event.message: + yield log_event except queue.Empty: # Empty is raised by queue.get if timeout is reached. Follow = False case. diff --git a/tests/tests_app_examples/conftest.py b/tests/tests_app_examples/conftest.py index 40d2db2e02..e69de29bb2 100644 --- a/tests/tests_app_examples/conftest.py +++ b/tests/tests_app_examples/conftest.py @@ -1,44 +0,0 @@ -import os - -from lightning_cloud.openapi.rest import ApiException - -from lightning_app.utilities.cloud import _get_project -from lightning_app.utilities.network import LightningClient - - -def pytest_timeout_cancel_timer(item): - """This hook deletes the Lightning App when timeout triggers.""" - - if item.name.endswith("_example_cloud"): - name = os.getenv("LIGHTNING_APP_NAME") - print(f"Timeout was triggered. Deleting the App {name}.") - - client = LightningClient() - project = _get_project(client) - - lightning_apps = [ - app - for app in client.lightningapp_instance_service_list_lightningapp_instances( - project.project_id - ).lightningapps - if app.name == name - ] - - if not lightning_apps: - return True - - assert len(lightning_apps) == 1 - - lightning_app = lightning_apps[0] - - try: - res = client.lightningapp_instance_service_delete_lightningapp_instance( - project_id=project.project_id, - id=lightning_app.id, - ) - assert res == {} - - except ApiException as e: - print(f"Failed to delete {name}. Exception {e}") - - return True diff --git a/tests/tests_app_examples/test_boring_app.py b/tests/tests_app_examples/test_boring_app.py index afb958571d..aa4c568b4f 100644 --- a/tests/tests_app_examples/test_boring_app.py +++ b/tests/tests_app_examples/test_boring_app.py @@ -10,7 +10,11 @@ from lightning_app.testing.testing import run_app_in_cloud, wait_for @pytest.mark.cloud def test_boring_app_example_cloud() -> None: - with run_app_in_cloud(os.path.join(_PROJECT_ROOT, "examples/app_boring/"), app_name="app_dynamic.py") as ( + with run_app_in_cloud( + os.path.join(_PROJECT_ROOT, "examples/app_boring/"), + app_name="app_dynamic.py", + debug=False, + ) as ( _, view_page, fetch_logs, @@ -19,15 +23,11 @@ def test_boring_app_example_cloud() -> None: def check_hello_there(*_, **__): locator = view_page.frame_locator("iframe").locator('ul:has-text("Hello there!")') - locator.wait_for(timeout=3 * 1000) if len(locator.all_text_contents()): return True wait_for(view_page, check_hello_there) - for _ in fetch_logs(): - pass - runner = CliRunner() result = runner.invoke(logs, [name]) lines = result.output.splitlines() @@ -35,3 +35,4 @@ 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) + print("Succeeded App!")