Skip to content

Commit b822ac1

Browse files
manskxmansypre-commit-ci[bot]akihironittaBorda
authored
Add CI app cloud e2e & fix setup UI download (#13499)
* Add CI app e2e * update UserID * fix apps cleanup * download frontend inside setup.py Co-authored-by: mansy <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Akihiro Nitta <[email protected]> Co-authored-by: Jirka Borovec <[email protected]>
1 parent e196e19 commit b822ac1

File tree

12 files changed

+340
-8
lines changed

12 files changed

+340
-8
lines changed

.actions/setup_tools.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
import glob
1515
import logging
1616
import os
17+
import pathlib
1718
import re
19+
import shutil
20+
import tarfile
21+
import tempfile
22+
import urllib.request
1823
from importlib.util import module_from_spec, spec_from_file_location
1924
from itertools import groupby
2025
from types import ModuleType
@@ -23,6 +28,9 @@
2328
_PROJECT_ROOT = os.path.dirname(os.path.dirname(__file__))
2429
_PACKAGE_MAPPING = {"pytorch": "pytorch_lightning", "app": "lightning_app"}
2530

31+
# TODO: remove this once lightning-ui package is ready as a dependency
32+
_LIGHTNING_FRONTEND_RELEASE_URL = "https://storage.googleapis.com/grid-packages/lightning-ui/v0.0.0/build.tar.gz"
33+
2634

2735
def _load_py_module(name: str, location: str) -> ModuleType:
2836
spec = spec_from_file_location(name, location)
@@ -317,3 +325,26 @@ class implementations by cross-imports to the true package.
317325
os.makedirs(os.path.dirname(new_file), exist_ok=True)
318326
with open(new_file, "w", encoding="utf-8") as fp:
319327
fp.writelines(lines)
328+
329+
330+
def _download_frontend(root: str = _PROJECT_ROOT):
331+
"""Downloads an archive file for a specific release of the Lightning frontend and extracts it to the correct
332+
directory."""
333+
334+
try:
335+
build_dir = "build"
336+
frontend_dir = pathlib.Path(root, "src", "lightning_app", "ui")
337+
download_dir = tempfile.mkdtemp()
338+
339+
shutil.rmtree(frontend_dir, ignore_errors=True)
340+
response = urllib.request.urlopen(_LIGHTNING_FRONTEND_RELEASE_URL)
341+
342+
file = tarfile.open(fileobj=response, mode="r|gz")
343+
file.extractall(path=download_dir)
344+
345+
shutil.move(os.path.join(download_dir, build_dir), frontend_dir)
346+
print("The Lightning UI has successfully been downloaded!")
347+
348+
# If installing from source without internet connection, we don't want to break the installation
349+
except Exception:
350+
print("The Lightning UI downloading has failed!")

.github/file-filters.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# This file contains filters to be used in the CI to detect file changes and run the required CI jobs.
2+
3+
app_examples:
4+
- "src/lightning_app/**"
5+
- "tests/tests_app_examples/**"
6+
- "requirements/app/**"
7+
- "examples/app_*"
8+
- "setup.py"
9+
- "src/pytorch_lightning/__version__.py"
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
name: cloud-testing
2+
3+
# Used to run the e2e tests on lightning.ai
4+
5+
on: # Trigger the workflow on push or pull request, but only for the master branch
6+
push:
7+
branches:
8+
- "master"
9+
pull_request:
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.head_ref }}
13+
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
14+
15+
jobs:
16+
# This is job should once only once per PR to detect file changes so run required jobs.
17+
# see .github/file-filters.yml to define file filters and run the jobs based on the output of each filter.
18+
# More info: https://github.com/marketplace/actions/paths-changes-filter
19+
20+
changes:
21+
runs-on: ubuntu-latest
22+
# Set job outputs to the values from filter step
23+
outputs:
24+
app_examples: ${{ steps.filter.outputs.app_examples }}
25+
steps:
26+
- uses: actions/checkout@v2
27+
- name: Set up Python 3.8
28+
uses: actions/setup-python@v2
29+
with:
30+
python-version: "3.8"
31+
32+
- uses: dorny/paths-filter@v2
33+
id: filter
34+
with:
35+
filters: .github/file-filters.yml
36+
37+
cloud-test:
38+
name: Cloud Test
39+
needs: changes
40+
if: ${{ needs.changes.outputs.app_examples == 'true' }}
41+
runs-on: ubuntu-20.04
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
app_name:
46+
- v0_app
47+
- boring_app
48+
# - quick_start # TODO: fix this
49+
- template_streamlit_ui
50+
- template_react_ui
51+
- template_jupyterlab
52+
- idle_timeout
53+
- collect_failures
54+
- custom_work_dependencies
55+
- drive
56+
- payload
57+
timeout-minutes: 35
58+
steps:
59+
- uses: actions/checkout@v2
60+
- name: Set up Python 3.8
61+
uses: actions/setup-python@v2
62+
with:
63+
python-version: "3.8"
64+
65+
- name: Get PR ID
66+
id: PR
67+
run: |
68+
if [ -z ${{github.event.number}} ]; then
69+
echo "::set-output name=ID::$(date +%s)"
70+
else
71+
echo "::set-output name=ID::${{github.event.number}}"
72+
fi
73+
74+
# TODO: Enable cache
75+
# - name: Cache virtualenv
76+
# id: cache-venv
77+
# uses: actions/cache@v2
78+
# with:
79+
# path: ./.venv/
80+
# key: ${{ runner.os }}-pip-${{ matrix.app_name }}-${{ hashFiles('requirements/app/base.txt', 'requirements/app/*.txt', 'src/lightning_app/__version__.py') }}
81+
# restore-keys: ${{ runner.os }}-venv-${{ matrix.app_name }}-
82+
83+
- name: Install dependencies
84+
shell: bash
85+
run: |
86+
pip --version
87+
python -m pip install -r requirements/app/devel.txt --no-cache --quiet --find-links https://download.pytorch.org/whl/cpu/torch_stable.html
88+
# if: steps.cache-venv.outputs.cache-hit != 'true' # TODO: Enable cache
89+
90+
- name: Cache Playwright dependencies
91+
id: playwright-cache
92+
uses: actions/cache@v2
93+
with:
94+
path: ~/.cache/ms-playwright
95+
key: ${{ runner.os }}-playwright-${{ matrix.app_name }}-${{ hashFiles('requirements/app/base.txt', 'requirements/app/*.txt', 'src/lightning_app/__version__.py') }}
96+
restore-keys: ${{ runner.os }}-playwright-${{ matrix.app_name }}-
97+
98+
- name: Install Playwright system dependencies
99+
shell: bash
100+
run: |
101+
python -m pip install playwright
102+
python -m playwright install --with-deps
103+
# if: steps.playwright-cache.outputs.cache-hit != 'true' # TODO: Enable cache
104+
105+
- name: Install lightning
106+
run: |
107+
pip install -e .
108+
shell: bash
109+
110+
- name: Lightning Install quick-start
111+
if: ${{ (matrix.app_name == 'quick_start') }}
112+
shell: bash
113+
run: |
114+
python -m lightning install app lightning/quick-start -y
115+
116+
- name: Clone Template React UI Repo
117+
uses: actions/checkout@v3
118+
with:
119+
repository: Lightning-AI/lightning-template-react
120+
token: ${{ secrets.PAT_GHOST }}
121+
ref: 'master'
122+
path: examples/app_template_react_ui
123+
124+
- name: Clone Template Jupyter Lab Repo
125+
uses: actions/checkout@v3
126+
with:
127+
repository: Lightning-AI/lightning-template-jupyterlab
128+
token: ${{ secrets.PAT_GHOST }}
129+
ref: 'master'
130+
path: examples/app_template_jupyterlab
131+
132+
- name: Copy Template Jupyter Lab Repo tests
133+
shell: bash
134+
run: cp examples/app_template_jupyterlab/tests/test_template_jupyterlab.py tests/tests_app_examples/test_template_jupyterlab.py
135+
136+
- name: List pip dependency
137+
shell: bash
138+
run: |
139+
pip list
140+
141+
- name: Run the tests
142+
env:
143+
LAI_USER: ${{ secrets.LAI_USER }}
144+
LAI_PASS: ${{ secrets.LAI_PASS }}
145+
LIGHTNING_USER_ID: ${{ secrets.LIGHTNING_USER_ID }}
146+
LIGHTNING_API_KEY: ${{ secrets.LIGHTNING_API_KEY }}
147+
LIGHTNING_USERNAME: ${{ secrets.LIGHTNING_USERNAME }}
148+
LIGHTNING_CLOUD_URL: ${{ secrets.LIGHTNING_CLOUD_URL }}
149+
CLOUD: "1"
150+
VIDEO_LOCATION: ./artifacts/videos
151+
PR_NUMBER: ${{ steps.PR.outputs.ID }}
152+
TEST_APP_NAME: ${{ matrix.app_name }}
153+
HAR_LOCATION: ./artifacts/hars
154+
SLOW_MO: 50
155+
shell: bash
156+
run: |
157+
mkdir -p ${VIDEO_LOCATION}
158+
HEADLESS=1 python -m pytest tests/tests_app_examples/test_${{ matrix.app_name }}.py::test_${{ matrix.app_name }}_example_cloud --timeout=900 --capture=no -v --color=yes
159+
# Delete the artifacts if successful
160+
rm -r ${VIDEO_LOCATION}/${{ matrix.app_name }}
161+
162+
- uses: actions/upload-artifact@v2
163+
164+
if: ${{ always() }}
165+
with:
166+
name: test-artifacts
167+
path: ./artifacts/videos
168+
169+
- name: Clean Previous Apps
170+
if: ${{ always() }}
171+
env:
172+
LAI_USER: ${{ secrets.LAI_USER }}
173+
LAI_PASS: ${{ secrets.LAI_PASS }}
174+
LIGHTNING_USER_ID: ${{ secrets.LIGHTNING_USER_ID }}
175+
LIGHTNING_API_KEY: ${{ secrets.LIGHTNING_API_KEY }}
176+
LIGHTNING_USERNAME: ${{ secrets.LIGHTNING_USERNAME }}
177+
LIGHTNING_CLOUD_URL: ${{ secrets.LIGHTNING_CLOUD_URL }}
178+
PR_NUMBER: ${{ steps.PR.outputs.ID }}
179+
TEST_APP_NAME: ${{ matrix.app_name }}
180+
GRID_USER_ID: ${{ secrets.LIGHTNING_USER_ID }}
181+
GRID_USER_KEY: ${{ secrets.LIGHTNING_API_KEY }}
182+
GRID_URL: ${{ secrets.LIGHTNING_CLOUD_URL }}
183+
_GRID_USERNAME: ${{ secrets.LIGHTNING_USERNAME }}
184+
shell: bash
185+
run: |
186+
time python -c "from lightning.app import testing; testing.delete_cloud_lightning_apps()"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import logging
2+
3+
from lightning_app import LightningApp, LightningFlow
4+
from lightning_app.frontend import StreamlitFrontend
5+
from lightning_app.utilities.state import AppState
6+
7+
logger = logging.getLogger(__name__)
8+
9+
10+
class StreamlitUI(LightningFlow):
11+
def __init__(self):
12+
super().__init__()
13+
self.message_to_print = "Hello World!"
14+
self.should_print = False
15+
16+
def configure_layout(self):
17+
return StreamlitFrontend(render_fn=render_fn)
18+
19+
20+
def render_fn(state: AppState):
21+
import streamlit as st
22+
23+
should_print = st.button("Should print to the terminal ?")
24+
25+
if should_print:
26+
state.should_print = not state.should_print
27+
28+
st.write("Currently printing." if state.should_print else "Currently waiting to print.")
29+
30+
31+
class HelloWorld(LightningFlow):
32+
def __init__(self):
33+
super().__init__()
34+
self.counter = 0
35+
self.streamlit_ui = StreamlitUI()
36+
37+
def run(self):
38+
self.streamlit_ui.run()
39+
if self.streamlit_ui.should_print:
40+
logger.info(f"{self.counter}: {self.streamlit_ui.message_to_print}")
41+
self.counter += 1
42+
self.streamlit_ui.should_print = False
43+
44+
def configure_layout(self):
45+
return [{"name": "StreamLitUI", "content": self.streamlit_ui}]
46+
47+
48+
app = LightningApp(HelloWorld())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
streamlit

src/lightning/__setup__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def _adjust_manifest(**kwargs: Any) -> None:
4040
"recursive-include src *.md" + os.linesep,
4141
"recursive-include requirements *.txt" + os.linesep,
4242
]
43+
44+
# TODO: remove this once lightning-ui package is ready as a dependency
45+
lines += ["recursive-include src/lightning_app/ui *" + os.linesep]
4346
with open(manifest_path, "w") as fp:
4447
fp.writelines(lines)
4548

@@ -64,7 +67,11 @@ def _setup_args(**kwargs: Any) -> Dict[str, Any]:
6467
if os.path.isdir(d)
6568
]
6669
_requires = list(chain(*_requires))
67-
# todo: consider invaliding some additional arguments from packages, for example if include data or safe to zip
70+
# TODO: consider invaliding some additional arguments from packages, for example if include data or safe to zip
71+
72+
# TODO: remove this once lightning-ui package is ready as a dependency
73+
_setup_tools._download_frontend(_PROJECT_ROOT)
74+
6875
return dict(
6976
name="lightning",
7077
version=_version.version, # todo: consider adding branch for installation from source

src/lightning_app/__setup__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ def _adjust_manifest(**__: Any) -> None:
5151
"recursive-include src/lightning_app *.md" + os.linesep,
5252
"recursive-include requirements/app *.txt" + os.linesep,
5353
]
54+
55+
# TODO: remove this once lightning-ui package is ready as a dependency
56+
lines += ["recursive-include src/lightning_app/ui *" + os.linesep]
57+
5458
with open(manifest_path, "w") as fp:
5559
fp.writelines(lines)
5660

@@ -63,7 +67,10 @@ def _setup_args(**__: Any) -> Dict[str, Any]:
6367
_long_description = _setup_tools.load_readme_description(
6468
_PACKAGE_ROOT, homepage=_about.__homepage__, version=_version.version
6569
)
66-
# TODO: at this point we need to download the UI to the package
70+
71+
# TODO: remove this once lightning-ui package is ready as a dependency
72+
_setup_tools._download_frontend(_PROJECT_ROOT)
73+
6774
return dict(
6875
name="lightning-app",
6976
version=_version.version, # todo: consider using date version + branch for installation from source
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
from lightning_app.testing.testing import application_testing, LightningTestApp, run_work_isolated
1+
from lightning_app.testing.testing import (
2+
application_testing,
3+
delete_cloud_lightning_apps,
4+
LightningTestApp,
5+
run_work_isolated,
6+
)
27

3-
__all__ = ["application_testing", "run_work_isolated", "LightningTestApp"]
8+
__all__ = ["application_testing", "run_work_isolated", "LightningTestApp", "delete_cloud_lightning_apps"]

0 commit comments

Comments
 (0)