Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ce17739
ci(ui-tests): use a better way to call AutoHotKey
dscho Jul 5, 2025
cf7a164
ci(ui-tests): upload the test logs
dscho Jul 5, 2025
41d3ae0
ci(ui-tests): take a screenshot when canceled
dscho Jul 4, 2025
8d098c5
ci(ui-tests): enforce a sensible timeout
dscho Jul 4, 2025
d287c6b
ui-tests: convert DOS line endings to Unix ones
dscho Jul 3, 2025
596d3dc
ui-tests: ensure that `.ahk` scripts are stored with Unix line endings
dscho Jul 3, 2025
4e375cc
ui-tests: refactor setup into its own function
dscho Jul 3, 2025
9b6f402
ui-tests: rename `CaptureText()` to reflect that it's about Windows T…
dscho Jul 3, 2025
e33eefe
ui-tests: Move reusable functions into a library
dscho Jul 3, 2025
fd9bd82
ui-tests: let SetWorkTree() accept a directory base name
dscho Jul 3, 2025
ab61d99
ui-tests: create the Git worktree as part of SetWorkTree()
dscho Jul 3, 2025
febbeef
ui-tests: document how `CaptureTextFromWindowsTerminal()` works
dscho Jul 5, 2025
f58efbe
ui-tests: move code to wait for text in the Windows Terminal into a f…
dscho Jul 3, 2025
cbb3b37
ui-tests: fix indentation of `RunWaitOne()`
dscho Jul 3, 2025
3ae005b
ui-tests: improve `RunWaitOne()`
dscho Jul 3, 2025
0e5a76a
ui-tests: fix the `uname -a` invocation
dscho Jul 3, 2025
ef6a272
ui-tests: do not ignore errors in `RunWaitOne()`
dscho Jul 3, 2025
0ba85df
ui-tests: verify that a `sleep` in Windows Terminal can be interrupted
dscho Jul 3, 2025
59f8289
ui-tests: trim the output in `RunWaitOne()`
dscho Jul 3, 2025
99f35a8
ui-tests: prepare `WaitForRegExInWindowsTerminal()` for disruptions
dscho Jul 3, 2025
22ed451
ui-tests: verify that interrupting clones via SSH works
dscho Jul 3, 2025
0be3642
ci(ui-tests): exclude the large repository from the build artifact
dscho Jul 5, 2025
6673d8e
Cygwin: console: Call set_input_mode() after changing disable_master_…
tyan0 Jul 3, 2025
f6c4c7f
ui-tests: add `ping` interrupt test
dscho Jul 3, 2025
772caa7
ui-tests: do verify the SSH hang fix
dscho Jul 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 101 additions & 7 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
env:
AUTOHOTKEY_VERSION: 2.0.19
WT_VERSION: 1.22.11141.0
WIN32_OPENSSH_VERSION: 9.8.3.0p2-Preview

jobs:
ui-tests:
Expand Down Expand Up @@ -76,19 +77,112 @@ jobs:
"$WINDIR/system32/tar.exe" -C "$RUNNER_TEMP/ahk" -xf "$RUNNER_TEMP/ahk.zip" &&
cygpath -aw "$RUNNER_TEMP/ahk" >>$GITHUB_PATH
- uses: actions/setup-node@v4 # the hook uses node for the background process
- uses: actions/cache/restore@v4
id: restore-win32-openssh
with:
key: win32-openssh-${{ env.WIN32_OPENSSH_VERSION }}
path: ${{ runner.temp }}/win32-openssh.zip
- name: Download Win32-OpenSSH
if: steps.restore-win32-openssh.outputs.cache-hit != 'true'
shell: bash
run: |
curl -fLo "$RUNNER_TEMP/win32-openssh.zip" \
https://github.com/PowerShell/Win32-OpenSSH/releases/download/v$WIN32_OPENSSH_VERSION/OpenSSH-Win64.zip
- uses: actions/cache/save@v4
if: steps.restore-win32-openssh.outputs.cache-hit != 'true'
with:
key: win32-openssh-${{ env.WIN32_OPENSSH_VERSION }}
path: ${{ runner.temp }}/win32-openssh.zip
- name: Unpack Win32-OpenSSH
shell: bash
run: |
"$WINDIR/system32/tar.exe" -C "$RUNNER_TEMP" -xvf "$RUNNER_TEMP/win32-openssh.zip" &&
echo "OPENSSH_FOR_WINDOWS_DIRECTORY=$(cygpath -aw "$RUNNER_TEMP/OpenSSH-Win64")" >>$GITHUB_ENV

- uses: actions/checkout@v4
with:
sparse-checkout: |
ui-tests
- name: Run UI tests
id: ui-tests
timeout-minutes: 10
working-directory: ui-tests
run: |
$p = Start-Process -PassThru -FilePath "${env:RUNNER_TEMP}\ahk\AutoHotKey64.exe" -ArgumentList ui-tests\background-hook.ahk, "$PWD\bg-hook"
$p.WaitForExit()
if ($p.ExitCode -ne 0) { echo "::error::Test failed!" } else { echo "::notice::Test log" }
$exitCode = 0
& "${env:RUNNER_TEMP}\ahk\AutoHotKey64.exe" /ErrorStdOut /force background-hook.ahk "$PWD\bg-hook" 2>&1 | Out-Default
if (!$?) { $exitCode = 1; echo "::error::Test failed!" } else { echo "::notice::Test log" }
type bg-hook.log
if ($p.ExitCode -ne 0) { exit 1 }
- name: Show logs, if canceled
if: cancelled()
run: type bg-hook.log
$env:LARGE_FILES_DIRECTORY = "${env:RUNNER_TEMP}\large"
& "${env:RUNNER_TEMP}\ahk\AutoHotKey64.exe" /ErrorStdOut /force ctrl-c.ahk "$PWD\ctrl-c" 2>&1 | Out-Default
if (!$?) { $exitCode = 1; echo "::error::Ctrl+C Test failed!" } else { echo "::notice::Ctrl+C Test log" }
type ctrl-c.log
exit $exitCode
- name: Show logs
if: always()
working-directory: ui-tests
run: |
type bg-hook.log
type ctrl-c.log
- name: Take screenshot, if canceled
id: take-screenshot
if: cancelled() || failure()
shell: powershell
run: |
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;

public class DpiHelper {
[DllImport("user32.dll")]
public static extern bool SetProcessDpiAwarenessContext(IntPtr dpiContext);

[DllImport("Shcore.dll")]
public static extern int GetDpiForMonitor(IntPtr hmonitor, int dpiType, out uint dpiX, out uint dpiY);

[DllImport("User32.dll")]
public static extern IntPtr MonitorFromPoint(System.Drawing.Point pt, uint dwFlags);

[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

public static uint GetDPI() {
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
SetProcessDpiAwarenessContext((IntPtr)(-4));

uint dpiX, dpiY;
IntPtr monitor = MonitorFromPoint(new System.Drawing.Point(0, 0), 2); // MONITOR_DEFAULTTONEAREST
GetDpiForMonitor(monitor, 0, out dpiX, out dpiY); // MDT_EFFECTIVE_DPI
return (dpiX + dpiY) / 2;
}
}
"@ -ReferencedAssemblies "System.Drawing.dll"

# First, minimize the Console window in which this script is running
$hwnd = (Get-Process -Id $PID).MainWindowHandle
$SW_MINIMIZE = 6

[DpiHelper]::ShowWindow($hwnd, $SW_MINIMIZE)

# Now, get the DPI
$dpi = [DpiHelper]::GetDPI()

# This function takes a screenshot and saves it as a PNG file
[Reflection.Assembly]::LoadWithPartialName("System.Drawing")
function screenshot([Drawing.Rectangle]$bounds, $path) {
$bmp = New-Object Drawing.Bitmap $bounds.width, $bounds.height
$graphics = [Drawing.Graphics]::FromImage($bmp)
$graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)
$bmp.Save($path)
$graphics.Dispose()
$bmp.Dispose()
}
Add-Type -AssemblyName System.Windows.Forms
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
$bounds = [Drawing.Rectangle]::FromLTRB(0, 0, $screen.Bounds.Width * $dpi / 96, $screen.Bounds.Height * $dpi / 96)
screenshot $bounds "ui-tests/screenshot.png"
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: ui-tests-${{ matrix.os }}
path: ui-tests
1 change: 1 addition & 0 deletions ui-tests/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.ahk eol=lf
183 changes: 54 additions & 129 deletions ui-tests/background-hook.ahk
Original file line number Diff line number Diff line change
@@ -1,129 +1,54 @@
#Requires AutoHotkey v2.0

; This script is an integration test for the following scenario:
; A Git hook spawns a background process that outputs some text
; to the console even after Git has exited.

; At some point in time, the Cygwin/MSYS2 runtime left the console
; in a state where it was not possible to navigate the history via
; CursorUp/Down, as reported in https://github.com/microsoft/git/issues/730.
; This was fixed in the Cygwin/MSYS2 runtime, but then regressed again.
; This test is meant to verify that the issue is fixed and remains so.

; First, set the worktree path; This path will be reused
; for the `.log` file).
if A_Args.Length > 0
workTree := A_Args[1]
else
{
; Create a unique worktree path in the TEMP directory.
workTree := EnvGet('TEMP') . '\git-test-background-hook'
if FileExist(workTree)
{
counter := 0
while FileExist(workTree '-' counter)
counter++
workTree := workTree '-' counter
}
}

Info(text) {
FileAppend text '`n', workTree '.log'
}

closeWindow := false
childPid := 0
ExitWithError(error) {
Info 'Error: ' error
if closeWindow
WinClose "A"
else if childPid != 0
ProcessClose childPid
ExitApp 1
}

RunWaitOne(command) {
shell := ComObject("WScript.Shell")
; Execute a single command via cmd.exe
exec := shell.Exec(A_ComSpec " /C " command)
; Read and return the command's output
return exec.StdOut.ReadAll()
}

SetWorkingDir(EnvGet('TEMP'))
Info 'uname: ' RunWaitOne('uname -a')
Info RunWaitOne('git version --build-options')

RunWait('git init "' workTree '"', '', 'Hide')
if A_LastError
ExitWithError 'Could not initialize Git worktree at: ' workTree

SetWorkingDir(workTree)
if A_LastError
ExitWithError 'Could not set working directory to: ' workTree

if not FileExist('.git/hooks') and not DirCreate('.git/hooks')
ExitWithError 'Could not create hooks directory: ' workTree

FileAppend("#!/bin/sh`npowershell -command 'for ($i = 0; $i -lt 50; $i++) { echo $i; sleep -milliseconds 10 }' &`n", '.git/hooks/pre-commit')
if A_LastError
ExitWithError 'Could not create pre-commit hook: ' A_LastError

Run 'wt.exe -d . ' A_ComSpec ' /d', , , &childPid
if A_LastError
ExitWithError 'Error launching CMD: ' A_LastError
Info 'Launched CMD: ' childPid
if not WinWait(A_ComSpec, , 9)
ExitWithError 'CMD window did not appear'
Info 'Got window'
WinActivate
CloseWindow := true
WinMove 0, 0
Info 'Moved window to top left (so that the bottom is not cut off)'

CaptureText() {
ControlGetPos &cx, &cy, &cw, &ch, 'Windows.UI.Composition.DesktopWindowContentBridge1', "A"
titleBarHeight := 54
scrollBarWidth := 28
pad := 8

SavedClipboard := ClipboardAll
A_Clipboard := ''
SendMode('Event')
MouseMove cx + pad, cy + titleBarHeight + pad
MouseClickDrag 'Left', , , cx + cw - scrollBarWidth, cy + ch - pad, , ''
MouseClick 'Right'
ClipWait()
Result := A_Clipboard
Clipboard := SavedClipboard
return Result
}

Info('Setting committer identity')
Send('git config user.name Test{Enter}git config user.email [email protected]{Enter}')

Info('Committing')
Send('git commit --allow-empty -m zOMG{Enter}')
; Wait for the hook to finish printing
While not RegExMatch(CaptureText(), '`n49$')
{
Sleep 100
if A_Index > 1000
ExitWithError 'Timed out waiting for commit to finish'
MouseClick 'WheelDown', , , 20
}
Info('Hook finished')

; Verify that CursorUp shows the previous command
Send('{Up}')
Sleep 150
Text := CaptureText()
if not RegExMatch(Text, 'git commit --allow-empty -m zOMG *$')
ExitWithError 'Cursor Up did not work: ' Text
Info('Match!')

Send('^C')
Send('exit{Enter}')
Sleep 50
SetWorkingDir(EnvGet('TEMP'))
DirDelete(workTree, true)
#Requires AutoHotkey v2.0
#Include ui-test-library.ahk

; This script is an integration test for the following scenario:
; A Git hook spawns a background process that outputs some text
; to the console even after Git has exited.

; At some point in time, the Cygwin/MSYS2 runtime left the console
; in a state where it was not possible to navigate the history via
; CursorUp/Down, as reported in https://github.com/microsoft/git/issues/730.
; This was fixed in the Cygwin/MSYS2 runtime, but then regressed again.
; This test is meant to verify that the issue is fixed and remains so.

SetWorkTree('git-test-background-hook')

if not FileExist('.git/hooks') and not DirCreate('.git/hooks')
ExitWithError 'Could not create hooks directory: ' workTree

FileAppend("#!/bin/sh`npowershell -command 'for ($i = 0; $i -lt 50; $i++) { echo $i; sleep -milliseconds 10 }' &`n", '.git/hooks/pre-commit')
if A_LastError
ExitWithError 'Could not create pre-commit hook: ' A_LastError

Run 'wt.exe -d . ' A_ComSpec ' /d', , , &childPid
if A_LastError
ExitWithError 'Error launching CMD: ' A_LastError
Info 'Launched CMD: ' childPid
if not WinWait(A_ComSpec, , 9)
ExitWithError 'CMD window did not appear'
Info 'Got window'
WinActivate
CloseWindow := true
WinMove 0, 0
Info 'Moved window to top left (so that the bottom is not cut off)'

Info('Setting committer identity')
Send('git config user.name Test{Enter}git config user.email [email protected]{Enter}')

Info('Committing')
Send('git commit --allow-empty -m zOMG{Enter}')
; Wait for the hook to finish printing
WaitForRegExInWindowsTerminal('`n49$', 'Timed out waiting for commit to finish', 'Hook finished', 100000)

; Verify that CursorUp shows the previous command
Send('{Up}')
Sleep 150
Text := CaptureTextFromWindowsTerminal()
if not RegExMatch(Text, 'git commit --allow-empty -m zOMG *$')
ExitWithError 'Cursor Up did not work: ' Text
Info('Match!')

Send('^C')
Send('exit{Enter}')
Sleep 50
CleanUpWorkTree()
Loading
Loading