IQ.Pilot Prebuilt Release @ 27f668a

This commit is contained in:
IQ.Lvbs CI [bot]
2026-09-03 18:23:24 -05:00
commit b073c5182b
2554 changed files with 679696 additions and 0 deletions

View File

@@ -0,0 +1,284 @@
import os
import pathlib
import shutil
import signal
import stat
import subprocess
import tempfile
import time
import pytest
from iqpilot.common.params import Params
from iqpilot.system.manager.process import ManagerProcess
from iqpilot.selfdrive.test.helpers import processes_context
def run(args, **kwargs):
return subprocess.check_output(args, **kwargs)
def update_release(directory, name, version, agnos_version, release_notes):
(directory / "iqpilot" / "docs").mkdir(parents=True, exist_ok=True)
with open(directory / "iqpilot" / "docs" / "CHANGELOG.md", "w") as f:
f.write(release_notes)
(directory / "iqpilot" / "common").mkdir(parents=True, exist_ok=True)
with open(directory / "iqpilot" / "common" / "version.h", "w") as f:
f.write(f'#define COMMA_VERSION "{version}"')
launch_env = directory / "launch_env.sh"
with open(launch_env, "w") as f:
f.write(f'export AGNOS_VERSION="{agnos_version}"')
st = os.stat(launch_env)
os.chmod(launch_env, st.st_mode | stat.S_IEXEC)
test_symlink = directory / "test_symlink"
if not os.path.exists(str(test_symlink)):
os.symlink("iqpilot/common/version.h", test_symlink)
def get_version(path: str) -> str:
with open(os.path.join(path, "iqpilot", "common", "version.h")) as f:
return f.read().split('"')[1]
@pytest.mark.linux
@pytest.mark.slow
class BaseUpdateTest:
def setup_method(self):
self.tmpdir = tempfile.mkdtemp()
self.mock_update_path = pathlib.Path(self.tmpdir)
self.params = Params()
self.basedir = self.mock_update_path / "openpilot"
self.basedir.mkdir()
self.staging_root = self.mock_update_path / "safe_staging"
self.remote_dir = self.mock_update_path / "remote"
self.remote_dir.mkdir()
os.environ["UPDATER_STAGING_ROOT"] = str(self.staging_root)
os.environ["UPDATER_LOCK_FILE"] = str(self.mock_update_path / "safe_staging_overlay.lock")
self.MOCK_RELEASES = {
"release3": ("0.1.2", "1.2", "0.1.2 release notes"),
"master": ("0.1.3", "1.2", "0.1.3 release notes"),
}
@pytest.fixture(autouse=True)
def mock_basedir(self, mocker):
mocker.patch("iqpilot.common.basedir.BASEDIR", self.basedir)
mocker.patch("iqpilot.system.updated.updated.BASEDIR", str(self.basedir))
mocker.patch("iqpilot.system.updated.updated.STAGING_ROOT", str(self.staging_root))
mocker.patch("iqpilot.system.updated.updated.LOCK_FILE", str(self.mock_update_path / "safe_staging_overlay.lock"))
mocker.patch("iqpilot.system.updated.updated.OVERLAY_INIT", self.basedir / ".overlay_init")
def set_target_branch(self, branch):
self.params.put("UpdaterTargetBranch", branch)
def setup_basedir_release(self, release):
self.params = Params()
self.set_target_branch(release)
def update_remote_release(self, release):
raise NotImplementedError("")
def setup_remote_release(self, release):
raise NotImplementedError("")
def additional_context(self):
raise NotImplementedError("")
def teardown_method(self):
shutil.rmtree(self.tmpdir)
def wait_for_condition(self, condition, timeout=12):
start = time.monotonic()
while True:
waited = time.monotonic() - start
if condition():
print(f"waited {waited}s for condition ")
return waited
if waited > timeout:
raise TimeoutError("timed out waiting for condition")
time.sleep(1)
def _test_finalized_update(self, branch, version, agnos_version, release_notes):
assert get_version(str(self.basedir)) == version
assert os.access(str(self.basedir / "launch_env.sh"), os.X_OK)
with open(self.basedir / "test_symlink") as f:
assert version in f.read()
class ParamsBaseUpdateTest(BaseUpdateTest):
def _test_finalized_update(self, branch, version, agnos_version, release_notes):
assert self.params.get("UpdaterNewDescription").startswith(f"{version} / {branch}")
assert self.params.get("UpdaterNewReleaseNotes") == f"{release_notes}\n".encode()
super()._test_finalized_update(branch, version, agnos_version, release_notes)
def send_check_for_updates_signal(self, updated: ManagerProcess):
updated.signal(signal.SIGUSR1.value)
def send_download_signal(self, updated: ManagerProcess):
updated.signal(signal.SIGHUP.value)
def _test_params(self, branch, fetch_available, update_available):
assert self.params.get("UpdaterTargetBranch") == branch
assert self.params.get_bool("UpdaterFetchAvailable") == fetch_available
assert self.params.get_bool("UpdateAvailable") == update_available
def wait_for_idle(self):
self.wait_for_condition(lambda: self.params.get("UpdaterState") == "idle")
def wait_for_failed(self):
self.wait_for_condition(lambda: self.params.get("UpdateFailedCount") is not None and \
self.params.get("UpdateFailedCount") > 0)
def wait_for_fetch_available(self):
self.wait_for_condition(lambda: self.params.get_bool("UpdaterFetchAvailable"))
def wait_for_update_available(self):
self.wait_for_condition(lambda: self.params.get_bool("UpdateAvailable"))
def wait_for_reboot_requested(self):
self.wait_for_condition(lambda: self.params.get_bool("DoReboot"))
def test_no_update(self):
# Start on release3, ensure we don't fetch any updates
self.setup_remote_release("release3")
self.setup_basedir_release("release3")
with self.additional_context(), processes_context(["updated"]) as [updated]:
self._test_params("release3", False, False)
self.wait_for_idle()
self._test_params("release3", False, False)
self.send_check_for_updates_signal(updated)
self.wait_for_idle()
self._test_params("release3", False, False)
def test_new_release(self):
# Start on release3, simulate a release3 commit, ensure we fetch that update properly
self.setup_remote_release("release3")
self.setup_basedir_release("release3")
with self.additional_context(), processes_context(["updated"]) as [updated]:
self._test_params("release3", False, False)
self.wait_for_idle()
self._test_params("release3", False, False)
self.MOCK_RELEASES["release3"] = ("0.1.3", "1.2", "0.1.3 release notes")
self.update_remote_release("release3")
self.send_check_for_updates_signal(updated)
self.wait_for_fetch_available()
self._test_params("release3", True, False)
self.send_download_signal(updated)
self.wait_for_update_available()
self._test_params("release3", False, True)
self._test_finalized_update("release3", *self.MOCK_RELEASES["release3"])
def test_switch_branches(self):
# Start on release3, request to switch to master manually, ensure we switched
self.setup_remote_release("release3")
self.setup_remote_release("master")
self.setup_basedir_release("release3")
with self.additional_context(), processes_context(["updated"]) as [updated]:
self._test_params("release3", False, False)
self.wait_for_idle()
self._test_params("release3", False, False)
self.set_target_branch("master")
self.send_check_for_updates_signal(updated)
self.wait_for_fetch_available()
self._test_params("master", True, False)
self.send_download_signal(updated)
self.wait_for_update_available()
self._test_params("master", False, True)
self._test_finalized_update("master", *self.MOCK_RELEASES["master"])
def test_download_only_does_not_auto_install(self):
self.setup_remote_release("release3")
self.setup_basedir_release("release3")
self.params.put("UpdaterInstallMode", "download_only")
with self.additional_context(), processes_context(["updated"]) as [updated]:
self.wait_for_idle()
self.MOCK_RELEASES["release3"] = ("0.1.3", "1.2", "0.1.3 release notes")
self.update_remote_release("release3")
self.send_check_for_updates_signal(updated)
self.wait_for_fetch_available()
self.send_download_signal(updated)
self.wait_for_update_available()
assert not self.params.get_bool("DoReboot")
def test_download_and_install_auto_installs(self):
self.setup_remote_release("release3")
self.setup_basedir_release("release3")
self.params.put("UpdaterInstallMode", "download_and_install")
with self.additional_context(), processes_context(["updated"]) as [updated]:
self.wait_for_idle()
self.MOCK_RELEASES["release3"] = ("0.1.3", "1.2", "0.1.3 release notes")
self.update_remote_release("release3")
self.send_check_for_updates_signal(updated)
self.wait_for_fetch_available()
self.send_download_signal(updated)
self.wait_for_reboot_requested()
def test_agnos_update(self, mocker):
# Start on release3, push an update with an agnos change
self.setup_remote_release("release3")
self.setup_basedir_release("release3")
with self.additional_context(), processes_context(["updated"]) as [updated]:
mocker.patch("iqpilot.system.hardware.AGNOS", "True")
mocker.patch("iqpilot.system.hardware.tici.hardware.Tici.get_os_version", "1.2")
mocker.patch("iqpilot.system.hardware.tici.agnos.get_target_slot_number")
mocker.patch("iqpilot.system.hardware.tici.agnos.flash_agnos_update")
self._test_params("release3", False, False)
self.wait_for_idle()
self._test_params("release3", False, False)
self.MOCK_RELEASES["release3"] = ("0.1.3", "1.3", "0.1.3 release notes")
self.update_remote_release("release3")
self.send_check_for_updates_signal(updated)
self.wait_for_fetch_available()
self._test_params("release3", True, False)
self.send_download_signal(updated)
self.wait_for_update_available()
self._test_params("release3", False, True)
self._test_finalized_update("release3", *self.MOCK_RELEASES["release3"])

View File

@@ -0,0 +1,62 @@
from iqpilot.common.params import Params
from iqpilot.common.basedir import BASEDIR
from iqpilot.system.version import BuildMetadata, OpenpilotMetadata
from iqpilot.system.updated.updated import Updater, display_commit_date
def test_display_commit_date():
assert display_commit_date("'1786378834 2026-08-10 11:20:34 -0500'") == "2026-08-10 11:20:34"
assert display_commit_date("1786378834 2026-08-10 11:20:34 -0500") == "2026-08-10 11:20:34"
assert display_commit_date("'1786378834 2026-08-10 11:20:34 -0500'\n") == "2026-08-10 11:20:34"
assert display_commit_date("Jul 02") == "Jul 02"
def test_tici_branch_unchanged(mocker):
params = Params()
params.put("UpdaterTargetBranch", "master-dev")
mocker.patch("iqpilot.system.updated.updated.HARDWARE.get_device_type", return_value="tici")
try:
assert Updater().target_branch == "master-dev"
finally:
params.remove("UpdaterTargetBranch")
def test_non_tici_branch_unchanged(mocker):
params = Params()
params.put("UpdaterTargetBranch", "master-dev")
mocker.patch("iqpilot.system.updated.updated.HARDWARE.get_device_type", return_value="tizi")
try:
assert Updater().target_branch == "master-dev"
finally:
params.remove("UpdaterTargetBranch")
def test_non_git_baked_deployment_uses_build_metadata(mocker):
mocker.patch("iqpilot.system.updated.updated.has_git_repo", return_value=False)
mocker.patch("iqpilot.system.updated.updated.HARDWARE.get_device_type", return_value="tici")
mocker.patch(
"iqpilot.system.updated.updated.get_build_metadata",
return_value=BuildMetadata(
"release3",
OpenpilotMetadata(
version="1.2.3",
release_notes="notes",
git_commit="abcdef1234567890",
git_origin="github.com/IQLvbs/openpilot",
git_commit_date="Jul 02",
build_style="release",
is_dirty=False,
),
),
)
updater = Updater()
assert updater.git_mode is False
assert updater.get_branch(BASEDIR) == "release3"
assert updater.get_commit_hash() == "abcdef1234567890"
assert updater.target_branch == "release3"
assert updater.update_available is False
assert updater.update_ready is False

View File

@@ -0,0 +1,48 @@
import contextlib
import os
from iqpilot.system.updated.tests.test_base import ParamsBaseUpdateTest, run, update_release
from iqpilot.system.updated.updated import cleanup_stale_prebuilt_marker
class TestUpdateDGitStrategy(ParamsBaseUpdateTest):
def update_remote_release(self, release):
update_release(self.remote_dir, release, *self.MOCK_RELEASES[release])
run(["git", "add", "."], cwd=self.remote_dir)
run(["git", "commit", "-m", f"openpilot release {release}"], cwd=self.remote_dir)
def setup_remote_release(self, release):
run(["git", "init"], cwd=self.remote_dir)
run(["git", "config", "user.name", "IQPilot Test"], cwd=self.remote_dir)
run(["git", "config", "user.email", "test@iqpilot.local"], cwd=self.remote_dir)
run(["git", "checkout", "-b", release], cwd=self.remote_dir)
self.update_remote_release(release)
def setup_basedir_release(self, release):
super().setup_basedir_release(release)
run(["git", "clone", "-b", release, self.remote_dir, self.basedir])
@contextlib.contextmanager
def additional_context(self):
yield
def test_cleanup_stale_prebuilt_marker(self):
self.setup_remote_release("release3")
self.setup_basedir_release("release3")
prebuilt_path = os.path.join(self.basedir, "prebuilt")
with open(prebuilt_path, "w") as f:
f.write("")
cleanup_stale_prebuilt_marker(str(self.basedir), "release3")
assert not os.path.exists(prebuilt_path)
def test_keep_prebuilt_marker_for_prebuilt_branch(self):
self.setup_remote_release("release3")
self.setup_basedir_release("release3")
prebuilt_path = os.path.join(self.basedir, "prebuilt")
with open(prebuilt_path, "w") as f:
f.write("")
cleanup_stale_prebuilt_marker(str(self.basedir), "release3-prebuilt")
assert os.path.exists(prebuilt_path)