初始化奇妙小屏幕控制器项目
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import gzip
|
||||
import hashlib
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tarfile
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.image_kernel_payload import (
|
||||
ImageKernelPayloadError,
|
||||
stage_payload,
|
||||
verify_staged_payload,
|
||||
)
|
||||
from scripts.image_bootstrap_payload import (
|
||||
APP_RELATIVE,
|
||||
TARGET_RELATIVE,
|
||||
ImageBootstrapPayloadError,
|
||||
stage_payload as stage_bootstrap_payload,
|
||||
verify_staged_payload as verify_bootstrap_payload,
|
||||
)
|
||||
from scripts.kernel_artifact import (
|
||||
ARTIFACT_NAME,
|
||||
KERNEL_RELEASE,
|
||||
REQUIRED_BOOT_FILES,
|
||||
SOURCE_ARCHIVE_SHA256,
|
||||
SOURCE_COMMIT,
|
||||
KernelArtifactError,
|
||||
sha256_file,
|
||||
verify_kernel_dependency,
|
||||
verify_source_dependency,
|
||||
)
|
||||
|
||||
|
||||
SOURCE_ROOT = Path(__file__).resolve().parents[1]
|
||||
PROJECT_ROOT = SOURCE_ROOT.parent
|
||||
|
||||
|
||||
def _dependency(
|
||||
root: Path,
|
||||
*,
|
||||
release_marker: str = KERNEL_RELEASE,
|
||||
source_marker: str = SOURCE_COMMIT,
|
||||
extra_member: tarfile.TarInfo | None = None,
|
||||
) -> Path:
|
||||
root.mkdir()
|
||||
files = {
|
||||
"boot/Image-matrix-axp313a1": f"prefix Linux version {KERNEL_RELEASE} suffix".encode(),
|
||||
"boot/sun50i-h616-walnutpi-1b-matrix-axp313a1.dtb": b"dtb-sd",
|
||||
"boot/sun50i-h616-walnutpi-1b-emmc-matrix-axp313a1.dtb": b"dtb-emmc",
|
||||
f"boot/System.map-{KERNEL_RELEASE}": b"system-map",
|
||||
f"boot/config-{KERNEL_RELEASE}": b"config",
|
||||
"KERNEL_RELEASE": f"{release_marker}\n".encode(),
|
||||
"SOURCE_COMMIT": f"{source_marker}\n".encode(),
|
||||
f"lib/modules/{KERNEL_RELEASE}/kernel/example.ko": b"module",
|
||||
}
|
||||
sums = "".join(
|
||||
f"{hashlib.sha256(body).hexdigest()} {name}\n" for name, body in sorted(files.items())
|
||||
).encode()
|
||||
files["SHA256SUMS"] = sums
|
||||
artifact = root / ARTIFACT_NAME
|
||||
with artifact.open("wb") as raw:
|
||||
with gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0) as compressed:
|
||||
with tarfile.open(fileobj=compressed, mode="w", format=tarfile.PAX_FORMAT) as archive:
|
||||
for name, body in sorted(files.items()):
|
||||
info = tarfile.TarInfo(name)
|
||||
info.size = len(body)
|
||||
info.uid = info.gid = 0
|
||||
info.mtime = 0
|
||||
archive.addfile(info, io.BytesIO(body))
|
||||
if extra_member is not None:
|
||||
extra_member.uid = extra_member.gid = 0
|
||||
extra_member.mtime = 0
|
||||
archive.addfile(extra_member)
|
||||
digest = sha256_file(artifact)
|
||||
(root / "SHA256SUMS").write_text(f"{digest} {ARTIFACT_NAME}\n", encoding="ascii")
|
||||
metadata = {
|
||||
"schema_version": 1,
|
||||
"architecture": "aarch64",
|
||||
"kernel_release": KERNEL_RELEASE,
|
||||
"source_commit": SOURCE_COMMIT,
|
||||
"source_archive_sha256": SOURCE_ARCHIVE_SHA256,
|
||||
"artifact": ARTIFACT_NAME,
|
||||
"artifact_bytes": artifact.stat().st_size,
|
||||
"artifact_sha256": digest,
|
||||
"unpacked_file_bytes": sum(len(body) for body in files.values()),
|
||||
"regular_file_count": len(files),
|
||||
"module_file_count": 1,
|
||||
"provenance": "synthetic test payload",
|
||||
}
|
||||
(root / "METADATA.json").write_text(json.dumps(metadata), encoding="utf-8")
|
||||
return root
|
||||
|
||||
|
||||
def test_registered_kernel_and_source_dependencies_are_complete():
|
||||
kernel = PROJECT_ROOT / "发布更新相关/其他依赖/aarch64-kernel/6.1.31-matrix-axp313a1"
|
||||
source = PROJECT_ROOT / "发布更新相关/其他依赖/kernel-source/walnutpi-linux-6.1.31-30ff3fd5"
|
||||
if os.environ.get("MATRIX_SOURCE_ONLY_UPDATE_TESTS") == "1":
|
||||
if not kernel.exists() and not source.exists():
|
||||
pytest.skip("source-only update payload intentionally omits image build dependencies")
|
||||
info = verify_kernel_dependency(kernel)
|
||||
assert info.module_file_count == 3048
|
||||
assert info.regular_file_count == 3056
|
||||
assert info.unpacked_file_bytes == 160051604
|
||||
assert verify_source_dependency(source)["archive_bytes"] == 249095239
|
||||
registry = json.loads((PROJECT_ROOT / "发布更新相关/其他依赖/DEPENDENCIES.json").read_text(encoding="utf-8"))
|
||||
active = {item["id"]: item for item in registry["active"]}
|
||||
assert active["axp313a-kernel-runtime"]["path"] == "aarch64-kernel/6.1.31-matrix-axp313a1"
|
||||
assert active["walnutpi-linux-kernel-source"]["path"] == (
|
||||
"kernel-source/walnutpi-linux-6.1.31-30ff3fd5"
|
||||
)
|
||||
|
||||
|
||||
def test_synthetic_kernel_dependency_round_trip(tmp_path: Path):
|
||||
info = verify_kernel_dependency(_dependency(tmp_path / "kernel"))
|
||||
assert info.kernel_release == KERNEL_RELEASE
|
||||
assert info.module_file_count == 1
|
||||
assert REQUIRED_BOOT_FILES
|
||||
|
||||
|
||||
def test_kernel_dependency_rejects_outer_digest_damage(tmp_path: Path):
|
||||
dependency = _dependency(tmp_path / "kernel")
|
||||
artifact = dependency / ARTIFACT_NAME
|
||||
body = bytearray(artifact.read_bytes())
|
||||
body[-1] ^= 0xFF
|
||||
artifact.write_bytes(body)
|
||||
with pytest.raises(KernelArtifactError, match="SHA-256 mismatch"):
|
||||
verify_kernel_dependency(dependency)
|
||||
|
||||
|
||||
def test_kernel_dependency_rejects_wrong_release_marker(tmp_path: Path):
|
||||
dependency = _dependency(tmp_path / "kernel", release_marker="wrong-release")
|
||||
with pytest.raises(KernelArtifactError, match="release marker"):
|
||||
verify_kernel_dependency(dependency)
|
||||
|
||||
|
||||
def test_kernel_dependency_rejects_wrong_source_marker_and_missing_binary(tmp_path: Path):
|
||||
dependency = _dependency(tmp_path / "wrong", source_marker="0" * 40)
|
||||
with pytest.raises(KernelArtifactError, match="source commit marker"):
|
||||
verify_kernel_dependency(dependency)
|
||||
missing = _dependency(tmp_path / "missing")
|
||||
(missing / ARTIFACT_NAME).unlink()
|
||||
with pytest.raises(KernelArtifactError, match="binary is missing"):
|
||||
verify_kernel_dependency(missing)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("name", "kind", "message"),
|
||||
(
|
||||
("unsafe-link", tarfile.SYMTYPE, "link or special file"),
|
||||
("../escape", tarfile.REGTYPE, "unsafe archive path"),
|
||||
("secret.txt", tarfile.REGTYPE, "unowned file"),
|
||||
),
|
||||
)
|
||||
def test_kernel_dependency_rejects_links_and_unsafe_paths(
|
||||
tmp_path: Path, name: str, kind: bytes, message: str
|
||||
):
|
||||
extra = tarfile.TarInfo(name)
|
||||
extra.type = kind
|
||||
if kind == tarfile.SYMTYPE:
|
||||
extra.linkname = "boot/Image-matrix-axp313a1"
|
||||
dependency = _dependency(tmp_path / "kernel", extra_member=extra)
|
||||
with pytest.raises(KernelArtifactError, match=message):
|
||||
verify_kernel_dependency(dependency)
|
||||
|
||||
|
||||
def test_image_root_payload_stage_verify_and_corruption(tmp_path: Path):
|
||||
dependency = _dependency(tmp_path / "kernel")
|
||||
root = tmp_path / "root"
|
||||
root.mkdir()
|
||||
result = stage_payload(root, dependency, reserve_bytes=0)
|
||||
assert result["kernel_release"] == KERNEL_RELEASE
|
||||
assert verify_staged_payload(root, dependency) == result
|
||||
staged = root / "opt/matrix-image-bootstrap/axp313a" / ARTIFACT_NAME
|
||||
staged.write_bytes(staged.read_bytes() + b"damage")
|
||||
with pytest.raises(KernelArtifactError, match="SHA-256 mismatch"):
|
||||
verify_staged_payload(root, dependency)
|
||||
|
||||
|
||||
def test_image_root_payload_refuses_existing_target_and_insufficient_space(tmp_path: Path):
|
||||
dependency = _dependency(tmp_path / "kernel")
|
||||
existing_root = tmp_path / "existing"
|
||||
existing_root.mkdir()
|
||||
stage_payload(existing_root, dependency, reserve_bytes=0)
|
||||
with pytest.raises(ImageKernelPayloadError, match="already exists"):
|
||||
stage_payload(existing_root, dependency, reserve_bytes=0)
|
||||
small_root = tmp_path / "small"
|
||||
small_root.mkdir()
|
||||
with pytest.raises(ImageKernelPayloadError, match="lacks room"):
|
||||
stage_payload(small_root, dependency, reserve_bytes=10**30)
|
||||
|
||||
|
||||
def test_image_bootstrap_stages_application_and_kernel_together(tmp_path: Path):
|
||||
dependency = _dependency(tmp_path / "kernel")
|
||||
bundle = tmp_path / "MSCBOOT.TGZ"
|
||||
bundle.write_bytes(b"application-bundle")
|
||||
root = tmp_path / "root"
|
||||
root.mkdir()
|
||||
result = stage_bootstrap_payload(root, bundle, dependency, reserve_bytes=0)
|
||||
assert result["bundle_bytes"] == len(b"application-bundle")
|
||||
assert result["bundle_path"] == "opt/matrix-image-bootstrap/app/MSCBOOT.TGZ"
|
||||
assert verify_bootstrap_payload(root, bundle, dependency) == result
|
||||
assert (root / TARGET_RELATIVE / APP_RELATIVE).read_bytes() == bundle.read_bytes()
|
||||
|
||||
|
||||
def test_image_bootstrap_rejects_damage_existing_target_and_combined_space(tmp_path: Path):
|
||||
dependency = _dependency(tmp_path / "kernel")
|
||||
bundle = tmp_path / "MSCBOOT.TGZ"
|
||||
bundle.write_bytes(b"application-bundle")
|
||||
root = tmp_path / "root"
|
||||
root.mkdir()
|
||||
stage_bootstrap_payload(root, bundle, dependency, reserve_bytes=0)
|
||||
with pytest.raises(ImageBootstrapPayloadError, match="already exists"):
|
||||
stage_bootstrap_payload(root, bundle, dependency, reserve_bytes=0)
|
||||
staged = root / TARGET_RELATIVE / APP_RELATIVE
|
||||
staged.write_bytes(b"damaged")
|
||||
with pytest.raises(ImageBootstrapPayloadError, match="differs"):
|
||||
verify_bootstrap_payload(root, bundle, dependency)
|
||||
small_root = tmp_path / "small"
|
||||
small_root.mkdir()
|
||||
with pytest.raises(ImageBootstrapPayloadError, match="both offline payloads"):
|
||||
stage_bootstrap_payload(small_root, bundle, dependency, reserve_bytes=10**30)
|
||||
Reference in New Issue
Block a user