"""Host-only relocation and preservation checks; no network, devices or installs.""" import importlib.util from pathlib import Path import tempfile import unittest from unittest.mock import patch import local_env as env class LocalEnvironmentTests(unittest.TestCase): def test_workspace_location_uses_source_not_cwd(self): self.assertEqual(env.ROOT / '测试相关资料/如何测试/本机测试环境', env.HERE) module = env.ROOT / '移动端相关内容/安卓app/如何安卓测试/local_test_paths.py' spec = importlib.util.spec_from_file_location('test_paths', module) value = importlib.util.module_from_spec(spec) spec.loader.exec_module(value) self.assertEqual(value.SSH_KNOWN_HOSTS, env.LOCAL / 'private/qms-mobile-known_hosts') def test_copied_venv_is_rejected_without_overwriting(self): with tempfile.TemporaryDirectory(prefix='中文 relocation ') as tmp: local = Path(tmp) venv = local / 'ssh/venv' (venv / 'Scripts').mkdir(parents=True) (venv / 'Scripts/python.exe').touch() cfg = venv / 'pyvenv.cfg' cfg.write_text('command = python -m venv old-location\n', encoding='utf-8') with patch.object(env, 'LOCAL', local), patch.object(env, 'run', side_effect=AssertionError('unexpected install')): with self.assertRaisesRegex(RuntimeError, 'preserve/rename'): env.setup_python('ssh', offline=True) self.assertEqual(cfg.read_text(encoding='utf-8'), 'command = python -m venv old-location\n') def test_source_dependency_change_requires_a_new_lock(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) source = root / '移动端相关内容/安卓app/如何安卓测试/requirements-host.txt' source.parent.mkdir(parents=True) source.write_text('paramiko==5.0.0\n', encoding='utf-8') (root / 'requirements-ssh.lock.txt').write_text('paramiko==4.0.0 --hash=sha256:old\n', encoding='utf-8') with patch.object(env, 'ROOT', root), patch.object(env, 'HERE', root): with self.assertRaisesRegex(RuntimeError, 'source requirements changed'): env.validate_lock('ssh') def test_offline_missing_image_does_not_download(self): with tempfile.TemporaryDirectory(prefix='中文 offline ') as tmp: local = Path(tmp) args = type('Args', (), {'offline': True, 'image_archive': None})() with patch.object(env, 'LOCAL', local), patch.object(env, 'CONFIG', local / 'tools.local.json'), patch('urllib.request.urlopen', side_effect=AssertionError('network')): with self.assertRaisesRegex(RuntimeError, 'missing image'): env.setup_android(args) def test_stale_avd_can_refresh_paths_without_resetting_userdata(self): with tempfile.TemporaryDirectory(prefix='中文 avd ') as tmp: local = Path(tmp) directory = local / 'android/sdk/system-images/android-35/google_apis/x86_64' directory.mkdir(parents=True) avd = local / 'android/avd/qms-local-api35.avd' avd.mkdir(parents=True) (avd / '.qms-managed').touch() (avd / 'config.ini').write_text('hw.ramSize=3072\nimage.sysdir.1=old\n', encoding='utf-8') userdata = avd / 'userdata-qemu.img' userdata.write_bytes(b'preserve') args = type('Args', (), {'offline': True, 'image_archive': None})() with patch.object(env, 'LOCAL', local), patch.object(env, 'CONFIG', local / 'tools.local.json'), patch.object(env, 'check_android'): env.setup_android(args) config = (avd / 'config.ini').read_text(encoding='utf-8') self.assertIn('hw.ramSize=3072', config) self.assertIn(str(directory.resolve()), config) self.assertEqual(userdata.read_bytes(), b'preserve') def test_unmanaged_avd_is_never_overwritten(self): with tempfile.TemporaryDirectory() as tmp: local = Path(tmp) (local / 'android/sdk/system-images/android-35/google_apis/x86_64').mkdir(parents=True) avd = local / 'android/avd/qms-local-api35.avd' avd.mkdir(parents=True) cfg = avd / 'config.ini' cfg.write_text('owner=someone-else\n', encoding='utf-8') args = type('Args', (), {'offline': True, 'image_archive': None})() with patch.object(env, 'LOCAL', local), patch.object(env, 'CONFIG', local / 'tools.local.json'): with self.assertRaisesRegex(RuntimeError, 'unmanaged'): env.setup_android(args) self.assertEqual(cfg.read_text(encoding='utf-8'), 'owner=someone-else\n') @unittest.skipUnless(__import__('os').name == 'nt', 'Windows junction lifecycle') def test_emulator_session_restores_image_path_and_removes_only_alias(self): with tempfile.TemporaryDirectory(prefix='中文 session ') as tmp: local = Path(tmp) avd = local / 'android/avd/qms-local-api35.avd' avd.mkdir(parents=True) cfg = avd / 'config.ini' cfg.write_text('hw.ramSize = 3072\nimage.sysdir.1 = before\n', encoding='utf-8') (local / 'android/emulator-home').mkdir() with patch.object(env, 'LOCAL', local): with env.android_session() as (launch, image, alias): self.assertTrue(alias.is_junction()) self.assertTrue(str(image).isascii()) self.assertTrue(str(launch['ANDROID_AVD_HOME']).isascii()) cfg.write_text(f'hw.ramSize = 3072\nimage.sysdir.1 = {image}\n', encoding='utf-8') self.assertFalse(alias.exists()) self.assertEqual(env.ini_values(cfg)['image.sysdir.1'], str(env.image_dir().resolve())) self.assertEqual(env.ini_values(cfg)['hw.ramSize'], '3072') self.assertTrue(avd.is_dir()) if __name__ == '__main__': unittest.main()