[e2e] The smoke tests no longer rely on the multiprocessing package

Running the scripts with this package leads to the following cryptic error in our CI, when run in macOS:
```
RuntimeError:
        An attempt has been made to start a new process before the
        current process has finished its bootstrapping phase.

        This probably means that you are not using fork to start your
        child processes and you have forgotten to use the proper idiom
        in the main module:

            if __name__ == '__main__':
                freeze_support()
                ...

        The "freeze_support()" line can be omitted if the program
        is not going to be frozen to produce an executable.
```
(and Python's docs clearly state that this `freeze_support` function has no effect when invoked on any operating system other than Windows ¯\_(ツ)_/¯ )
This commit is contained in:
Olivier Philippon
2022-05-03 13:51:33 +01:00
parent 85db9263a8
commit 078b7c151b
4 changed files with 419 additions and 19 deletions

View File

@@ -1,32 +1,55 @@
from __future__ import annotations
import shlex
import sys
import multiprocessing
import subprocess
import threading
from pathlib import Path
project_root = Path(__file__).parent.parent
for python_path in [str(project_root), str(project_root / "src")]:
sys.path.append(python_path)
target_script_name = "basic"
script_time_to_live = 2.0 # in seconds
if len(sys.argv) > 1:
target_script_name = sys.argv[1]
if len(sys.argv) > 2:
script_time_to_live = float(sys.argv[2])
e2e_root = Path(__file__).parent
completed_process = None
def launch_sandbox_script():
from sandbox.basic import app as basic_app
def launch_sandbox_script(python_file_name: str) -> None:
global completed_process
basic_app.run()
command = f"{sys.executable} ./test_apps/{shlex.quote(python_file_name)}.py"
print(f"Launching command '{command}'...")
try:
completed_process = subprocess.run(
command, shell=True, check=True, capture_output=True, cwd=str(e2e_root)
)
except subprocess.CalledProcessError as err:
print(f"Process error: {err.stderr}")
raise
# The following line seems to be required in order to have this work in the MacOS part
# of our CI - despite the fact that Python docs say that this function
# "has no effect when invoked on any operating system other than Windows" 🤔
multiprocessing.freeze_support()
thread = threading.Thread(
target=launch_sandbox_script, args=(target_script_name,), daemon=True
)
thread.start()
process = multiprocessing.Process(target=launch_sandbox_script, daemon=True)
process.start()
print(
f"Launching Python script in a sub-thread; we'll wait for it for {script_time_to_live} seconds..."
)
thread.join(timeout=script_time_to_live)
print("The wait is over.")
process.join(timeout=2)
process_still_running = process.exitcode is None
process_still_running = completed_process is None
process_was_able_to_run_without_errors = process_still_running
if process_still_running:
process.kill()
if process_was_able_to_run_without_errors:
print("Python script is still running :-)")
else:
print("Python script is no longer running :-/")
sys.exit(0 if process_was_able_to_run_without_errors else 1)

View File

@@ -0,0 +1,227 @@
/* CSS file for basic.py */
* {
transition: color 300ms linear, background 300ms linear;
}
* {
scrollbar-background: $panel-darken-2;
scrollbar-background-hover: $panel-darken-3;
scrollbar-color: $system;
scrollbar-color-active: $accent-darken-1;
}
App > Screen {
layout: dock;
docks: side=left/1;
background: $surface;
color: $text-surface;
}
#sidebar {
color: $text-primary;
background: $primary;
dock: side;
width: 30;
offset-x: -100%;
layout: dock;
transition: offset 500ms in_out_cubic;
}
#sidebar.-active {
offset-x: 0;
}
#sidebar .title {
height: 3;
background: $primary-darken-2;
color: $text-primary-darken-2 ;
border-right: outer $primary-darken-3;
content-align: center middle;
}
#sidebar .user {
height: 8;
background: $primary-darken-1;
color: $text-primary-darken-1;
border-right: outer $primary-darken-3;
content-align: center middle;
}
#sidebar .content {
background: $primary;
color: $text-primary;
border-right: outer $primary-darken-3;
content-align: center middle;
}
#header {
color: $text-primary-darken-1;
background: $primary-darken-1;
height: 3;
content-align: center middle;
}
#content {
color: $text-background;
background: $background;
layout: vertical;
overflow-y: scroll;
}
Tweet {
height: 12;
width: 80;
margin: 1 3;
background: $panel;
color: $text-panel;
layout: vertical;
/* border: outer $primary; */
padding: 1;
border: wide $panel-darken-2;
overflow-y: scroll;
align-horizontal: center;
}
.scrollable {
width: 80;
overflow-y: scroll;
max-width:80;
height: 20;
align-horizontal: center;
layout: vertical;
}
.code {
height: 34;
width: 100%;
}
TweetHeader {
height:1;
background: $accent;
color: $text-accent
}
TweetBody {
width: 100%;
background: $panel;
color: $text-panel;
height:20;
padding: 0 1 0 0;
}
.button {
background: $accent;
color: $text-accent;
width:20;
height: 3;
/* border-top: hidden $accent-darken-3; */
border: tall $accent-darken-2;
/* border-left: tall $accent-darken-1; */
/* padding: 1 0 0 0 ; */
transition: background 200ms in_out_cubic, color 300ms in_out_cubic;
}
.button:hover {
background: $accent-lighten-1;
color: $text-accent-lighten-1;
width: 20;
height: 3;
border: tall $accent-darken-1;
/* border-left: tall $accent-darken-3; */
}
#footer {
color: $text-accent;
background: $accent;
height: 1;
border-top: hkey $accent-darken-2;
content-align: center middle;
}
#sidebar .content {
layout: vertical
}
OptionItem {
height: 3;
background: $primary;
transition: background 100ms linear;
border-right: outer $primary-darken-2;
border-left: hidden;
content-align: center middle;
}
OptionItem:hover {
height: 3;
color: $accent;
background: $primary-darken-1;
/* border-top: hkey $accent2-darken-3;
border-bottom: hkey $accent2-darken-3; */
text-style: bold;
border-left: outer $accent-darken-2;
}
Error {
width: 80;
height:3;
background: $error;
color: $text-error;
border-top: hkey $error-darken-2;
border-bottom: hkey $error-darken-2;
margin: 1 3;
text-style: bold;
align-horizontal: center;
}
Warning {
width: 80;
height:3;
background: $warning;
color: $text-warning-fade-1;
border-top: hkey $warning-darken-2;
border-bottom: hkey $warning-darken-2;
margin: 1 2;
text-style: bold;
align-horizontal: center;
}
Success {
width: 80;
height:3;
box-sizing: border-box;
background: $success-lighten-3;
color: $text-success-lighten-3-fade-1;
border-top: hkey $success;
border-bottom: hkey $success;
margin: 1 2;
text-style: bold;
align-horizontal: center;
}
.horizontal {
layout: horizontal
}

View File

@@ -0,0 +1,150 @@
from pathlib import Path
from rich.align import Align
from rich.console import RenderableType
from rich.syntax import Syntax
from rich.text import Text
from textual.app import App
from textual.widget import Widget
from textual.widgets import Static
CODE = '''
class Offset(NamedTuple):
"""A point defined by x and y coordinates."""
x: int = 0
y: int = 0
@property
def is_origin(self) -> bool:
"""Check if the point is at the origin (0, 0)"""
return self == (0, 0)
def __bool__(self) -> bool:
return self != (0, 0)
def __add__(self, other: object) -> Offset:
if isinstance(other, tuple):
_x, _y = self
x, y = other
return Offset(_x + x, _y + y)
return NotImplemented
def __sub__(self, other: object) -> Offset:
if isinstance(other, tuple):
_x, _y = self
x, y = other
return Offset(_x - x, _y - y)
return NotImplemented
def __mul__(self, other: object) -> Offset:
if isinstance(other, (float, int)):
x, y = self
return Offset(int(x * other), int(y * other))
return NotImplemented
'''
lorem = Text.from_markup(
"""Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. """
"""Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. In velit libero, volutpat nec hendrerit at, faucibus in odio. Aliquam hendrerit nibh sed quam volutpat maximus. Nullam suscipit convallis lorem quis sodales. In tristique lobortis ante et dictum. Ut at finibus ipsum. In urna dolor, placerat et mi facilisis, congue sollicitudin massa. Phasellus felis turpis, cursus eu lectus et, porttitor malesuada augue. Sed feugiat volutpat velit, sollicitudin fringilla velit bibendum faucibus. """
)
class TweetHeader(Widget):
def render(self) -> RenderableType:
return Text("Lorem Impsum", justify="center")
class TweetBody(Widget):
def render(self) -> Text:
return lorem
class Tweet(Widget):
pass
class OptionItem(Widget):
def render(self) -> Text:
return Text("Option")
class Error(Widget):
def render(self) -> Text:
return Text("This is an error message", justify="center")
class Warning(Widget):
def render(self) -> Text:
return Text("This is a warning message", justify="center")
class Success(Widget):
def render(self) -> Text:
return Text("This is a success message", justify="center")
class BasicApp(App):
"""A basic app demonstrating CSS"""
def on_load(self):
"""Bind keys here."""
self.bind("tab", "toggle_class('#sidebar', '-active')")
def on_mount(self):
"""Build layout here."""
self.mount(
header=Static(
Text.from_markup(
"[b]This is a [u]Textual[/u] app, running in the terminal"
),
),
content=Widget(
Tweet(
TweetBody(),
# Widget(
# Widget(classes={"button"}),
# Widget(classes={"button"}),
# classes={"horizontal"},
# ),
),
Widget(
Static(Syntax(CODE, "python"), classes="code"),
classes="scrollable",
),
Error(),
Tweet(TweetBody()),
Warning(),
Tweet(TweetBody()),
Success(),
),
footer=Widget(),
sidebar=Widget(
Widget(classes="title"),
Widget(classes="user"),
OptionItem(),
OptionItem(),
OptionItem(),
Widget(classes="content"),
),
)
async def on_key(self, event) -> None:
await self.dispatch_key(event)
def key_d(self):
self.dark = not self.dark
def key_x(self):
self.panic(self.tree)
css_file = Path(__file__).parent / "basic.css"
app = BasicApp(
css_file=str(css_file), watch_css=True, log="textual.log", log_verbosity=0
)
if __name__ == "__main__":
app.run()