"""Exercise the standalone PowerShell installer without modifying installed DSView.""" import os from pathlib import Path import shutil import subprocess import sys import tempfile import unittest ROOT = Path(__file__).resolve().parents[1] SHELL = shutil.which('powershell.exe') or shutil.which('pwsh') @unittest.skipUnless(os.name == 'nt' and SHELL, 'Windows PowerShell required') class InstallerTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory(prefix='dsview installer ') self.addCleanup(self.temp.cleanup) self.app = Path(self.temp.name)/'DSView test' self.app.mkdir() (self.app/'DSView.exe').write_bytes(b'fixture, not executable') (self.app/'decoders/common').mkdir(parents=True) (self.app/'decoders/common/__init__.py').write_text('# existing common package\n') (self.app/'decoders/common/keep.txt').write_text('unrelated') def run_installer(self, *extra, script=None): return subprocess.run([SHELL, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', str(script or ROOT/'install_decoders.ps1'), '-DsViewPath', str(self.app), *extra], capture_output=True, text=True) def test_install_import_and_update_backup(self): result = self.run_installer() self.assertEqual(result.returncode, 0, result.stdout + result.stderr) code = ''' import importlib, pathlib, sys, types root = pathlib.Path(sys.argv[1]) sys.path.insert(0, str(root)) stub = types.ModuleType('sigrokdecode') stub.Decoder = type('Decoder', (), {}) sys.modules['sigrokdecode'] = stub for name in ('gate_driver_timing', 'transistor_pair', 'set_uart', 'set_can', 'pm35_uart'): module = importlib.import_module(name + '.pd') assert module.Decoder.api_version == 3 core = importlib.import_module('common.setgui_decoders.gate_timing') pathlib.Path(core.__file__).resolve().relative_to(root) assert '1SP0635' in core.PROFILES ''' check = subprocess.run([sys.executable, '-I', '-c', code, str(self.app/'decoders')], cwd=self.app, capture_output=True, text=True) self.assertEqual(check.returncode, 0, check.stderr) target = self.app/'decoders/set_uart/pd.py' target.write_text('# old decoder\n') result = self.run_installer() self.assertEqual(result.returncode, 0, result.stdout + result.stderr) backups = list((self.app/'set-decoder-backups').glob('*/set_uart/pd.py')) self.assertEqual(len(backups), 1) self.assertEqual(backups[0].read_text(), '# old decoder\n') self.assertEqual(target.read_bytes(), (ROOT/'decoders/set_uart/pd.py').read_bytes()) self.assertEqual((self.app/'decoders/common/keep.txt').read_text(), 'unrelated') self.assertEqual((self.app/'decoders/common/__init__.py').read_text(), '# existing common package\n') for name in ('gate_timing', 'transistor_pair', 'set_uart', 'set_can', 'pm35_uart'): self.assertEqual((self.app/('decoders/common/setgui_decoders/'+name+'.py')).read_bytes(), (ROOT.parents[1]/('python/logic_analyzer/decoders/'+name+'.py')).read_bytes()) def test_check_only_does_not_install(self): result = self.run_installer('-CheckOnly') self.assertEqual(result.returncode, 0, result.stderr) self.assertFalse((self.app/'decoders/set_uart').exists()) self.assertFalse((self.app/'set-decoder-backups').exists()) def test_copy_failure_restores_previous_decoders(self): old = self.app/'decoders/gate_driver_timing/pd.py' old.parent.mkdir() old.write_text('# original\n') runner = Path(self.temp.name)/'inject-failure.ps1' runner.write_text(''' param([string]$Installer, [string]$Target) $ErrorActionPreference = 'Stop' $global:installerTestFailed = $false function Copy-Item { param([string]$LiteralPath, [string]$Destination, [switch]$Force) if (-not $global:installerTestFailed -and $Destination.Replace('\\', '/').EndsWith('/decoders/set_uart/pd.py')) { $global:installerTestFailed = $true throw 'Injected copy failure' } Microsoft.PowerShell.Management\\Copy-Item -LiteralPath $LiteralPath -Destination $Destination -Force:$Force } & $Installer -DsViewPath $Target ''', encoding='utf-8') result = subprocess.run([SHELL, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', str(runner), '-Installer', str(ROOT/'install_decoders.ps1'), '-Target', str(self.app)], capture_output=True) self.assertNotEqual(result.returncode, 0) self.assertEqual(old.read_text(), '# original\n') self.assertFalse((old.parent/'__init__.py').exists()) self.assertFalse((self.app/'decoders/set_uart/__init__.py').exists()) self.assertEqual(len(list((self.app/'set-decoder-backups').glob('*/gate_driver_timing/pd.py'))), 1, result.stderr) def test_incomplete_target_is_untouched(self): (self.app/'DSView.exe').unlink() result = self.run_installer() self.assertNotEqual(result.returncode, 0) self.assertFalse((self.app/'decoders/set_uart').exists()) def test_missing_source_is_detected_before_changes(self): source = Path(self.temp.name)/'empty-template/tools/dsview' source.mkdir(parents=True) shutil.copy2(ROOT/'install_decoders.ps1', source/'install_decoders.ps1') result = self.run_installer(script=source/'install_decoders.ps1') self.assertNotEqual(result.returncode, 0) self.assertFalse((self.app/'decoders/gate_driver_timing').exists()) self.assertFalse((self.app/'set-decoder-backups').exists()) if __name__ == '__main__': unittest.main()