144 lines
8.5 KiB
PowerShell
144 lines
8.5 KiB
PowerShell
param([string]$BuildDirectory = (Join-Path $PSScriptRoot '../build/staged'))
|
|
$ErrorActionPreference = 'Stop'
|
|
if (Get-Process KSwitcher,'KSwitcher.Config' -ErrorAction SilentlyContinue) {
|
|
throw 'Close KSwitcher and its configurator before this isolated process test.'
|
|
}
|
|
Add-Type @'
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
public static class ConfigurationTestUi {
|
|
[StructLayout(LayoutKind.Explicit, Size=40)] public struct Input {
|
|
[FieldOffset(0)] public uint type;
|
|
[FieldOffset(8)] public ushort key;
|
|
[FieldOffset(12)] public uint flags;
|
|
}
|
|
[DllImport("user32.dll")] public static extern uint SendInput(uint count,Input[] input,int size);
|
|
public static uint PressF24() { return SendInput(2,new Input[]{new Input{type=1,key=0x87},new Input{type=1,key=0x87,flags=2}},40); }
|
|
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
|
[DllImport("user32.dll", EntryPoint="SendMessageW", CharSet=CharSet.Unicode)] public static extern IntPtr GetText(IntPtr w,uint msg,IntPtr count,System.Text.StringBuilder text);
|
|
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern IntPtr FindWindow(string cls,string title);
|
|
[DllImport("user32.dll")] public static extern IntPtr GetDlgItem(IntPtr w,int id);
|
|
[DllImport("user32.dll")] public static extern bool PostMessage(IntPtr w,uint msg,IntPtr wp,IntPtr lp);
|
|
[DllImport("user32.dll")] public static extern IntPtr SendMessage(IntPtr w,uint msg,IntPtr wp,IntPtr lp);
|
|
[DllImport("user32.dll", EntryPoint="SendMessageW", CharSet=CharSet.Unicode)] public static extern IntPtr SetText(IntPtr w,uint msg,IntPtr wp,string text);
|
|
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr w,out uint pid);
|
|
[DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();
|
|
[DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a,uint b,bool attach);
|
|
[DllImport("user32.dll")] public static extern IntPtr SetFocus(IntPtr w);
|
|
public static bool FocusControl(IntPtr w) {
|
|
uint pid; uint target=GetWindowThreadProcessId(w,out pid), current=GetCurrentThreadId();
|
|
if(!AttachThreadInput(current,target,true)) return false;
|
|
try { SetFocus(w); return true; } finally { AttachThreadInput(current,target,false); }
|
|
}
|
|
}
|
|
'@
|
|
$root=Join-Path ([IO.Path]::GetTempPath()) ('KSwitcher-config-tests-'+[guid]::NewGuid().ToString('N'))
|
|
$processes=[Collections.Generic.List[Diagnostics.Process]]::new()
|
|
$checks=0
|
|
function Assert($ok,$label) { if (!$ok) { throw $label }; $script:checks++ }
|
|
function Window($class) {
|
|
for($i=0;$i -lt 100;$i++) {
|
|
$title=switch($class){'KSwitcher.Main'{'KSwitcher'};'KSwitcher.Settings'{'KSwitcher settings'};'KSwitcher.Snippets'{'Text snippets'}}
|
|
$window=[ConfigurationTestUi]::FindWindow($class,$title)
|
|
if($window -ne [IntPtr]::Zero){return $window}
|
|
Start-Sleep -Milliseconds 50
|
|
}
|
|
foreach($process in $processes){$process.Refresh(); Write-Host ("Diagnostic process {0}: exited={1} title={2}" -f $process.Id,$process.HasExited,$process.MainWindowTitle)}
|
|
throw "Window did not appear: $class"
|
|
}
|
|
function Launch($file,$argument) {
|
|
$options=@{FilePath=(Join-Path $root $file);PassThru=$true;WindowStyle='Hidden'}
|
|
if($argument){$options.ArgumentList=$argument}
|
|
$process=Start-Process @options
|
|
$processes.Add($process)
|
|
return $process
|
|
}
|
|
function Click($window,$id) {
|
|
$null=[ConfigurationTestUi]::PostMessage($window,0x111,[IntPtr]$id,[IntPtr]::Zero)
|
|
}
|
|
try {
|
|
New-Item -ItemType Directory -Path (Join-Path $root 'data') -Force | Out-Null
|
|
foreach($file in @('KSwitcher.exe','KSwitcher.Config.exe')) {
|
|
Copy-Item -LiteralPath (Join-Path $BuildDirectory $file) -Destination $root
|
|
}
|
|
# No clipboard history capture, auto-correction, autostart or user data in the fixture.
|
|
$settingsPath=Join-Path $root 'data/settings.json'
|
|
@{format='kswitcher-settings';schemaVersion=1;historyEnabled=$false;autoCorrectLayout=$false;writeLog=$false} |
|
|
ConvertTo-Json | Set-Content -LiteralPath $settingsPath -Encoding utf8
|
|
$resident=Launch 'KSwitcher.exe' ''
|
|
$main=Window 'KSwitcher.Main'
|
|
$config=Launch 'KSwitcher.Config.exe' ''
|
|
$settings=Window 'KSwitcher.Settings'
|
|
[uint32]$ownerId=0
|
|
$null=[ConfigurationTestUi]::GetWindowThreadProcessId($settings,[ref]$ownerId)
|
|
Assert ($ownerId -eq $config.Id -and $ownerId -ne $resident.Id) 'settings live in the separate process'
|
|
$second=Launch 'KSwitcher.Config.exe' ''
|
|
Assert ($second.WaitForExit(6000) -and $second.ExitCode -eq 0) 'second launch forwards to existing instance'
|
|
Assert ((Window 'KSwitcher.Settings') -eq $settings) 'existing settings window reused'
|
|
# Only inject into the isolated fixture window, never another foreground app.
|
|
Assert ([ConfigurationTestUi]::GetForegroundWindow() -eq $settings) 'fixture owns foreground before shortcut capture test'
|
|
$field=[ConfigurationTestUi]::GetDlgItem($settings,210)
|
|
Assert ([ConfigurationTestUi]::FocusControl($field)) 'shortcut field receives actual focus'
|
|
Assert ([ConfigurationTestUi]::PressF24() -eq 2) 'test F24 events accepted'
|
|
$text=[Text.StringBuilder]::new(128)
|
|
for($i=0;$i -lt 100;$i++) {
|
|
$null=$text.Clear()
|
|
$null=[ConfigurationTestUi]::GetText($field,0x0d,[IntPtr]128,$text)
|
|
if($text.ToString() -eq 'F24'){break}
|
|
Start-Sleep -Milliseconds 20
|
|
}
|
|
Assert ($text.ToString() -eq 'F24') 'resident hook forwards shortcut capture to configurator'
|
|
$null=[ConfigurationTestUi]::SetText([ConfigurationTestUi]::GetDlgItem($settings,240),0x0c,[IntPtr]::Zero,'321')
|
|
Click $settings 1
|
|
Assert ($config.WaitForExit(6000) -and $config.ExitCode -eq 0) 'save exits configurator'
|
|
Assert ((Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json).maxItems -eq 321) 'save committed through resident process'
|
|
Assert ((Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json).toggleHotkey -eq 'F24') 'captured shortcut persisted by resident'
|
|
$resident.Refresh(); Assert (!$resident.HasExited) 'resident survives settings save'
|
|
$config=Launch 'KSwitcher.Config.exe' ''
|
|
$settings=Window 'KSwitcher.Settings'
|
|
$null=[ConfigurationTestUi]::SetText([ConfigurationTestUi]::GetDlgItem($settings,240),0x0c,[IntPtr]::Zero,'456')
|
|
Click $settings 2
|
|
Assert ($config.WaitForExit(6000)) 'cancel exits configurator'
|
|
Assert ((Get-Content -LiteralPath $settingsPath -Raw | ConvertFrom-Json).maxItems -eq 321) 'cancel preserves saved settings'
|
|
$config=Launch 'KSwitcher.Config.exe' '--snippets'
|
|
$snippets=Window 'KSwitcher.Snippets'
|
|
$second=Launch 'KSwitcher.Config.exe' ''
|
|
Assert ($second.WaitForExit(6000) -and $second.ExitCode -eq 0) 'settings and snippets share a single process'
|
|
$settings=Window 'KSwitcher.Settings'
|
|
Click $settings 2
|
|
Start-Sleep -Milliseconds 100
|
|
$config.Refresh(); Assert (!$config.HasExited) 'closing settings keeps open snippets alive'
|
|
Click $snippets 2
|
|
Assert ($config.WaitForExit(6000)) 'closing last window exits configurator'
|
|
$config=Launch 'KSwitcher.Config.exe' ''
|
|
$null=Window 'KSwitcher.Settings'
|
|
$null=[ConfigurationTestUi]::PostMessage($main,0x10,[IntPtr]::Zero,[IntPtr]::Zero)
|
|
Assert ($resident.WaitForExit(10000)) 'resident shuts down normally'
|
|
Assert ($config.WaitForExit(6000)) 'resident exit closes configurator'
|
|
$config=Launch 'KSwitcher.Config.exe' ''
|
|
$main=Window 'KSwitcher.Main'
|
|
[uint32]$bootstrappedId=0
|
|
$null=[ConfigurationTestUi]::GetWindowThreadProcessId($main,[ref]$bootstrappedId)
|
|
$bootstrapped=Get-Process -Id $bootstrappedId
|
|
$processes.Add($bootstrapped)
|
|
$settings=Window 'KSwitcher.Settings'
|
|
Assert ($bootstrappedId -ne $resident.Id) 'standalone configurator starts its resident sibling'
|
|
Click $settings 2
|
|
Assert ($config.WaitForExit(6000)) 'standalone configurator exits after cancel'
|
|
$null=[ConfigurationTestUi]::PostMessage($main,0x10,[IntPtr]::Zero,[IntPtr]::Zero)
|
|
Assert ($bootstrapped.WaitForExit(10000)) 'bootstrapped resident shuts down normally'
|
|
"Passed $checks configurator process checks"
|
|
} finally {
|
|
foreach($process in $processes) {
|
|
$process.Refresh()
|
|
if(!$process.HasExited) { Stop-Process -Id $process.Id -Force }
|
|
$process.Dispose()
|
|
}
|
|
# The generated fixture is the only recursive deletion target.
|
|
$resolved=[IO.Path]::GetFullPath($root)
|
|
$temp=[IO.Path]::GetFullPath([IO.Path]::GetTempPath())
|
|
if(!$resolved.StartsWith($temp,[StringComparison]::OrdinalIgnoreCase) -or
|
|
[IO.Path]::GetFileName($resolved) -notlike 'KSwitcher-config-tests-*') { throw 'Unexpected fixture path' }
|
|
Remove-Item -LiteralPath $resolved -Recurse -Force
|
|
}
|