test_stub.nsi (19208B)
1 Unicode true 2 3 ; Tests use the silent install feature to bypass message box prompts. 4 SilentInstall silent 5 6 ; This is the executable that will be run by an xpcshell test 7 OutFile "test_stub_installer.exe" 8 9 10 ; The executable will write output to this file, and the xpcshell test 11 ; will read the contents of the file to determine whether the tests passed 12 !define TEST_OUTPUT_FILENAME .\test_installer_output.txt 13 14 15 !include "stub_shared_defs.nsh" 16 17 ; Some variables used only by tests and mocks 18 Var Stdout 19 Var FailureMessage 20 Var TestBreakpointNumber 21 22 ; For building the test exectuable, this version of the IsTestBreakpointSet macro 23 ; checks the value of the TestBreakpointNumber. For real installer executables, 24 ; this macro is empty, and does nothing. (See stub.nsi for the that version.) 25 !macro IsTestBreakpointSet breakpointNumber 26 ${If} ${breakpointNumber} == $TestBreakpointNumber 27 Return 28 ${EndIf} 29 !macroend 30 31 !include "LogicLib.nsh" 32 !include "FileFunc.nsh" 33 !include "TextFunc.nsh" 34 !include "WinVer.nsh" 35 !include "WordFunc.nsh" 36 !include "control_utils.nsh" 37 38 Function ExitProcess 39 FunctionEnd 40 41 Function AttachConsole 42 ; NSIS doesn't attach a console to the installer, so we'll do that now 43 System::Call 'kernel32::AttachConsole(i -1)i.r0' 44 ${If} $0 != 0 45 ; Windows STD_OUTPUT_HANDLE is defined as -11 46 System::Call 'kernel32::GetStdHandle(i -11)i.r0' 47 Push $0 48 ${Else} 49 ; If there's no console, exit with a nonzero exit code 50 System::Call 'kernel32::ExitProcess(i 65536)' 51 ${EndIf} 52 FunctionEnd 53 54 ; ${AtLeastWin10} is a macro provided by WinVer.nsh that can act as the predicate of a LogicLib 55 ; control statement. This macro redefinition makes this test mockable 56 Var MockAtLeastWin10 57 !define /redef AtLeastWin10 `$MockAtLeastWin10 IsMockedAsTrue ""` 58 59 ; See stub.nsi for the real version of CheckCpuSupportsSSE 60 Var MockCpuHasSSE 61 Function CheckCpuSupportsSSE 62 StrCpy $CpuSupportsSSE "$MockCpuHasSSE" 63 FunctionEnd 64 65 ; See stub.nsi for the real version of CanWrite 66 Var MockCanWrite 67 Function CanWrite 68 StrCpy $CanWriteToInstallDir $MockCanWrite 69 FunctionEnd 70 71 !macro _IsMockedAsTrue _v _b _t _f 72 ; If the mock value is the same as 'true', jump to the label specified in $_t 73 ; Otherwise, jump to the label specified in $_f 74 ; (This is compatible with LogicLib ${If} and similar tests) 75 StrCmp `${_v}` 'true' `${_t}` `${_f}` 76 !macroend 77 78 ; Fail a unit test. Prints the message and location of the failure. 79 !macro Fail _message 80 StrCpy $FailureMessage "At Line ${__LINE__}: ${_message}" 81 SetErrors 82 Return 83 !macroend 84 !define Fail "!insertmacro Fail" 85 86 ; A helpful macro for testing that a variable is equal to a value. 87 ; Provide the variable name (bare, without $ prefix) and the expected value. 88 ; For example, to test that $MyVariable is equal to "hello there", you would write: 89 ; !insertmacro AssertEqual MyVariable "hello there" 90 !macro AssertEqual _variableName _expectedValue 91 ${If} "$${_variableName}" != "${_expectedValue}" ; quoted to prevent numeric coercion (01 != 1) 92 ${Fail} "Expected ${_variableName} to have value `${_expectedValue}` , actual value was `$${_variableName}`" 93 SetErrors 94 Return 95 ${EndIf} 96 !macroend 97 !define AssertEqual "!insertmacro AssertEqual" 98 99 Var TestFailureCount 100 101 !macro UnitTest _testFunctionName 102 Call ${_testFunctionName} 103 IfErrors 0 +3 104 IntOp $TestFailureCount $TestFailureCount + 1 105 FileWrite $Stdout "FAILURE: $FailureMessage; " 106 ClearErrors 107 !macroend 108 !define UnitTest "!insertmacro UnitTest" 109 110 ; Redefine ElevateUAC as a no-op in this test exectuable 111 !define /redef ElevateUAC `` 112 113 !include stub.nsh 114 115 116 ; .onInit is responsible for running the tests 117 Function .onInit 118 Call AttachConsole 119 Pop $Stdout 120 IntOp $TestFailureCount 0 + 0 121 ${UnitTest} TestDontInstallOnOldWindows 122 123 ${UnitTest} TestDontInstallOnNewWindowsWithoutSSE 124 125 ${UnitTest} TestDontInstallOnOldWindowsWithoutSSE 126 127 ${UnitTest} TestGetArchToInstall 128 129 ${UnitTest} TestMaintServiceCfg 130 131 ${UnitTest} TestCanWriteToDirSuccess 132 133 ${UnitTest} TestCanWriteToDirFail 134 135 ${UnitTest} TestUninstallRegKey 136 137 ${UnitTest} TestSwapShellVarContext 138 139 ${UnitTest} TestGetInstallationTypeAbsent 140 ${UnitTest} TestGetInstallationTypeEmpty 141 ${UnitTest} TestGetInstallationTypeInvalid_ACP 142 ${UnitTest} TestGetInstallationTypeStub_ACP 143 ${UnitTest} TestGetInstallationTypeFull_ACP 144 ${UnitTest} TestGetInstallationTypeOther_ACP 145 ${UnitTest} TestGetInstallationTypeInvalid_UTF16 146 ${UnitTest} TestGetInstallationTypeStub_UTF16 147 ${UnitTest} TestGetInstallationTypeFull_UTF16 148 ${UnitTest} TestGetInstallationTypeOther_UTF16 149 150 ${If} $TestFailureCount = 0 151 ; On success, write the success metric and jump to the end 152 FileWrite $Stdout "All stub installer tests passed" 153 ${Else} 154 FileWrite $Stdout "$TestFailureCount stub installer tests failed" 155 ${EndIf} 156 FileClose $Stdout 157 Return 158 OnError: 159 Abort "Failed to run tests." 160 FunctionEnd 161 162 ; Expect installation to abort if windows version < 10 163 Function TestDontInstallOnOldWindows 164 StrCpy $MockAtLeastWin10 'false' 165 StrCpy $MockCpuHasSSE '1' 166 StrCpy $AbortInstallation '' 167 StrCpy $ExitCode "Unknown" 168 Call CommonOnInit 169 !insertmacro AssertEqual ExitCode "${ERR_PREINSTALL_SYS_OS_REQ}" 170 !insertmacro AssertEqual AbortInstallation "true" 171 !insertmacro AssertEqual R7 "$(WARN_MIN_SUPPORTED_OSVER_MSG2)" 172 FunctionEnd 173 174 175 ; Expect installation to abort on processor without SSE, WIndows 10+ version 176 Function TestDontInstallOnNewWindowsWithoutSSE 177 StrCpy $MockAtLeastWin10 'true' 178 StrCpy $MockCpuHasSSE '0' 179 StrCpy $AbortInstallation '' 180 StrCpy $ExitCode "Unknown" 181 Call CommonOnInit 182 !insertmacro AssertEqual ExitCode "${ERR_PREINSTALL_SYS_HW_REQ}" 183 !insertmacro AssertEqual AbortInstallation "true" 184 FunctionEnd 185 186 ; Expect installation to abort on processor without SSE, Windows <10 version 187 Function TestDontInstallOnOldWindowsWithoutSSE 188 StrCpy $MockAtLeastWin10 'false' 189 StrCpy $MockCpuHasSSE '0' 190 StrCpy $AbortInstallation '' 191 StrCpy $ExitCode "Unknown" 192 Call CommonOnInit 193 !insertmacro AssertEqual ExitCode "${ERR_PREINSTALL_SYS_OS_REQ}" 194 !insertmacro AssertEqual AbortInstallation "true" 195 !insertmacro AssertEqual R7 "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG2)" 196 FunctionEnd 197 198 ; Expect to find a known supported architecture for Windows 199 Function TestGetArchToInstall 200 StrCpy $TestBreakpointNumber "${TestBreakpointArchToInstall}" 201 StrCpy $MockAtLeastWin10 'true' 202 StrCpy $MockCpuHasSSE '1' 203 StrCpy $INSTDIR "Unknown" 204 StrCpy $ArchToInstall "Unknown" 205 206 Call CommonOnInit 207 ${Switch} $ArchToInstall 208 ${Case} "${ARCH_X86}" 209 ; OK, fall through 210 ${Case} "${ARCH_AMD64}" 211 ; OK, fall through 212 ${Case} "${ARCH_AARCH64}" 213 ; OK 214 ${Break} 215 ${Default} 216 StrCpy $FailureMessage "Unexpected value for ArchToInstall: $ArchToInstall" 217 SetErrors 218 Return 219 ${EndSwitch} 220 221 ${Switch} $INSTDIR 222 ${Case} "${DefaultInstDir64bit}" 223 ; OK, fall through 224 ${Case} "${DefaultInstDir32bit}" 225 ; OK 226 ${Break} 227 ${Default} 228 StrCpy $FailureMessage "Unexpected value for INSTDIR: $INSTDIR" 229 SetErrors 230 Return 231 ${EndSwitch} 232 FunctionEnd 233 234 ; Since we're not actually elevating permissions, expect not to enable installing maintenance service 235 Function TestMaintServiceCfg 236 StrCpy $TestBreakpointNumber "${TestBreakpointMaintService}" 237 StrCpy $CheckboxInstallMaintSvc 'Unknown' 238 StrCpy $MockAtLeastWin10 'true' 239 StrCpy $MockCpuHasSSE '1' 240 241 Call CommonOnInit 242 243 !insertmacro AssertEqual CheckboxInstallMaintSvc "0" 244 FunctionEnd 245 246 ; Expect success if we can write to installation directory 247 Function TestCanWriteToDirSuccess 248 StrCpy $TestBreakpointNumber "${TestBreakpointCanWriteToDir}" 249 StrCpy $MockCanWrite 'true' 250 StrCpy $MockAtLeastWin10 'true' 251 StrCpy $MockCpuHasSSE '1' 252 StrCpy $CanWriteToInstallDir "Unknown" 253 StrCpy $AbortInstallation "false" 254 255 Call CommonOnInit 256 257 ; Since we're not running as admin, expect directory to be under user's own home directory, so it should be writable 258 !insertmacro AssertEqual CanWriteToInstallDir "true" 259 !insertmacro AssertEqual AbortInstallation "false" 260 FunctionEnd 261 262 ; Expect failure if we can't write to installation directory 263 Function TestCanWriteToDirFail 264 StrCpy $TestBreakpointNumber "${TestBreakpointCanWriteToDir}" 265 StrCpy $MockCanWrite 'false' 266 StrCpy $MockAtLeastWin10 'true' 267 StrCpy $MockCpuHasSSE '1' 268 StrCpy $CanWriteToInstallDir "Unknown" 269 StrCpy $AbortInstallation "false" 270 271 Call CommonOnInit 272 273 ; Since we're not running as admin, expect directory to be under user's own home directory, so it should be writable 274 !insertmacro AssertEqual CanWriteToInstallDir "false" 275 !insertmacro AssertEqual ExitCode "${ERR_PREINSTALL_NOT_WRITABLE}" 276 !insertmacro AssertEqual AbortInstallation "true" 277 FunctionEnd 278 279 !include postupdate_helper.nsh 280 281 Function TestUninstallRegKey 282 Call CommonOnInit 283 Push $R0 ; We will locally use $R0, ensuring that we can restore it later. 284 285 Call findUninstallKey 286 Pop $R0 287 !insertmacro AssertEqual R0 "" 288 289 Call getModernUninstallKey 290 Pop $R0 291 !insertmacro AssertEqual R0 \ 292 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal}" 293 294 Call getLegacyUninstallKey 295 Pop $R0 296 !insertmacro AssertEqual R0 \ 297 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 298 299 ; Ensure that the getDefaultInstallDir function does not modify the 300 ; contents of $1. 301 Push $INSTDIR ; back up the original contents for later 302 Push $1 303 Call getDefaultInstallDir 304 Pop $INSTDIR ; discard the return value of getDefaultInstallDir 305 Pop $INSTDIR ; restore to original (see above) 306 !insertmacro AssertEqual INSTDIR "$1" 307 308 ; Later, we also want to check that none of the common registers have been 309 ; altered by calling functions. We save their contents on the stack for 310 ; that check. 311 Push "(0 $0, 1 $1, 2 $2, 3 $3, 4 $4, 5 $5, 6 $6, 7 $7, 8 $8, 9 $9)" 312 313 ; ---- 314 ; Modify the INSTDIR in a way as if a user had chosen to add a version 315 ; number to the path 316 UserInfo::GetAccountType 317 Pop $INSTDIR 318 319 ${If} $INSTDIR == "User" 320 ${GetLocalAppDataFolder} $INSTDIR 321 StrCpy $INSTDIR "$INSTDIR\${BrandFullName} 123.0\" ; %LOCALAPPDATA%/${BrandFullName} 322 ${Else} 323 !ifdef HAVE_64BIT_BUILD 324 StrCpy $INSTDIR "$PROGRAMFILES64\${BrandFullNameInternal} 123.0\" 325 !else 326 StrCpy $INSTDIR "$PROGRAMFILES32\${BrandFullNameInternal} 123.0\" 327 !endif 328 ${EndIf} 329 Push ${buildNumWin10} 330 Call getUninstallKey 331 Pop $INSTDIR ; reuse INSTDIR for the return value 332 !insertmacro AssertEqual INSTDIR \ 333 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 334 ClearErrors 335 336 ; ---- 337 ; Modify the INSTDIR as if we wanted to install to 338 ; (like unelevated installations do it) 339 Push ${buildNumWin10} 340 Call getUninstallKey 341 Pop $INSTDIR ; reuse INSTDIR for the return value 342 !insertmacro AssertEqual INSTDIR \ 343 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 344 ClearErrors 345 346 ; ---- 347 ; An empty installation path should lead to the legacy uninstall key. 348 StrCpy $INSTDIR "" 349 Push ${buildNumWin10} 350 Call getUninstallKey 351 Pop $INSTDIR ; reuse INSTDIR for the return value 352 !insertmacro AssertEqual INSTDIR \ 353 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 354 ClearErrors 355 356 ; ---- 357 ; Modify the INSTDIR as if it was not touched by a user (use the default 358 ; settings), which under Windows 10 is a special case and the Uninstall 359 ; key should be rewritten, leaving the Version number out of it. 360 Call getDefaultInstallDir ; pushes the default directory on the stack 361 Pop $INSTDIR 362 Push ${buildNumWin10} 363 Call getUninstallKey 364 Pop $INSTDIR ; reuse INSTDIR for the return value 365 !insertmacro AssertEqual INSTDIR \ 366 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal}" 367 ClearErrors 368 369 ; ---- 370 ; Special case: The maximal supported path length here is 1024 chars, in 371 ; which case the return value for the path comparison is an error value. 372 ; Since this error value will never match the default path name, we expect 373 ; the same return value as we would get with an unsupported OS version. 374 StrCpy $INSTDIR "" 375 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 376 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 377 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 378 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 379 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 380 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 381 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 382 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 383 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 384 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 385 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 386 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 387 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 388 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 389 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 390 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 391 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 392 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 393 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 394 StrCpy $INSTDIR "$INSTDIR--------------------------------------------------" 395 StrCpy $INSTDIR "$INSTDIR-----------------------" ; 1023 - chars 396 397 ; 1st test: Just a sanity check to see if the block above really has 1023 398 ; minus signs. 399 push $0 ; save $0 400 StrLen $0 $INSTDIR 401 !insertmacro AssertEqual 0 1023 402 Pop $0 ; restore $0 403 404 ; 2nd test: getNormalizedPath is supposed to return the error message 405 ; instead of a path. 406 push $0 ; save $0 407 ; test the one error case specifically that getNormalizedPath can return. 408 Push $INSTDIR ; Simulate Windows 10 409 Call getNormalizedPath 410 Pop $0 411 412 !insertmacro AssertEqual 0 \ 413 "[!] GetFullPathNameW: Insufficient buffer memory." 414 ${IfNot} ${Errors} 415 StrCpy $FailureMessage "At Line ${__LINE__}. An error was expected. Is a SetError missing?" 416 SetErrors 417 Return 418 ${EndIf} 419 ClearErrors 420 Pop $0 ; restore $0 421 422 ; 3rd test: getUninstallKey should return the legacy uninstall key with 423 ; this buffer overflow. 424 push $0 ; save $0 425 Push 10240 ; Simulate Windows 10 426 Call getUninstallKey 427 Pop $0 428 !insertmacro AssertEqual 0 \ 429 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion} (${ARCH} ${AB_CD})" 430 ClearErrors 431 Pop $0 ; restore $0 432 433 ; ---- 434 ; Make sure that after all the testing the common working registers still 435 ; have their original values. We are using $R0, because INSTDIR can have 436 ; `Function .onVerifyInstDir` attached to it and that can modify registers. 437 Pop $R0 438 ${If} "$R0" != "(0 $0, 1 $1, 2 $2, 3 $3, 4 $4, 5 $5, 6 $6, 7 $7, 8 $8, 9 $9)" 439 StrCpy $FailureMessage "At Line ${__LINE__} Registers were changed, we expected $\n" 440 StrCpy $FailureMessage "$FailureMessage '$R0' but received $\n" 441 StrCpy $FailureMessage "$FailureMessage '(0 $0, 1 $1, 2 $2, 3 $3, 4 $4, 5 $5, 6 $6, 7 $7, 8 $8, 9 $9)'" 442 SetErrors 443 Return 444 ${EndIf} 445 Pop $INSTDIR 446 Pop $R0 447 FunctionEnd 448 449 Function TestSwapShellVarContext 450 Push $0 451 StrCpy $0 "" 452 SetShellVarContext current 453 454 ; Swap from current to all 455 ${SwapShellVarContext} all $0 456 ${AssertEqual} 0 "current" 457 ${IfNot} ${ShellVarContextAll} 458 ${Fail} "Expected shell var context to have value 'all', but it was 'current'" 459 ${EndIf} 460 461 ; Swap from all to all 462 ${SwapShellVarContext} all $0 463 ${AssertEqual} 0 "all" 464 ${IfNot} ${ShellVarContextAll} 465 ${Fail} "Expected shell var context to have value 'all', but it was 'current'" 466 ${EndIf} 467 468 ; Swap from all to current 469 ${SwapShellVarContext} current $0 470 ${AssertEqual} 0 "all" 471 ${If} ${ShellVarContextAll} 472 ${Fail} "Expected shell var context to have value 'current', but it was 'all'" 473 ${EndIf} 474 475 ; Swap from current to current 476 ${SwapShellVarContext} current $0 477 ${AssertEqual} 0 "current" 478 ${If} ${ShellVarContextAll} 479 ${Fail} "Expected shell var context to have value 'current', but it was 'all'" 480 ${EndIf} 481 482 ; Leave context and register in expected start state 483 SetShellVarContext current 484 Pop $0 485 FunctionEnd 486 487 Function TestGetInstallationTypeAbsent 488 GetTempFileName $1 489 Delete "$1" 490 Push $1 491 Call GetInstallationType 492 Pop $0 493 ${AssertEqual} 0 "unknown" 494 FunctionEnd 495 496 Function TestGetInstallationTypeEmpty 497 GetTempFileName $1 498 FileOpen $0 "$1" w 499 FileClose $0 500 501 Push $1 502 Call GetInstallationType 503 Pop $0 504 ${AssertEqual} 0 "unknown" 505 Delete $1 506 FunctionEnd 507 508 !macro GetInstallationTypeTests _WRITEFUNC _ENCODING 509 Function TestGetInstallationTypeInvalid_${_ENCODING} 510 GetTempFileName $1 511 FileOpen $0 "$1" w 512 ${_WRITEFUNC} $0 "{$\"installer_type$\":[1, 2, 3]}" 513 FileClose $0 514 515 Push $1 516 Call GetInstallationType 517 Pop $0 518 Delete $1 519 ${AssertEqual} 0 "unknown" 520 FunctionEnd 521 522 Function TestGetInstallationTypeStub_${_ENCODING} 523 GetTempFileName $1 524 FileOpen $0 "$1" w 525 ${_WRITEFUNC} $0 "{$\"installer_type$\":$\"stub$\"}" 526 FileClose $0 527 528 Push $1 529 Call GetInstallationType 530 Pop $0 531 Delete $1 532 ${AssertEqual} 0 "stub" 533 FunctionEnd 534 535 Function TestGetInstallationTypeFull_${_ENCODING} 536 GetTempFileName $1 537 FileOpen $0 "$1" w 538 ${_WRITEFUNC} $0 "{$\"installer_type$\":$\"full$\"}" 539 FileClose $0 540 541 Push $1 542 Call GetInstallationType 543 Pop $0 544 Delete $1 545 ${AssertEqual} 0 "full" 546 FunctionEnd 547 548 Function TestGetInstallationTypeOther_${_ENCODING} 549 GetTempFileName $1 550 FileOpen $0 "$1" w 551 ${_WRITEFUNC} $0 "{$\"installer_type$\":$\"abc$\"}" 552 FileClose $0 553 554 Push $1 555 Call GetInstallationType 556 Pop $0 557 Delete $1 558 ${AssertEqual} 0 "abc" 559 FunctionEnd 560 !macroend 561 !insertmacro GetInstallationTypeTests FileWrite ACP 562 !insertmacro GetInstallationTypeTests FileWriteUTF16LE UTF16 563 564 Section 565 SectionEnd