valtrion 0.7.4__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.
- valtrion-0.7.4/.gitignore +10 -0
- valtrion-0.7.4/LICENSE +21 -0
- valtrion-0.7.4/PKG-INFO +39 -0
- valtrion-0.7.4/README.md +15 -0
- valtrion-0.7.4/hatch_build.py +24 -0
- valtrion-0.7.4/pyproject.toml +45 -0
- valtrion-0.7.4/tests/__init__.py +0 -0
- valtrion-0.7.4/tests/fake_adb.py +200 -0
- valtrion-0.7.4/tests/shortdir.py +16 -0
- valtrion-0.7.4/tests/test_android.py +707 -0
- valtrion-0.7.4/tests/test_artifacts.py +156 -0
- valtrion-0.7.4/tests/test_cli.py +132 -0
- valtrion-0.7.4/tests/test_compare.py +426 -0
- valtrion-0.7.4/tests/test_gitstate.py +283 -0
- valtrion-0.7.4/tests/test_host.py +444 -0
- valtrion-0.7.4/tests/test_hostsync.py +369 -0
- valtrion-0.7.4/tests/test_install.py +53 -0
- valtrion-0.7.4/tests/test_ios.py +649 -0
- valtrion-0.7.4/tests/test_jobs.py +453 -0
- valtrion-0.7.4/tests/test_lease.py +328 -0
- valtrion-0.7.4/tests/test_macos.py +744 -0
- valtrion-0.7.4/tests/test_packaging.py +27 -0
- valtrion-0.7.4/tests/test_pipeline.py +726 -0
- valtrion-0.7.4/tests/test_platform_editor.py +373 -0
- valtrion-0.7.4/tests/test_platform_windows.py +479 -0
- valtrion-0.7.4/tests/test_project.py +272 -0
- valtrion-0.7.4/tests/test_release.py +33 -0
- valtrion-0.7.4/tests/test_release_guard.py +66 -0
- valtrion-0.7.4/tests/test_remote_run.py +407 -0
- valtrion-0.7.4/tests/test_resolver.py +225 -0
- valtrion-0.7.4/tests/test_retention.py +64 -0
- valtrion-0.7.4/tests/test_schema.py +245 -0
- valtrion-0.7.4/tests/test_select.py +647 -0
- valtrion-0.7.4/tests/test_ssh_reachability.py +317 -0
- valtrion-0.7.4/tests/test_supervise.py +202 -0
- valtrion-0.7.4/tests/test_transport_quoting.py +94 -0
- valtrion-0.7.4/tests/test_unity_slots.py +421 -0
- valtrion-0.7.4/tests/test_workroot.py +811 -0
- valtrion-0.7.4/valtrion/__init__.py +3 -0
- valtrion-0.7.4/valtrion/__main__.py +5 -0
- valtrion-0.7.4/valtrion/_compat.json +7 -0
- valtrion-0.7.4/valtrion/_schemas/baseline.schema.json +38 -0
- valtrion-0.7.4/valtrion/_schemas/checks.schema.json +48 -0
- valtrion-0.7.4/valtrion/_schemas/common.schema.json +48 -0
- valtrion-0.7.4/valtrion/_schemas/event.schema.json +24 -0
- valtrion-0.7.4/valtrion/_schemas/heartbeat.schema.json +29 -0
- valtrion-0.7.4/valtrion/_schemas/hosts.schema.json +64 -0
- valtrion-0.7.4/valtrion/_schemas/job.schema.json +38 -0
- valtrion-0.7.4/valtrion/_schemas/lease.schema.json +21 -0
- valtrion-0.7.4/valtrion/_schemas/pipeline.schema.json +62 -0
- valtrion-0.7.4/valtrion/_schemas/plan.schema.json +54 -0
- valtrion-0.7.4/valtrion/_schemas/registry.schema.json +42 -0
- valtrion-0.7.4/valtrion/_schemas/result.schema.json +131 -0
- valtrion-0.7.4/valtrion/adapter.py +161 -0
- valtrion-0.7.4/valtrion/artifacts.py +266 -0
- valtrion-0.7.4/valtrion/cli.py +159 -0
- valtrion-0.7.4/valtrion/commands.py +1047 -0
- valtrion-0.7.4/valtrion/compare.py +404 -0
- valtrion-0.7.4/valtrion/config.py +275 -0
- valtrion-0.7.4/valtrion/constants.py +79 -0
- valtrion-0.7.4/valtrion/core.py +217 -0
- valtrion-0.7.4/valtrion/doctor.py +296 -0
- valtrion-0.7.4/valtrion/gitstate.py +107 -0
- valtrion-0.7.4/valtrion/hostsync.py +162 -0
- valtrion-0.7.4/valtrion/jobs.py +959 -0
- valtrion-0.7.4/valtrion/lease.py +481 -0
- valtrion-0.7.4/valtrion/pipeline.py +908 -0
- valtrion-0.7.4/valtrion/project.py +218 -0
- valtrion-0.7.4/valtrion/providers/__init__.py +55 -0
- valtrion-0.7.4/valtrion/providers/android.py +695 -0
- valtrion-0.7.4/valtrion/providers/editor.py +495 -0
- valtrion-0.7.4/valtrion/providers/ios.py +664 -0
- valtrion-0.7.4/valtrion/providers/macos.py +684 -0
- valtrion-0.7.4/valtrion/providers/unity/__init__.py +1 -0
- valtrion-0.7.4/valtrion/providers/unity/support.py +239 -0
- valtrion-0.7.4/valtrion/providers/windows.py +423 -0
- valtrion-0.7.4/valtrion/remote.py +289 -0
- valtrion-0.7.4/valtrion/report.py +272 -0
- valtrion-0.7.4/valtrion/resolver.py +164 -0
- valtrion-0.7.4/valtrion/retention.py +63 -0
- valtrion-0.7.4/valtrion/schema.py +336 -0
- valtrion-0.7.4/valtrion/select.py +598 -0
- valtrion-0.7.4/valtrion/submit.py +221 -0
- valtrion-0.7.4/valtrion/supervise.py +548 -0
- valtrion-0.7.4/valtrion/transport.py +295 -0
- valtrion-0.7.4/valtrion/wheel.py +78 -0
- valtrion-0.7.4/valtrion/workroot.py +649 -0
valtrion-0.7.4/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GORYNED
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
valtrion-0.7.4/PKG-INFO
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: valtrion
|
|
3
|
+
Version: 0.7.4
|
|
4
|
+
Summary: Repeatable checks, supervised execution and verifiable evidence on local and SSH hosts.
|
|
5
|
+
Project-URL: Homepage, https://github.com/GORYNED/Valtrion
|
|
6
|
+
Project-URL: Documentation, https://github.com/GORYNED/Valtrion/blob/main/docs/quick-start.md
|
|
7
|
+
Project-URL: Changelog, https://github.com/GORYNED/Valtrion/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/GORYNED/Valtrion/issues
|
|
9
|
+
Author: GORYNED
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: evidence,supervision,testing,unity,validation
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Testing
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Valtrion Core
|
|
26
|
+
|
|
27
|
+
The Python CLI and library that drive Valtrion checks on local and SSH hosts.
|
|
28
|
+
|
|
29
|
+
Core handles host selection, resource locks, supervised jobs, platform providers, result collection and metric comparison. The runtime needs Python 3.10 or newer and uses only the standard library. Unity, device tools and project-defined build methods are required only for the checks that use them.
|
|
30
|
+
|
|
31
|
+
Use `valtrion job start -- ...` to supervise an ordinary command without Unity. It retains process status, events and console output, with timeouts and resource leases. See the Quick Start for a Python data check, including failure and timeout cases. Command jobs do not produce app-side plan results for `compare` or `report`.
|
|
32
|
+
|
|
33
|
+
Unity is the first and only ready application integration. The higher-level `run` and `pipeline` workflow, project discovery, build profiles and metric comparability still have Unity-specific assumptions. Platform providers are transport and lifecycle mechanisms, not proof of another engine integration.
|
|
34
|
+
|
|
35
|
+
This is pre-release software. Install with `pipx install valtrion` (update with `pipx upgrade valtrion`), or from a full repository checkout with `python tools/install.py --venv`. The Unity package is installed separately from Git with its own version; see the installation guide.
|
|
36
|
+
|
|
37
|
+
See the [repository README](https://github.com/GORYNED/Valtrion#readme), [Quick Start](https://github.com/GORYNED/Valtrion/blob/main/docs/quick-start.md), and [CLI reference](https://github.com/GORYNED/Valtrion/blob/main/docs/cli.md). Core and the Unity package have separate versions; the repository's `compat.json` defines compatible pairs.
|
|
38
|
+
|
|
39
|
+
Valtrion runs trusted project code with your permissions. See the [trust model](https://github.com/GORYNED/Valtrion/blob/main/docs/trust-model.md) before using it with a project or remote host.
|
valtrion-0.7.4/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Valtrion Core
|
|
2
|
+
|
|
3
|
+
The Python CLI and library that drive Valtrion checks on local and SSH hosts.
|
|
4
|
+
|
|
5
|
+
Core handles host selection, resource locks, supervised jobs, platform providers, result collection and metric comparison. The runtime needs Python 3.10 or newer and uses only the standard library. Unity, device tools and project-defined build methods are required only for the checks that use them.
|
|
6
|
+
|
|
7
|
+
Use `valtrion job start -- ...` to supervise an ordinary command without Unity. It retains process status, events and console output, with timeouts and resource leases. See the Quick Start for a Python data check, including failure and timeout cases. Command jobs do not produce app-side plan results for `compare` or `report`.
|
|
8
|
+
|
|
9
|
+
Unity is the first and only ready application integration. The higher-level `run` and `pipeline` workflow, project discovery, build profiles and metric comparability still have Unity-specific assumptions. Platform providers are transport and lifecycle mechanisms, not proof of another engine integration.
|
|
10
|
+
|
|
11
|
+
This is pre-release software. Install with `pipx install valtrion` (update with `pipx upgrade valtrion`), or from a full repository checkout with `python tools/install.py --venv`. The Unity package is installed separately from Git with its own version; see the installation guide.
|
|
12
|
+
|
|
13
|
+
See the [repository README](https://github.com/GORYNED/Valtrion#readme), [Quick Start](https://github.com/GORYNED/Valtrion/blob/main/docs/quick-start.md), and [CLI reference](https://github.com/GORYNED/Valtrion/blob/main/docs/cli.md). Core and the Unity package have separate versions; the repository's `compat.json` defines compatible pairs.
|
|
14
|
+
|
|
15
|
+
Valtrion runs trusted project code with your permissions. See the [trust model](https://github.com/GORYNED/Valtrion/blob/main/docs/trust-model.md) before using it with a project or remote host.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Bundle shared files from a checkout or reuse the copies in a source distribution."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CustomBuildHook(BuildHookInterface):
|
|
9
|
+
def initialize(self, version, build_data):
|
|
10
|
+
root = Path(self.root)
|
|
11
|
+
sources = {
|
|
12
|
+
"valtrion/_schemas": (root / "valtrion/_schemas", root.parent / "contracts/schemas"),
|
|
13
|
+
"valtrion/_compat.json": (root / "valtrion/_compat.json", root.parent / "compat.json"),
|
|
14
|
+
"valtrion/_license.txt": (root / "valtrion/_license.txt", root / "LICENSE", root.parent / "LICENSE"),
|
|
15
|
+
}
|
|
16
|
+
included = build_data.setdefault("force_include", {})
|
|
17
|
+
for target, candidates in sources.items():
|
|
18
|
+
source = next((path for path in candidates if path.exists()), None)
|
|
19
|
+
if source is None:
|
|
20
|
+
raise FileNotFoundError(f"Missing required distribution file: {target}")
|
|
21
|
+
if source != root / target:
|
|
22
|
+
included[str(source)] = target
|
|
23
|
+
if self.target_name == "sdist":
|
|
24
|
+
included[str(next(path for path in sources["valtrion/_license.txt"] if path.is_file()))] = "LICENSE"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "valtrion"
|
|
7
|
+
version = "0.7.4"
|
|
8
|
+
description = "Repeatable checks, supervised execution and verifiable evidence on local and SSH hosts."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = []
|
|
14
|
+
keywords = ["testing", "validation", "supervision", "unity", "evidence"]
|
|
15
|
+
authors = [{ name = "GORYNED" }]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Topic :: Software Development :: Testing",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/GORYNED/Valtrion"
|
|
31
|
+
Documentation = "https://github.com/GORYNED/Valtrion/blob/main/docs/quick-start.md"
|
|
32
|
+
Changelog = "https://github.com/GORYNED/Valtrion/blob/main/CHANGELOG.md"
|
|
33
|
+
Issues = "https://github.com/GORYNED/Valtrion/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
valtrion = "valtrion.cli:main"
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build.targets.wheel]
|
|
39
|
+
packages = ["valtrion"]
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.hooks.custom]
|
|
42
|
+
path = "hatch_build.py"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.sdist]
|
|
45
|
+
include = ["valtrion", "tests", "README.md", "hatch_build.py"]
|
|
File without changes
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""A fake `adb` for the Android adapter tests: a small device simulated in a folder named by FAKE_ADB_STATE.
|
|
2
|
+
|
|
3
|
+
`state.json` describes the attached devices; `sdcard/` and `tmp/` stand for `/sdcard` and `/data/local/tmp`; `calls.log` records
|
|
4
|
+
every command line so a test can assert what was (or was not) run. Only the commands the adapter issues are understood.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
15
|
+
from valtrion.core import atomic_write_json
|
|
16
|
+
|
|
17
|
+
ROOT = Path(os.environ["FAKE_ADB_STATE"])
|
|
18
|
+
STATE = ROOT / "state.json"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load():
|
|
22
|
+
return json.loads(STATE.read_text(encoding="utf-8"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def save(state):
|
|
26
|
+
atomic_write_json(STATE, state)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def local(path):
|
|
30
|
+
if path.startswith("/sdcard/"):
|
|
31
|
+
return ROOT / "sdcard" / path[len("/sdcard/"):]
|
|
32
|
+
if path.startswith("/data/local/tmp/"):
|
|
33
|
+
return ROOT / "tmp" / path[len("/data/local/tmp/"):]
|
|
34
|
+
return ROOT / "other" / path.lstrip("/")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def say(text, code=0):
|
|
38
|
+
sys.stdout.write(text if text.endswith("\n") or not text else text + "\n")
|
|
39
|
+
sys.stdout.flush()
|
|
40
|
+
sys.exit(code)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def start_daemon():
|
|
44
|
+
"""Like the real `adb start-server`: leave a long-lived process behind that outlives this command, and record its pid in
|
|
45
|
+
`server.pid` (one line per start). It is NOT moved out of the caller's process group or Windows job, as an adb server started
|
|
46
|
+
inside a supervised call is not: whoever kills that call's tree kills the server too. It ends by itself after two minutes, so
|
|
47
|
+
a test that forgets it does not leave it forever."""
|
|
48
|
+
import subprocess
|
|
49
|
+
options = {"stdin": subprocess.DEVNULL, "stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL}
|
|
50
|
+
code = "import time; time.sleep(120)"
|
|
51
|
+
if os.name == "nt":
|
|
52
|
+
process = subprocess.Popen([sys.executable, "-c", code], creationflags=0x00000008, **options) # DETACHED_PROCESS
|
|
53
|
+
else:
|
|
54
|
+
process = subprocess.Popen([sys.executable, "-c", code], **options)
|
|
55
|
+
with open(ROOT / "server.pid", "a", encoding="utf-8") as record:
|
|
56
|
+
record.write(f"{process.pid}\n")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def main(argv):
|
|
60
|
+
with open(ROOT / "calls.log", "a", encoding="utf-8") as log:
|
|
61
|
+
log.write(" ".join(argv) + "\n")
|
|
62
|
+
state = load()
|
|
63
|
+
if argv[:1] == ["start-server"]:
|
|
64
|
+
if state.get("daemon"):
|
|
65
|
+
start_daemon()
|
|
66
|
+
say("")
|
|
67
|
+
if state.get("down", 0) > 0: # the shared adb server was restarted by another adb on the machine
|
|
68
|
+
state["down"] -= 1
|
|
69
|
+
save(state)
|
|
70
|
+
say("* daemon not running; starting now at tcp:5037\nadb.exe: failed to check server version: cannot connect to daemon", 1)
|
|
71
|
+
serial = None
|
|
72
|
+
if argv[:1] == ["-s"]:
|
|
73
|
+
serial, argv = argv[1], argv[2:]
|
|
74
|
+
command, rest = argv[0], argv[1:]
|
|
75
|
+
if command == "version":
|
|
76
|
+
say("Android Debug Bridge version 1.0.41")
|
|
77
|
+
if command == "devices":
|
|
78
|
+
lines = ["adb server version (41) doesn't match this client (39); killing..."] if state.get("mismatch_notice") else []
|
|
79
|
+
lines.append("List of devices attached")
|
|
80
|
+
for d in state["devices"]:
|
|
81
|
+
shown = d["state"]
|
|
82
|
+
if d.get("offline_polls", 0) > 0: # transient `offline` right after a server restart
|
|
83
|
+
d["offline_polls"] -= 1
|
|
84
|
+
shown = "offline"
|
|
85
|
+
save(state)
|
|
86
|
+
# Real adb prints no model for a device it cannot talk to.
|
|
87
|
+
model = f" model:{d.get('model', 'Pixel_Fake')}" if shown == "device" else ""
|
|
88
|
+
lines.append(f"{d['serial']}\t{shown} usb:1-1{model} transport_id:1")
|
|
89
|
+
say("\n".join(lines))
|
|
90
|
+
device = next((d for d in state["devices"] if d["serial"] == serial), None)
|
|
91
|
+
if device is None:
|
|
92
|
+
say(f"error: device '{serial}' not found", 1)
|
|
93
|
+
if device.get("offline_polls", 0) > 0 and command != "devices":
|
|
94
|
+
say("error: device offline", 1)
|
|
95
|
+
if device["state"] == "unauthorized":
|
|
96
|
+
say("error: device unauthorized.\nThis adbd's $ADB_VENDOR_KEYS is not set; try 'adb kill-server'.", 1)
|
|
97
|
+
if device["state"] == "offline":
|
|
98
|
+
say("error: device offline", 1)
|
|
99
|
+
if command == "install":
|
|
100
|
+
if state.get("install_error"):
|
|
101
|
+
say(f"Performing Streamed Install\nadb: failed to install {rest[-1]}: Failure [{state['install_error']}]", 1)
|
|
102
|
+
state["installed"] = sorted(set(state.get("installed", [])) | {state["package"]})
|
|
103
|
+
save(state)
|
|
104
|
+
say("Performing Streamed Install\nSuccess")
|
|
105
|
+
if command == "push":
|
|
106
|
+
source, target = rest
|
|
107
|
+
destination = local(target)
|
|
108
|
+
if source.endswith(os.sep + ".") or source.endswith("/."):
|
|
109
|
+
shutil.copytree(source[:-2], destination, dirs_exist_ok=True)
|
|
110
|
+
else:
|
|
111
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
112
|
+
shutil.copyfile(source, destination)
|
|
113
|
+
say("1 file pushed")
|
|
114
|
+
if command == "pull":
|
|
115
|
+
source, target = rest
|
|
116
|
+
if source.endswith("/."):
|
|
117
|
+
shutil.copytree(local(source[:-2]), target, dirs_exist_ok=True)
|
|
118
|
+
else:
|
|
119
|
+
shutil.copyfile(local(source), target)
|
|
120
|
+
say("files pulled")
|
|
121
|
+
if command == "logcat":
|
|
122
|
+
if "-c" in rest:
|
|
123
|
+
say("")
|
|
124
|
+
if state.get("no_logcat"):
|
|
125
|
+
say("", 0)
|
|
126
|
+
lines = state.get("logcat", [])
|
|
127
|
+
for line in lines:
|
|
128
|
+
print(line if "raw" in rest else f"10-29 12:00:00.000 1 1 I Unity : {line}", flush=True)
|
|
129
|
+
time.sleep(120)
|
|
130
|
+
say("")
|
|
131
|
+
if command == "shell":
|
|
132
|
+
shell(state, device, " ".join(rest))
|
|
133
|
+
say(f"unsupported fake adb command: {argv}", 2)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def shell(state, device, script):
|
|
137
|
+
if script.startswith("'am' "): # the adapter quotes every word of `am start`; the device shell would unquote them
|
|
138
|
+
import shlex
|
|
139
|
+
script = " ".join(shlex.split(script))
|
|
140
|
+
package = state["package"]
|
|
141
|
+
running = state.setdefault("running", [])
|
|
142
|
+
if script.startswith("getprop"):
|
|
143
|
+
say("\n".join(f"[{k}]: [{v}]" for k, v in device["props"].items()))
|
|
144
|
+
if "dumpsys battery" in script:
|
|
145
|
+
say("Current Battery Service state:\n level: 77\n temperature: 301\n status: 2\n--thermal--\nThermal Status: 0\n"
|
|
146
|
+
"--df--\nFilesystem 1K-blocks Used Available Use% Mounted on\n/dev/fuse 100000000 20000000 80000000 20% /sdcard")
|
|
147
|
+
if script.startswith("dumpsys window") and device.get("no_keyguard_keys"):
|
|
148
|
+
say("", 1)
|
|
149
|
+
if script.startswith("dumpsys window"):
|
|
150
|
+
say(f" mShowingDream=false mDreamingLockscreen=false\n isKeyguardShowing={'true' if device.get('locked') else 'false'}")
|
|
151
|
+
if script.startswith("input keyevent") or "dismiss-keyguard" in script:
|
|
152
|
+
device["locked"] = False if device.get("insecure") else device.get("locked", False)
|
|
153
|
+
save(state)
|
|
154
|
+
say("")
|
|
155
|
+
if script.startswith("mkdir -p"):
|
|
156
|
+
local(script.split("'")[1]).mkdir(parents=True, exist_ok=True)
|
|
157
|
+
say("")
|
|
158
|
+
if script.startswith("if [ -d"):
|
|
159
|
+
path = local(script.split("'")[1])
|
|
160
|
+
say("dir" if path.is_dir() else "file" if path.exists() else "absent")
|
|
161
|
+
if script.startswith("pm list packages"):
|
|
162
|
+
say(f"package:{package}" if package in state.get("installed", []) else "")
|
|
163
|
+
if script.startswith("pm clear"):
|
|
164
|
+
shutil.rmtree(local(f"/sdcard/Android/data/{package}"), ignore_errors=True)
|
|
165
|
+
say("Success")
|
|
166
|
+
if script.startswith("cmd package resolve-activity"):
|
|
167
|
+
say(f"priority=0 preferredOrder=0\n{package}/com.unity3d.player.UnityPlayerGameActivity")
|
|
168
|
+
if script.startswith("pidof"):
|
|
169
|
+
say("4242" if package in running else "", 0 if package in running else 1)
|
|
170
|
+
if script.startswith("dumpsys meminfo") or script.startswith("dumpsys gfxinfo"):
|
|
171
|
+
say(script.split()[1] + " dump for " + package)
|
|
172
|
+
if script.startswith("am force-stop"):
|
|
173
|
+
if package in running:
|
|
174
|
+
running.remove(package)
|
|
175
|
+
save(state)
|
|
176
|
+
say("")
|
|
177
|
+
if script.startswith("am start"):
|
|
178
|
+
if state.get("launch_error"):
|
|
179
|
+
say("Error type 3\nError: Activity class does not exist.", 0)
|
|
180
|
+
if package not in running:
|
|
181
|
+
running.append(package)
|
|
182
|
+
for relative, content in state.get("on_launch", {}).items(): # what the in-app harness writes after it starts
|
|
183
|
+
target = local(f"/sdcard/Android/data/{package}/files/{relative}")
|
|
184
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
185
|
+
target.write_text(json.dumps(content), encoding="utf-8")
|
|
186
|
+
save(state)
|
|
187
|
+
say("Stopping: " + package + "\nStarting: Intent\nStatus: ok\nLaunchState: COLD")
|
|
188
|
+
if script.startswith("screencap"):
|
|
189
|
+
target = local(script.split()[-1])
|
|
190
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
target.write_bytes(b"\x89PNG fake")
|
|
192
|
+
say("")
|
|
193
|
+
if script.startswith("rm -f"):
|
|
194
|
+
local(script.split()[-1]).unlink(missing_ok=True)
|
|
195
|
+
say("")
|
|
196
|
+
say(f"unsupported fake shell command: {script}", 2)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
main(sys.argv[1:])
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""A temporary folder whose path is short enough for a unix-domain socket (the default temp folder on macOS is not)."""
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@contextlib.contextmanager
|
|
10
|
+
def short_temp_dir():
|
|
11
|
+
parent = "/tmp" if os.name == "posix" and os.path.isdir("/tmp") else None
|
|
12
|
+
folder = tempfile.mkdtemp(prefix="gdv-", dir=parent)
|
|
13
|
+
try:
|
|
14
|
+
yield folder
|
|
15
|
+
finally:
|
|
16
|
+
shutil.rmtree(folder, ignore_errors=True)
|