krypton-app 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- krypton_app-0.1.0/.gitignore +41 -0
- krypton_app-0.1.0/PKG-INFO +46 -0
- krypton_app-0.1.0/README.md +29 -0
- krypton_app-0.1.0/pyproject.toml +43 -0
- krypton_app-0.1.0/src/krypton_app/__init__.py +2 -0
- krypton_app-0.1.0/src/krypton_app/__main__.py +5 -0
- krypton_app-0.1.0/src/krypton_app/cli.py +61 -0
- krypton_app-0.1.0/src/krypton_app/interfaces/__init__.py +1 -0
- krypton_app-0.1.0/src/krypton_app/interfaces/krypton_launcher_interface.py +13 -0
- krypton_app-0.1.0/src/krypton_app/launcher.py +104 -0
- krypton_app-0.1.0/src/krypton_app/py.typed +0 -0
- krypton_app-0.1.0/tests/test_launcher.py +141 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.venv/
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.mypy_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.coverage
|
|
8
|
+
.coverage.*
|
|
9
|
+
htmlcov/
|
|
10
|
+
*.egg-info/
|
|
11
|
+
/dist/
|
|
12
|
+
/build/
|
|
13
|
+
.claudeloop/
|
|
14
|
+
.playwright-mcp/
|
|
15
|
+
.vibey/plans/
|
|
16
|
+
.agyloop/
|
|
17
|
+
.codexloop/
|
|
18
|
+
.cursorloop/
|
|
19
|
+
.qwenloop/
|
|
20
|
+
.vibey-tmp/site/
|
|
21
|
+
/site/
|
|
22
|
+
|
|
23
|
+
# Workspace members do not carry their own lockfiles. uv resolves the whole workspace
|
|
24
|
+
# (`[tool.uv.workspace] members`, ADR-0021) into the single root uv.lock, and that is the
|
|
25
|
+
# only one `uv lock --check` validates. The per-runner locks that survived the sibling-repo
|
|
26
|
+
# absorption were installed from by nothing -- CI installs the runners with plain
|
|
27
|
+
# `pip install -e ".[dev]"` -- and served only to raise Dependabot advisories against
|
|
28
|
+
# versions no environment ever resolved. Keep them out.
|
|
29
|
+
src/vibey_runners/*/uv.lock
|
|
30
|
+
src/vibey_tools/*/uv.lock
|
|
31
|
+
|
|
32
|
+
# pre-commit writes here: `[env] PRE_COMMIT_HOME = ".cache/pre-commit"` in
|
|
33
|
+
# .claude/settings.json keeps its cache inside the sandbox-writable tree, which means
|
|
34
|
+
# every worktree grows a .cache/ that is tooling state, never repository content.
|
|
35
|
+
.cache/
|
|
36
|
+
|
|
37
|
+
# The npm workspace (root package.json, ADR-0067): its install tree and each package's build.
|
|
38
|
+
/node_modules/
|
|
39
|
+
packages/*/node_modules/
|
|
40
|
+
packages/*/dist/
|
|
41
|
+
packages/*/coverage/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: krypton-app
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: krypton, the apps of vibey: launches the local vibey hub and opens it.
|
|
5
|
+
Project-URL: Homepage, https://the-vibey-project.github.io/vibey/
|
|
6
|
+
Project-URL: Source, https://github.com/the-vibey-project/vibey/tree/main/clients/krypton-app
|
|
7
|
+
Project-URL: Issues, https://github.com/the-vibey-project/vibey/issues
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
|
+
Requires-Dist: vibey-engine
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
15
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# krypton-app
|
|
19
|
+
|
|
20
|
+
krypton is every app and interface of [vibey](https://the-vibey-project.github.io/vibey/).
|
|
21
|
+
This package carries them and installs one command, `krypton`.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install krypton-app # brings vibey-engine with it
|
|
25
|
+
krypton # start the local vibey hub and open it in your browser
|
|
26
|
+
krypton --help
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What `krypton` does today
|
|
30
|
+
|
|
31
|
+
`krypton` is a launcher. It finds the `vibey` command, starts `vibey serve` on
|
|
32
|
+
`127.0.0.1:8765` (change it with `--host` and `--port`), waits until the hub answers, and
|
|
33
|
+
opens it in your browser. `--no-browser` only starts it.
|
|
34
|
+
|
|
35
|
+
When the installed vibey-engine has no `vibey serve` yet, `krypton` says so plainly, lists
|
|
36
|
+
what you can use instead, and exits with status 1. It never pretends a hub is running.
|
|
37
|
+
|
|
38
|
+
## Releases
|
|
39
|
+
|
|
40
|
+
Published by `.github/workflows/krypton-app.yml` alone, by trusted publishing: a dev build
|
|
41
|
+
to TestPyPI (environment `testpypi`) on each push to `develop` that changes this tree or the
|
|
42
|
+
workflow, and a release to PyPI (environment `pypi`) on each such push to `main`. There is no
|
|
43
|
+
schedule. A push GitHub skips publishes nothing: a squash merge whose message quotes an old
|
|
44
|
+
`[skip ci]` subject skips every push workflow, which is how the 3.0.0 promotion first
|
|
45
|
+
published neither package. Merge a promotion by rebase, or edit the squash message. ADR-0069 records why vibey publishes exactly two packages, `vibey-engine` and
|
|
46
|
+
`krypton-app`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# krypton-app
|
|
2
|
+
|
|
3
|
+
krypton is every app and interface of [vibey](https://the-vibey-project.github.io/vibey/).
|
|
4
|
+
This package carries them and installs one command, `krypton`.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pip install krypton-app # brings vibey-engine with it
|
|
8
|
+
krypton # start the local vibey hub and open it in your browser
|
|
9
|
+
krypton --help
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## What `krypton` does today
|
|
13
|
+
|
|
14
|
+
`krypton` is a launcher. It finds the `vibey` command, starts `vibey serve` on
|
|
15
|
+
`127.0.0.1:8765` (change it with `--host` and `--port`), waits until the hub answers, and
|
|
16
|
+
opens it in your browser. `--no-browser` only starts it.
|
|
17
|
+
|
|
18
|
+
When the installed vibey-engine has no `vibey serve` yet, `krypton` says so plainly, lists
|
|
19
|
+
what you can use instead, and exits with status 1. It never pretends a hub is running.
|
|
20
|
+
|
|
21
|
+
## Releases
|
|
22
|
+
|
|
23
|
+
Published by `.github/workflows/krypton-app.yml` alone, by trusted publishing: a dev build
|
|
24
|
+
to TestPyPI (environment `testpypi`) on each push to `develop` that changes this tree or the
|
|
25
|
+
workflow, and a release to PyPI (environment `pypi`) on each such push to `main`. There is no
|
|
26
|
+
schedule. A push GitHub skips publishes nothing: a squash merge whose message quotes an old
|
|
27
|
+
`[skip ci]` subject skips every push workflow, which is how the 3.0.0 promotion first
|
|
28
|
+
published neither package. Merge a promotion by rebase, or edit the squash message. ADR-0069 records why vibey publishes exactly two packages, `vibey-engine` and
|
|
29
|
+
`krypton-app`.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
# krypton-app: the apps' package (sub-doctrine 9.e, ADR-0069). Every app and interface a
|
|
6
|
+
# person uses is krypton; this package carries them and the `krypton` command. It is
|
|
7
|
+
# published by its own workflow, .github/workflows/krypton-app.yml, and by no other.
|
|
8
|
+
[project]
|
|
9
|
+
name = "krypton-app"
|
|
10
|
+
version = "0.1.0"
|
|
11
|
+
description = "krypton, the apps of vibey: launches the local vibey hub and opens it."
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.12"
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
# The launcher starts `vibey serve`, so the engine is what makes it useful. Unpinned: the
|
|
16
|
+
# two packages release on their own cadences, and the launcher checks what the installed
|
|
17
|
+
# engine can do at run time rather than trusting a version number to say it.
|
|
18
|
+
dependencies = ["vibey-engine"]
|
|
19
|
+
|
|
20
|
+
[project.urls]
|
|
21
|
+
Homepage = "https://the-vibey-project.github.io/vibey/"
|
|
22
|
+
Source = "https://github.com/the-vibey-project/vibey/tree/main/clients/krypton-app"
|
|
23
|
+
Issues = "https://github.com/the-vibey-project/vibey/issues"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
krypton = "krypton_app.cli:main"
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = ["pytest>=8.3", "pytest-cov>=6.0", "ruff>=0.8", "mypy>=1.13"]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel]
|
|
32
|
+
packages = ["src/krypton_app"]
|
|
33
|
+
|
|
34
|
+
[tool.ruff]
|
|
35
|
+
line-length = 100
|
|
36
|
+
target-version = "py312"
|
|
37
|
+
|
|
38
|
+
[tool.mypy]
|
|
39
|
+
strict = true
|
|
40
|
+
mypy_path = "src"
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
# Made with ❤️ by [Vibey](https://the-vibey-project.github.io/vibey/), Developed by [Adam Matthew Steinberger](https://vibewithadam.matthewsteinberger.com/) ([@adammatthewsteinberger](https://github.com/adammatthewsteinberger/)).
|
|
2
|
+
"""krypton-app: the apps of vibey, and the `krypton` command that launches them."""
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""The `krypton` command."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
from collections.abc import Sequence
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from krypton_app.launcher import KryptonLauncher
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _version() -> str:
|
|
16
|
+
# A module-level function because it is a one-line lookup with no state (ADR-0016).
|
|
17
|
+
try:
|
|
18
|
+
return version("krypton-app")
|
|
19
|
+
except PackageNotFoundError: # pragma: no cover - only in an uninstalled tree
|
|
20
|
+
return "0+unknown"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _vibey_beside_me() -> str | None:
|
|
24
|
+
# A module-level function because it is a one-line lookup with no state (ADR-0016).
|
|
25
|
+
# The engine installed into the same environment as krypton-app comes first: a `vibey`
|
|
26
|
+
# earlier on PATH may be an older one from another environment.
|
|
27
|
+
candidate = Path(sys.executable).parent / "vibey"
|
|
28
|
+
return str(candidate) if os.access(candidate, os.X_OK) else None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
32
|
+
# A module-level function because a console-script entry point must be one (ADR-0016);
|
|
33
|
+
# everything it does is KryptonLauncher's.
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
prog="krypton",
|
|
36
|
+
description=(
|
|
37
|
+
"krypton, the apps of vibey. Starts the local vibey hub (`vibey serve`) and "
|
|
38
|
+
"opens it in your browser; if the installed vibey cannot serve it yet, says so "
|
|
39
|
+
"and lists what is available instead."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument("--version", action="version", version=f"krypton {_version()}")
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--host", default="127.0.0.1", help="address to serve on (default: 127.0.0.1)"
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument("--port", type=int, default=8765, help="port to serve on (default: 8765)")
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--no-browser", action="store_true", help="start the hub without opening it"
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--vibey",
|
|
52
|
+
default=None,
|
|
53
|
+
help="the vibey program to use (default: the one beside krypton, else vibey on PATH)",
|
|
54
|
+
)
|
|
55
|
+
args = parser.parse_args(argv)
|
|
56
|
+
launcher = KryptonLauncher(out=sys.stdout, vibey=args.vibey or _vibey_beside_me())
|
|
57
|
+
return launcher.launch(args.host, args.port, open_browser=not args.no_browser)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__": # pragma: no cover
|
|
61
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Interfaces of krypton-app (ADR-0016)."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""The contract of the krypton launcher (ADR-0016: an interface beside every class)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KryptonLauncherInterface(ABC):
|
|
9
|
+
"""Starts the local vibey hub and opens it, or says plainly why it cannot."""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def launch(self, host: str, port: int, open_browser: bool) -> int:
|
|
13
|
+
"""Run the hub until it exits; return the process exit status."""
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""The krypton launcher: `vibey serve`, then the browser -- or an honest refusal."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import subprocess
|
|
6
|
+
import time
|
|
7
|
+
import urllib.error
|
|
8
|
+
import urllib.request
|
|
9
|
+
import webbrowser
|
|
10
|
+
from collections.abc import Callable, Sequence
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from shutil import which
|
|
13
|
+
from typing import IO, Protocol
|
|
14
|
+
|
|
15
|
+
from krypton_app.interfaces.krypton_launcher_interface import KryptonLauncherInterface
|
|
16
|
+
|
|
17
|
+
AVAILABLE_INSTEAD = (
|
|
18
|
+
"What you can use now:\n"
|
|
19
|
+
" vibey --help the vibey command line\n"
|
|
20
|
+
" vibey doctor what is installed and what is missing\n"
|
|
21
|
+
" the VS Code extension krypton inside your editor (clients/vscode)\n"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class _Process(Protocol):
|
|
26
|
+
def poll(self) -> int | None: ...
|
|
27
|
+
def wait(self) -> int: ...
|
|
28
|
+
def terminate(self) -> None: ...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _probe(url: str) -> bool:
|
|
32
|
+
"""Whether anything answers HTTP at `url`, whatever the status."""
|
|
33
|
+
try:
|
|
34
|
+
with urllib.request.urlopen(url, timeout=2): # nosec B310 - loopback URL we built
|
|
35
|
+
return True
|
|
36
|
+
except urllib.error.HTTPError:
|
|
37
|
+
return True
|
|
38
|
+
except (urllib.error.URLError, OSError):
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class KryptonLauncher(KryptonLauncherInterface):
|
|
44
|
+
"""Launches the hub through the installed `vibey`, with every side effect injectable."""
|
|
45
|
+
|
|
46
|
+
out: IO[str]
|
|
47
|
+
vibey: str | None = None
|
|
48
|
+
ready_timeout: float = 60.0
|
|
49
|
+
find: Callable[[str], str | None] = which
|
|
50
|
+
run: Callable[[Sequence[str]], int] = field(
|
|
51
|
+
default=lambda argv: (
|
|
52
|
+
subprocess.run( # nosec B603 - argv, no shell
|
|
53
|
+
list(argv), capture_output=True, timeout=30, check=False
|
|
54
|
+
).returncode
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
spawn: Callable[[Sequence[str]], _Process] = field(
|
|
58
|
+
default=lambda argv: subprocess.Popen(list(argv)) # nosec B603 - argv, no shell
|
|
59
|
+
)
|
|
60
|
+
probe: Callable[[str], bool] = _probe
|
|
61
|
+
browse: Callable[[str], object] = webbrowser.open
|
|
62
|
+
sleep: Callable[[float], None] = time.sleep
|
|
63
|
+
clock: Callable[[], float] = time.monotonic
|
|
64
|
+
|
|
65
|
+
def launch(self, host: str, port: int, open_browser: bool) -> int:
|
|
66
|
+
vibey = self.vibey or self.find("vibey")
|
|
67
|
+
if vibey is None:
|
|
68
|
+
self.out.write(
|
|
69
|
+
"krypton: the vibey command is not installed, so there is no hub to start.\n"
|
|
70
|
+
"Install the engine with: pip install vibey-engine\n"
|
|
71
|
+
)
|
|
72
|
+
return 1
|
|
73
|
+
if self.run([vibey, "serve", "--help"]) != 0:
|
|
74
|
+
self.out.write(
|
|
75
|
+
"krypton: the local web app is started by `vibey serve`, and the installed\n"
|
|
76
|
+
f"vibey ({vibey}) does not provide it yet. Nothing was started.\n"
|
|
77
|
+
+ AVAILABLE_INSTEAD
|
|
78
|
+
)
|
|
79
|
+
return 1
|
|
80
|
+
url = f"http://{host}:{port}/"
|
|
81
|
+
process = self.spawn([vibey, "serve", "--host", host, "--port", str(port)])
|
|
82
|
+
deadline = self.clock() + self.ready_timeout
|
|
83
|
+
while not self.probe(url):
|
|
84
|
+
status = process.poll()
|
|
85
|
+
if status is not None:
|
|
86
|
+
self.out.write(
|
|
87
|
+
f"krypton: vibey serve exited with status {status} before it answered.\n"
|
|
88
|
+
)
|
|
89
|
+
return status or 1
|
|
90
|
+
if self.clock() >= deadline:
|
|
91
|
+
self.out.write(
|
|
92
|
+
f"krypton: nothing answered at {url} within {self.ready_timeout:g}s.\n"
|
|
93
|
+
)
|
|
94
|
+
process.terminate()
|
|
95
|
+
return 1
|
|
96
|
+
self.sleep(0.5)
|
|
97
|
+
self.out.write(f"krypton: the vibey hub is at {url}\n")
|
|
98
|
+
if open_browser:
|
|
99
|
+
self.browse(url)
|
|
100
|
+
try:
|
|
101
|
+
return process.wait()
|
|
102
|
+
except KeyboardInterrupt:
|
|
103
|
+
process.terminate()
|
|
104
|
+
return 130
|
|
File without changes
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""The krypton launcher, with every side effect faked: no process, browser or network."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from krypton_app import cli
|
|
11
|
+
from krypton_app.launcher import KryptonLauncher
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FakeProcess:
|
|
15
|
+
def __init__(
|
|
16
|
+
self, polls: Sequence[int | None] = (None,), wait: int | BaseException = 0
|
|
17
|
+
) -> None:
|
|
18
|
+
self._polls = list(polls)
|
|
19
|
+
self._wait = wait
|
|
20
|
+
self.terminated = False
|
|
21
|
+
|
|
22
|
+
def poll(self) -> int | None:
|
|
23
|
+
return self._polls.pop(0) if len(self._polls) > 1 else self._polls[0]
|
|
24
|
+
|
|
25
|
+
def wait(self) -> int:
|
|
26
|
+
if isinstance(self._wait, BaseException):
|
|
27
|
+
raise self._wait
|
|
28
|
+
return self._wait
|
|
29
|
+
|
|
30
|
+
def terminate(self) -> None:
|
|
31
|
+
self.terminated = True
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def make(
|
|
35
|
+
*,
|
|
36
|
+
found: str | None = "/x/vibey",
|
|
37
|
+
serve_rc: int = 0,
|
|
38
|
+
process: FakeProcess | None = None,
|
|
39
|
+
probes: Sequence[bool] = (True,),
|
|
40
|
+
times: Sequence[float] = (0.0,),
|
|
41
|
+
) -> tuple[KryptonLauncher, io.StringIO, list[list[str]], list[str]]:
|
|
42
|
+
out = io.StringIO()
|
|
43
|
+
spawned: list[list[str]] = []
|
|
44
|
+
browsed: list[str] = []
|
|
45
|
+
probe_values = list(probes)
|
|
46
|
+
clock_values = list(times)
|
|
47
|
+
proc = process or FakeProcess()
|
|
48
|
+
|
|
49
|
+
def spawn(argv: Sequence[str]) -> FakeProcess:
|
|
50
|
+
spawned.append(list(argv))
|
|
51
|
+
return proc
|
|
52
|
+
|
|
53
|
+
launcher = KryptonLauncher(
|
|
54
|
+
out=out,
|
|
55
|
+
find=lambda _name: found,
|
|
56
|
+
run=lambda _argv: serve_rc,
|
|
57
|
+
spawn=spawn,
|
|
58
|
+
probe=lambda _url: probe_values.pop(0) if len(probe_values) > 1 else probe_values[0],
|
|
59
|
+
browse=browsed.append,
|
|
60
|
+
sleep=lambda _s: None,
|
|
61
|
+
clock=lambda: clock_values.pop(0) if len(clock_values) > 1 else clock_values[0],
|
|
62
|
+
)
|
|
63
|
+
return launcher, out, spawned, browsed
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_no_vibey_says_how_to_install_it() -> None:
|
|
67
|
+
launcher, out, spawned, _ = make(found=None)
|
|
68
|
+
assert launcher.launch("127.0.0.1", 8765, open_browser=True) == 1
|
|
69
|
+
assert "pip install vibey-engine" in out.getvalue()
|
|
70
|
+
assert spawned == []
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_an_engine_without_serve_starts_nothing_and_says_what_is_available() -> None:
|
|
74
|
+
launcher, out, spawned, _ = make(serve_rc=2)
|
|
75
|
+
assert launcher.launch("127.0.0.1", 8765, open_browser=True) == 1
|
|
76
|
+
assert "does not provide it yet" in out.getvalue()
|
|
77
|
+
assert "vibey --help" in out.getvalue()
|
|
78
|
+
assert spawned == []
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_the_hub_starts_and_opens() -> None:
|
|
82
|
+
launcher, out, spawned, browsed = make()
|
|
83
|
+
assert launcher.launch("127.0.0.1", 8765, open_browser=True) == 0
|
|
84
|
+
assert spawned == [["/x/vibey", "serve", "--host", "127.0.0.1", "--port", "8765"]]
|
|
85
|
+
assert browsed == ["http://127.0.0.1:8765/"]
|
|
86
|
+
assert "the vibey hub is at" in out.getvalue()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_no_browser_opens_nothing() -> None:
|
|
90
|
+
launcher, _, _, browsed = make()
|
|
91
|
+
assert launcher.launch("127.0.0.1", 8765, open_browser=False) == 0
|
|
92
|
+
assert browsed == []
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_a_serve_that_dies_early_is_reported() -> None:
|
|
96
|
+
launcher, out, _, _ = make(process=FakeProcess(polls=[3]), probes=[False])
|
|
97
|
+
assert launcher.launch("127.0.0.1", 8765, open_browser=True) == 3
|
|
98
|
+
assert "exited with status 3" in out.getvalue()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_a_serve_that_never_answers_is_stopped() -> None:
|
|
102
|
+
proc = FakeProcess()
|
|
103
|
+
launcher, out, _, _ = make(process=proc, probes=[False], times=[0.0, 100.0])
|
|
104
|
+
assert launcher.launch("127.0.0.1", 8765, open_browser=True) == 1
|
|
105
|
+
assert proc.terminated
|
|
106
|
+
assert "nothing answered" in out.getvalue()
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_a_poll_after_one_wait_then_ready() -> None:
|
|
110
|
+
launcher, _, _, browsed = make(probes=[False, True], times=[0.0, 1.0])
|
|
111
|
+
assert launcher.launch("h", 1, open_browser=True) == 0
|
|
112
|
+
assert browsed == ["http://h:1/"]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_interrupt_stops_the_hub() -> None:
|
|
116
|
+
proc = FakeProcess(wait=KeyboardInterrupt())
|
|
117
|
+
launcher, _, _, _ = make(process=proc)
|
|
118
|
+
assert launcher.launch("127.0.0.1", 8765, open_browser=False) == 130
|
|
119
|
+
assert proc.terminated
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_cli_passes_its_options(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
123
|
+
calls: list[tuple[str, int, bool]] = []
|
|
124
|
+
|
|
125
|
+
class Fake:
|
|
126
|
+
def __init__(self, out: object, vibey: str | None) -> None:
|
|
127
|
+
assert vibey == "/nonexistent/vibey"
|
|
128
|
+
|
|
129
|
+
def launch(self, host: str, port: int, open_browser: bool) -> int:
|
|
130
|
+
calls.append((host, port, open_browser))
|
|
131
|
+
return 7
|
|
132
|
+
|
|
133
|
+
monkeypatch.setattr(cli, "KryptonLauncher", Fake)
|
|
134
|
+
assert cli.main(["--no-browser", "--vibey", "/nonexistent/vibey"]) == 7
|
|
135
|
+
assert calls == [("127.0.0.1", 8765, False)]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_cli_help_exits_zero() -> None:
|
|
139
|
+
with pytest.raises(SystemExit) as raised:
|
|
140
|
+
cli.main(["--help"])
|
|
141
|
+
assert raised.value.code == 0
|