updated: basic e2e update tests (#31742)
	
		
	
				
					
				
			* e2e update test
* that too
* fix
* fix
* fix running in docker
* don't think GHA will work
* also test switching branches
* it's a test
* lets not delete that yet
* comment
* space
old-commit-hash: ac77129041
			
			
				pull/32199/head
			
			
		
							parent
							
								
									e991495530
								
							
						
					
					
						commit
						4166525f03
					
				
				 2 changed files with 173 additions and 0 deletions
			
			
		| @ -0,0 +1,172 @@ | |||||||
|  | #!/usr/bin/env python3 | ||||||
|  | import os | ||||||
|  | import pathlib | ||||||
|  | import shutil | ||||||
|  | import signal | ||||||
|  | import subprocess | ||||||
|  | import tempfile | ||||||
|  | import time | ||||||
|  | import unittest | ||||||
|  | from unittest import mock | ||||||
|  | 
 | ||||||
|  | import pytest | ||||||
|  | from openpilot.selfdrive.manager.process import ManagerProcess | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | from openpilot.selfdrive.test.helpers import processes_context | ||||||
|  | from openpilot.common.params import Params | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def run(args, **kwargs): | ||||||
|  |   return subprocess.run(args, **kwargs, check=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def update_release(directory, name, version, release_notes): | ||||||
|  |   with open(directory / "RELEASES.md", "w") as f: | ||||||
|  |     f.write(release_notes) | ||||||
|  | 
 | ||||||
|  |   (directory / "common").mkdir(exist_ok=True) | ||||||
|  | 
 | ||||||
|  |   with open(directory / "common" / "version.h", "w") as f: | ||||||
|  |     f.write(f'#define COMMA_VERSION "{version}"') | ||||||
|  | 
 | ||||||
|  |   run(["git", "add", "."], cwd=directory) | ||||||
|  |   run(["git", "commit", "-m", f"openpilot release {version}"], cwd=directory) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.mark.slow # TODO: can we test overlayfs in GHA? | ||||||
|  | class TestUpdateD(unittest.TestCase): | ||||||
|  |   def setUp(self): | ||||||
|  |     self.tmpdir = tempfile.mkdtemp() | ||||||
|  | 
 | ||||||
|  |     run(["sudo", "mount", "-t", "tmpfs", "tmpfs", self.tmpdir]) # overlayfs doesn't work inside of docker unless this is a tmpfs | ||||||
|  | 
 | ||||||
|  |     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.staging_root.mkdir() | ||||||
|  | 
 | ||||||
|  |     self.remote_dir = self.mock_update_path / "remote" | ||||||
|  |     self.remote_dir.mkdir() | ||||||
|  | 
 | ||||||
|  |     mock.patch("openpilot.common.basedir.BASEDIR", self.basedir).start() | ||||||
|  | 
 | ||||||
|  |     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", "0.1.2 release notes"), | ||||||
|  |       "master": ("0.1.3", "0.1.3 release notes"), | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |   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) | ||||||
|  |     run(["git", "clone", "-b", release, self.remote_dir, self.basedir]) | ||||||
|  | 
 | ||||||
|  |   def update_remote_release(self, release): | ||||||
|  |     update_release(self.remote_dir, release, *self.MOCK_RELEASES[release]) | ||||||
|  | 
 | ||||||
|  |   def setup_remote_release(self, release): | ||||||
|  |     run(["git", "init"], cwd=self.remote_dir) | ||||||
|  |     run(["git", "checkout", "-b", release], cwd=self.remote_dir) | ||||||
|  |     self.update_remote_release(release) | ||||||
|  | 
 | ||||||
|  |   def tearDown(self): | ||||||
|  |     mock.patch.stopall() | ||||||
|  |     run(["sudo", "umount", "-l", str(self.staging_root / "merged")]) | ||||||
|  |     run(["sudo", "umount", "-l", self.tmpdir]) | ||||||
|  |     shutil.rmtree(self.tmpdir) | ||||||
|  | 
 | ||||||
|  |   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): | ||||||
|  |     self.assertEqual(self.params.get("UpdaterTargetBranch", encoding="utf-8"), branch) | ||||||
|  |     self.assertEqual(self.params.get_bool("UpdaterFetchAvailable"), fetch_available) | ||||||
|  |     self.assertEqual(self.params.get_bool("UpdateAvailable"), update_available) | ||||||
|  | 
 | ||||||
|  |   def _test_update_params(self, branch, version, release_notes): | ||||||
|  |     self.assertTrue(self.params.get("UpdaterNewDescription", encoding="utf-8").startswith(f"{version} / {branch}")) | ||||||
|  |     self.assertEqual(self.params.get("UpdaterNewReleaseNotes", encoding="utf-8"), f"<p>{release_notes}</p>\n") | ||||||
|  | 
 | ||||||
|  |   def wait_for_idle(self, timeout=5, min_wait_time=2): | ||||||
|  |     start = time.monotonic() | ||||||
|  |     time.sleep(min_wait_time) | ||||||
|  | 
 | ||||||
|  |     while True: | ||||||
|  |       waited = time.monotonic() - start | ||||||
|  |       if self.params.get("UpdaterState", encoding="utf-8") == "idle": | ||||||
|  |         print(f"waited {waited}s for idle") | ||||||
|  |         break | ||||||
|  | 
 | ||||||
|  |       if waited > timeout: | ||||||
|  |         raise TimeoutError("timed out waiting for idle") | ||||||
|  | 
 | ||||||
|  |       time.sleep(1) | ||||||
|  | 
 | ||||||
|  |   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 processes_context(["updated"]) as [updated]: | ||||||
|  |       self._test_params("release3", False, False) | ||||||
|  |       time.sleep(1) | ||||||
|  |       self._test_params("release3", False, False) | ||||||
|  | 
 | ||||||
|  |       self.MOCK_RELEASES["release3"] = ("0.1.3", "0.1.3 release notes") | ||||||
|  |       self.update_remote_release("release3") | ||||||
|  | 
 | ||||||
|  |       self.send_check_for_updates_signal(updated) | ||||||
|  | 
 | ||||||
|  |       self.wait_for_idle() | ||||||
|  | 
 | ||||||
|  |       self._test_params("release3", True, False) | ||||||
|  | 
 | ||||||
|  |       self.send_download_signal(updated) | ||||||
|  | 
 | ||||||
|  |       self.wait_for_idle() | ||||||
|  | 
 | ||||||
|  |       self._test_params("release3", False, True) | ||||||
|  |       self._test_update_params("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 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_idle() | ||||||
|  | 
 | ||||||
|  |       self._test_params("master", True, False) | ||||||
|  | 
 | ||||||
|  |       self.send_download_signal(updated) | ||||||
|  | 
 | ||||||
|  |       self.wait_for_idle() | ||||||
|  | 
 | ||||||
|  |       self._test_params("master", False, True) | ||||||
|  |       self._test_update_params("master", *self.MOCK_RELEASES["master"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |   unittest.main() | ||||||
					Loading…
					
					
				
		Reference in new issue