269 lines
12 KiB
Plaintext
269 lines
12 KiB
Plaintext
#ifndef FixtureRoot
|
|
#error FixtureRoot must point at an isolated directory under the workspace tmp directory.
|
|
#endif
|
|
#define MigrationFixture
|
|
|
|
[Setup]
|
|
AppId=PebrelInstallerMigrationFixture
|
|
AppName=Pebrel Installer Migration Fixture
|
|
AppVersion=0.0.0
|
|
DefaultDirName={#FixtureRoot}
|
|
CreateAppDir=no
|
|
Uninstallable=no
|
|
PrivilegesRequired=lowest
|
|
OutputBaseFilename=installer-migration-fixture
|
|
Compression=none
|
|
SetupLogging=no
|
|
|
|
[CustomMessages]
|
|
OpenInPebrelWsl=Open in Pebrel (WSL)
|
|
WslMenuConflict=Preserved an edited or foreign submenu: %1.
|
|
WslMenuRegistrationFailed=Unable to register the WSL context submenu.
|
|
|
|
[Code]
|
|
#include "..\installer-migration.iss"
|
|
|
|
var
|
|
Checks: Integer;
|
|
|
|
function FixtureOpenFile(FileName: string; Access, Share: LongWord;
|
|
Security: Integer; Creation, Flags: LongWord; Template: Integer): LongWord;
|
|
external 'CreateFileW@kernel32.dll stdcall';
|
|
function FixtureCloseHandle(Handle: LongWord): Boolean;
|
|
external 'CloseHandle@kernel32.dll stdcall';
|
|
|
|
procedure Check(Condition: Boolean; Name: string);
|
|
begin
|
|
if not Condition then
|
|
RaiseException(Name);
|
|
Checks := Checks + 1;
|
|
end;
|
|
|
|
procedure WriteFixtureFile(Root, Relative: string);
|
|
var
|
|
FileName: string;
|
|
begin
|
|
FileName := AddBackslash(Root) + Relative;
|
|
Check(ForceDirectories(ExtractFileDir(FileName)), 'create fixture parent');
|
|
Check(SaveStringToFile(FileName, 'fixture data', False), 'write fixture file');
|
|
end;
|
|
|
|
procedure CheckPlanning;
|
|
begin
|
|
Check(SuggestedInstallDir('', 'D:\Programs\Pebrel') = 'D:\Programs\Pebrel', 'fresh install default');
|
|
Check(SuggestedInstallDir('D:\Program Files\Nebula Terminal', '') =
|
|
'D:\Program Files\Pebrel', 'legacy directory uses the same parent');
|
|
Check(SuggestedInstallDir('D:\apps\Custom Terminal', '') =
|
|
'D:\apps\Custom Terminal', 'custom install directory is preserved');
|
|
Check(SuggestedInstallDir('D:\apps\Pebrel', '') = 'D:\apps\Pebrel', 'Pebrel upgrades stay in place');
|
|
Check(SuggestedInstallDir('D:\apps\NEBULA TERMINAL\', '') =
|
|
'D:\apps\Pebrel', 'legacy directory comparison is case insensitive');
|
|
Check(PathContainsDirectory('D:\tools;"D:\Programs\Pebrel\";D:\other',
|
|
'd:\programs\pebrel'), 'PATH recognizes quotes, case and trailing slash');
|
|
Check(not PathContainsDirectory('D:\apps\Pebrel-old', 'D:\apps\Pebrel'), 'PATH requires a full entry');
|
|
Check(RemovePathDirectory('D:\tools;d:\apps\NEBULA TERMINAL\;D:\other',
|
|
'D:\apps\Nebula Terminal') = 'D:\tools;D:\other', 'PATH preserves unrelated entries');
|
|
Check(RemovePathDirectory('D:\apps\Nebula Terminal;D:\apps\Nebula Terminal',
|
|
'D:\apps\Nebula Terminal') = '', 'PATH removes duplicate owned entries');
|
|
Check(IsLegacyUninstaller('D:\apps\Nebula Terminal',
|
|
'D:\apps\Nebula Terminal\unins003.exe'), 'registered numbered uninstaller accepted');
|
|
Check(not IsLegacyUninstaller('D:\apps\Nebula Terminal',
|
|
'D:\other\unins000.exe'), 'uninstaller must belong to the previous installation');
|
|
Check(not IsLegacyUninstaller('D:\apps\Nebula Terminal',
|
|
'D:\apps\Nebula Terminal\other.exe'), 'arbitrary executable is not an uninstaller');
|
|
end;
|
|
|
|
procedure CheckPayloadMigration(Root: string);
|
|
var
|
|
OldDir, NewDir: string;
|
|
begin
|
|
OldDir := Root + '\move\Nebula Terminal';
|
|
NewDir := Root + '\move\Pebrel';
|
|
WriteFixtureFile(OldDir, 'nebula.exe');
|
|
WriteFixtureFile(OldDir, 'runtime\nebula-hook.exe');
|
|
WriteFixtureFile(OldDir, 'runtime\conpty.dll');
|
|
WriteFixtureFile(OldDir, 'docs\CHANGELOG.md');
|
|
WriteFixtureFile(OldDir, 'data\nebula.toml');
|
|
WriteFixtureFile(OldDir, 'notes.txt');
|
|
WriteFixtureFile(OldDir, 'docs\custom.md');
|
|
WriteFixtureFile(OldDir, 'unins003.exe');
|
|
WriteFixtureFile(OldDir, 'unins003.dat');
|
|
WriteFixtureFile(NewDir, 'pebrel.exe');
|
|
RemoveLegacyPayload(OldDir, NewDir, OldDir + '\unins003.exe');
|
|
Check(not FileExists(OldDir + '\nebula.exe'), 'old executable removed');
|
|
Check(not FileExists(OldDir + '\runtime\nebula-hook.exe'), 'old helper removed');
|
|
Check(not FileExists(OldDir + '\unins003.dat'), 'only registered old uninstall log removed');
|
|
Check(FileExists(OldDir + '\data\nebula.toml'), 'configuration preserved');
|
|
Check(FileExists(OldDir + '\notes.txt'), 'unknown root file preserved');
|
|
Check(FileExists(OldDir + '\docs\custom.md'), 'unknown child file preserved');
|
|
Check(FileExists(NewDir + '\pebrel.exe'), 'new executable preserved');
|
|
RemoveLegacyPayload(OldDir, NewDir, OldDir + '\unins003.exe');
|
|
Check(FileExists(OldDir + '\data\nebula.toml'), 'retry is idempotent');
|
|
|
|
OldDir := Root + '\custom';
|
|
WriteFixtureFile(OldDir, 'nebula.exe');
|
|
WriteFixtureFile(OldDir, 'runtime\nebula-hook.exe');
|
|
WriteFixtureFile(OldDir, 'pebrel.exe');
|
|
WriteFixtureFile(OldDir, 'runtime\pebrel-hook.exe');
|
|
WriteFixtureFile(OldDir, 'runtime\conpty.dll');
|
|
WriteFixtureFile(OldDir, 'docs\CHANGELOG.md');
|
|
WriteFixtureFile(OldDir, 'unins000.dat');
|
|
RemoveLegacyPayload(OldDir, OldDir, OldDir + '\unins000.exe');
|
|
Check(not FileExists(OldDir + '\nebula.exe'), 'in-place rename removes old executable');
|
|
Check(FileExists(OldDir + '\pebrel.exe'), 'in-place rename preserves new executable');
|
|
Check(FileExists(OldDir + '\runtime\pebrel-hook.exe'), 'in-place rename preserves new helper');
|
|
Check(FileExists(OldDir + '\runtime\conpty.dll'), 'in-place rename preserves shared runtime');
|
|
Check(FileExists(OldDir + '\docs\CHANGELOG.md'), 'in-place rename preserves current documentation');
|
|
Check(FileExists(OldDir + '\unins000.dat'), 'in-place upgrade keeps the appended uninstall log');
|
|
end;
|
|
|
|
procedure CheckFailures(Root: string);
|
|
var
|
|
OldDir, Ignored: string;
|
|
Failed: Boolean;
|
|
Handle: LongWord;
|
|
begin
|
|
OldDir := Root + '\locked';
|
|
WriteFixtureFile(OldDir, 'nebula.exe');
|
|
Handle := FixtureOpenFile(OldDir + '\nebula.exe', $80000000, 0, 0, 3, 0, 0);
|
|
Check(Handle <> $FFFFFFFF, 'lock fixture executable');
|
|
Failed := False;
|
|
try
|
|
try
|
|
RemoveLegacyPayload(OldDir, Root + '\new', '');
|
|
except
|
|
Failed := True;
|
|
end;
|
|
finally
|
|
FixtureCloseHandle(Handle);
|
|
end;
|
|
Check(Failed, 'locked old executable produces an error');
|
|
Check(FileExists(OldDir + '\nebula.exe'), 'locked file remains intact');
|
|
RemoveLegacyPayload(OldDir, Root + '\new', '');
|
|
Check(not FileExists(OldDir + '\nebula.exe'), 'retry succeeds after unlocking');
|
|
|
|
Failed := False;
|
|
try
|
|
Ignored := SafePayloadPath(Root, '..\outside.exe');
|
|
except
|
|
Failed := True;
|
|
end;
|
|
Check(Failed, 'relative path cannot escape the old installation');
|
|
Failed := False;
|
|
try
|
|
RemoveLegacyFile(Root + '\linked-install', 'nebula.exe');
|
|
except
|
|
Failed := True;
|
|
end;
|
|
Check(Failed, 'linked directory is rejected');
|
|
Check(FileExists(Root + '\link-target\nebula.exe'), 'linked target file is preserved');
|
|
Check(IsExecutableRunning(ExpandConstant('{srcexe}')), 'running executable is detected without termination');
|
|
Check(not IsExecutableRunning(Root + '\not-running.exe'), 'absent executable does not block installation');
|
|
end;
|
|
|
|
procedure CheckShortcuts(Root: string);
|
|
var
|
|
Shell, Shortcut: Variant;
|
|
OldExe, NewExe, OldLink, NewLink: string;
|
|
begin
|
|
OldExe := Root + '\shortcuts\nebula.exe';
|
|
NewExe := Root + '\shortcuts\pebrel.exe';
|
|
OldLink := Root + '\shortcuts\Nebula Terminal.lnk';
|
|
NewLink := Root + '\shortcuts\Pebrel.lnk';
|
|
WriteFixtureFile(Root, 'shortcuts\nebula.exe');
|
|
WriteFixtureFile(Root, 'shortcuts\pebrel.exe');
|
|
Shell := CreateOleObject('WScript.Shell');
|
|
Shortcut := Shell.CreateShortcut(OldLink);
|
|
Shortcut.TargetPath := OldExe;
|
|
Shortcut.Save;
|
|
Shortcut := Shell.CreateShortcut(NewLink);
|
|
Shortcut.TargetPath := NewExe;
|
|
Shortcut.Save;
|
|
RemoveOwnedShortcut(OldLink, OldExe);
|
|
RemoveOwnedShortcut(NewLink, OldExe);
|
|
Check(not FileExists(OldLink), 'shortcut pointing at the old program is removed');
|
|
Check(FileExists(NewLink), 'shortcut pointing at another program is preserved');
|
|
end;
|
|
|
|
procedure CheckContextMenus(Root: string);
|
|
var
|
|
RegistryRoot, ShellRoot, Executable, Value, Menu, ForeignCommand: string;
|
|
Distros, Names: TArrayOfString;
|
|
Index: Integer;
|
|
Failed: Boolean;
|
|
begin
|
|
RegistryRoot := 'Software\PebrelTestFixtures\' + ExtractFileName(Root);
|
|
Executable := Root + '\Program Files\pebrel.exe';
|
|
ForeignCommand := '"D:\other\pebrel.exe" --gpui --shell "wsl:Other" --working-directory "%1"';
|
|
try
|
|
for Index := 0 to 1 do begin
|
|
ShellRoot := RegistryRoot + '\root' + IntToStr(Index);
|
|
Menu := ShellRoot + '\PebrelWslMenu';
|
|
Check(RegWriteStringValue(HKCU, ShellRoot + '\Pebrel\command', '', 'ordinary-open'), 'seed ordinary entry');
|
|
Check(RegWriteStringValue(HKCU, ShellRoot + '\PebrelWslForeign\command', '', ForeignCommand), 'seed foreign entry');
|
|
Check(RegWriteStringValue(HKCU, ShellRoot + '\PebrelWsl0\command', '',
|
|
'"' + Executable + '" --gpui --shell "wsl:Old" --working-directory "%1"'), 'seed old flat entry');
|
|
SetArrayLength(Distros, 3);
|
|
Distros[0] := 'Debian';
|
|
Distros[1] := 'Ubuntu Test';
|
|
Distros[2] := '开发环境';
|
|
if Index = 0 then Value := '%1' else Value := '%V';
|
|
RegisterWslContextMenuAt(ShellRoot, Executable, Value, Distros);
|
|
Check(not RegKeyExists(HKCU, ShellRoot + '\PebrelWsl0'), 'migrate old flat entry');
|
|
Check(RegGetSubkeyNames(HKCU, ShellRoot, Names) and (GetArrayLength(Names) = 3), 'only normal, cascade and foreign roots remain');
|
|
Check(RegGetSubkeyNames(HKCU, Menu + '\shell', Names) and (GetArrayLength(Names) = 3), 'three distros live inside cascade');
|
|
Check(RegQueryStringValue(HKCU, Menu, 'SubCommands', Value) and (Value = ''), 'enable static cascade');
|
|
Check(RegQueryStringValue(HKCU, Menu + '\shell\PebrelWsl1\command', '', Value), 'child command exists');
|
|
if Index = 0 then
|
|
Check(Value = '"' + Executable + '" --gpui --shell "wsl:Ubuntu Test" --working-directory "%1"', 'selected directory argv')
|
|
else
|
|
Check(Value = '"' + Executable + '" --gpui --shell "wsl:Ubuntu Test" --working-directory "%V"', 'background directory argv');
|
|
Check(RegQueryStringValue(HKCU, Menu + '\shell\PebrelWsl2', 'MUIVerb', Value) and (Value = '开发环境'), 'Unicode label retained');
|
|
RegisterWslContextMenuAt(ShellRoot, Executable, '%1', Distros);
|
|
Check(RegGetSubkeyNames(HKCU, Menu + '\shell', Names) and (GetArrayLength(Names) = 3), 'repeat registration has no duplicates');
|
|
SetArrayLength(Distros, 1);
|
|
RegisterWslContextMenuAt(ShellRoot, Executable, '%1', Distros);
|
|
Check(RegGetSubkeyNames(HKCU, Menu + '\shell', Names) and (GetArrayLength(Names) = 1), 'removed distros leave no stale entries');
|
|
Check(RegWriteStringValue(HKCU, Menu + '\shell\Custom\command', '', ForeignCommand), 'seed edited submenu');
|
|
Failed := False;
|
|
try
|
|
RegisterWslContextMenuAt(ShellRoot, Executable, '%1', Distros);
|
|
except
|
|
Failed := True;
|
|
end;
|
|
Check(Failed and RegKeyExists(HKCU, Menu + '\shell\Custom'), 'edited submenu is not overwritten');
|
|
RemoveOwnedWslContextMenusAt(ShellRoot, Executable);
|
|
Check(RegKeyExists(HKCU, Menu + '\shell\Custom'), 'uninstall preserves edited subtree');
|
|
Check(RegDeleteKeyIncludingSubkeys(HKCU, Menu + '\shell\Custom'), 'remove fixture edit');
|
|
RemoveOwnedWslContextMenusAt(ShellRoot, 'D:\other\install.exe');
|
|
Check(RegKeyExists(HKCU, Menu), 'other installation cannot remove this menu');
|
|
SetArrayLength(Distros, 0);
|
|
RegisterWslContextMenuAt(ShellRoot, Executable, '%1', Distros);
|
|
Check(not RegKeyExists(HKCU, Menu), 'zero distros removes empty cascade');
|
|
Check(RegQueryStringValue(HKCU, ShellRoot + '\PebrelWslForeign\command', '', Value) and (Value = ForeignCommand), 'foreign entry preserved');
|
|
Check(RegQueryStringValue(HKCU, ShellRoot + '\Pebrel\command', '', Value) and (Value = 'ordinary-open'), 'ordinary entry preserved');
|
|
end;
|
|
finally
|
|
RegDeleteKeyIncludingSubkeys(HKCU, RegistryRoot);
|
|
end;
|
|
end;
|
|
|
|
function InitializeSetup: Boolean;
|
|
var
|
|
Root, Report: string;
|
|
begin
|
|
Root := ExpandConstant('{#FixtureRoot}');
|
|
try
|
|
CheckPlanning;
|
|
CheckPayloadMigration(Root);
|
|
CheckFailures(Root);
|
|
CheckShortcuts(Root);
|
|
CheckContextMenus(Root);
|
|
Report := 'PASS: ' + IntToStr(Checks) + ' migration checks';
|
|
except
|
|
Report := 'FAIL: ' + GetExceptionMessage;
|
|
end;
|
|
SaveStringToFile(Root + '\result.txt', Report, False);
|
|
Result := False;
|
|
end;
|