diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9751e97089..d12b24c378 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,6 +2,9 @@ name: build on: [push, pull_request] +permissions: + contents: read + jobs: build: runs-on: windows-latest @@ -34,6 +37,7 @@ jobs: with: name: install path: _dest/ + minimal-sdk-artifact: runs-on: windows-latest needs: [build] @@ -107,8 +111,73 @@ jobs: with: name: git-artifacts path: git-artifacts.tar.gz + test-minimal-sdk: needs: [minimal-sdk-artifact] uses: git-for-windows/git-sdk-64/.github/workflows/test-ci-artifacts.yml@main with: git-artifacts-extract-location: ${{ needs.minimal-sdk-artifact.outputs.git-artifacts-extract-location }} + + ui-tests: + needs: build + uses: ./.github/workflows/ui-tests.yml + with: + msys2-runtime-artifact-name: install + permissions: + contents: read + + generate-msys2-tests-matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - id: matrix + uses: msys2/msys2-tests/gha-matrix-gen@main + + msys2-tests: + needs: [build, generate-msys2-tests-matrix] + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.generate-msys2-tests-matrix.outputs.matrix) }} + + name: msys2-tests ${{ matrix.msystem }}-${{ matrix.cc }} + runs-on: ${{ matrix.runner }} + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + FC: ${{ matrix.fc }} + steps: + - id: msys2 + uses: msys2/setup-msys2@v2 + with: + msystem: ${{ matrix.msystem }} + update: true + install: ${{ matrix.packages }} + + - name: Add staging repo + shell: msys2 {0} + run: | + sed -i '1s|^|[staging]\nServer = https://repo.msys2.org/staging/\nSigLevel = Never\n|' /etc/pacman.conf + + - name: Update using staging + shell: pwsh + run: | + msys2 -c 'pacman --noconfirm -Suuy' + $ErrorActionPreference = 'Stop' + $PSNativeCommandUseErrorActionPreference = $true + msys2 -c 'pacman --noconfirm -Suu' + + - name: Download msys2-runtime artifact + uses: actions/download-artifact@v4 + with: + name: install + path: ${{ steps.msys2.outputs.msys2-location }} + + - name: uname -a + shell: msys2 {0} + run: uname -a + + - name: Run tests + uses: msys2/msys2-tests@main + diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml new file mode 100644 index 0000000000..2fd306c280 --- /dev/null +++ b/.github/workflows/ui-tests.yml @@ -0,0 +1,87 @@ +name: ui-tests + +on: + workflow_call: + inputs: + msys2-runtime-artifact-name: + required: true + type: string + +env: + AUTOHOTKEY_VERSION: 2.0.19 + WT_VERSION: 1.22.11141.0 + +jobs: + ui-tests: + runs-on: windows-latest + steps: + - uses: actions/download-artifact@v4 + with: + name: ${{ inputs.msys2-runtime-artifact-name }} + path: ${{ runner.temp }}/artifacts + - name: replace MSYS2 runtime + run: | + $p = Get-ChildItem -Recurse "${env:RUNNER_TEMP}\artifacts" | where {$_.Name -eq "msys-2.0.dll"} | Select -ExpandProperty VersionInfo | Select -First 1 -ExpandProperty FileName + cp $p "c:/Program Files/Git/usr/bin/msys-2.0.dll" + + - uses: actions/cache/restore@v4 + id: restore-wt + with: + key: wt-${{ env.WT_VERSION }} + path: ${{ runner.temp }}/wt.zip + - name: Download Windows Terminal + if: steps.restore-wt.outputs.cache-hit != 'true' + shell: bash + run: | + curl -fLo "$RUNNER_TEMP/wt.zip" \ + https://github.com/microsoft/terminal/releases/download/v$WT_VERSION/Microsoft.WindowsTerminal_${WT_VERSION}_x64.zip + - uses: actions/cache/save@v4 + if: steps.restore-wt.outputs.cache-hit != 'true' + with: + key: wt-${{ env.WT_VERSION }} + path: ${{ runner.temp }}/wt.zip + - name: Install Windows Terminal + shell: bash + working-directory: ${{ runner.temp }} + run: | + "$WINDIR/system32/tar.exe" -xf "$RUNNER_TEMP/wt.zip" && + cygpath -aw terminal-$WT_VERSION >>$GITHUB_PATH + - uses: actions/cache/restore@v4 + id: restore-ahk + with: + key: ahk-${{ env.AUTOHOTKEY_VERSION }} + path: ${{ runner.temp }}/ahk.zip + - name: Download AutoHotKey2 + if: steps.restore-ahk.outputs.cache-hit != 'true' + shell: bash + run: | + curl -L -o "$RUNNER_TEMP/ahk.zip" \ + https://github.com/AutoHotkey/AutoHotkey/releases/download/v$AUTOHOTKEY_VERSION/AutoHotkey_$AUTOHOTKEY_VERSION.zip + - uses: actions/cache/save@v4 + if: steps.restore-ahk.outputs.cache-hit != 'true' + with: + key: ahk-${{ env.AUTOHOTKEY_VERSION }} + path: ${{ runner.temp }}/ahk.zip + - name: Install AutoHotKey2 + shell: bash + run: | + mkdir -p "$RUNNER_TEMP/ahk" && + "$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/checkout@v4 + with: + sparse-checkout: | + ui-tests + - name: Run UI tests + id: 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" } + type bg-hook.log + if ($p.ExitCode -ne 0) { exit 1 } + - name: Show logs, if canceled + if: cancelled() + run: type bg-hook.log diff --git a/newlib/libc/include/sys/unistd.h b/newlib/libc/include/sys/unistd.h index 771a4bd79c..4cf9f06362 100644 --- a/newlib/libc/include/sys/unistd.h +++ b/newlib/libc/include/sys/unistd.h @@ -215,7 +215,7 @@ int setpgrp (void); #if defined(__CYGWIN__) && __BSD_VISIBLE /* Stub for Linux libbsd compatibility. */ #define initsetproctitle(c, a, e) setproctitle_init((c), (a), (e)) -static inline void setproctitle_init (int _c, char *_a[], char *_e[]) {} +static __inline void setproctitle_init (int _c, char *_a[], char *_e[]) {} void setproctitle (const char *, ...) _ATTRIBUTE ((__format__ (__printf__, 1, 2))); diff --git a/ui-tests/background-hook.ahk b/ui-tests/background-hook.ahk new file mode 100755 index 0000000000..43f723b56d --- /dev/null +++ b/ui-tests/background-hook.ahk @@ -0,0 +1,129 @@ +#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 t@e.st{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) \ No newline at end of file diff --git a/winsup/cygwin/fhandler/console.cc b/winsup/cygwin/fhandler/console.cc index f33e354c25..2a224fe026 100644 --- a/winsup/cygwin/fhandler/console.cc +++ b/winsup/cygwin/fhandler/console.cc @@ -771,6 +771,8 @@ fhandler_console::setup () con.disable_master_thread = true; con.master_thread_suspended = false; con.num_processed = 0; + con.curr_input_mode = tty::restore; + con.curr_output_mode = tty::restore; } } @@ -849,11 +851,6 @@ fhandler_console::set_input_mode (tty::cons_mode m, const termios *t, flags |= ENABLE_PROCESSED_INPUT; break; } - if (con.curr_input_mode != tty::cygwin && m == tty::cygwin) - { - prev_input_mode_backup = con.prev_input_mode; - con.prev_input_mode = oflags; - } con.curr_input_mode = m; SetConsoleMode (p->input_handle, flags); if (!(oflags & ENABLE_VIRTUAL_TERMINAL_INPUT) @@ -893,11 +890,6 @@ fhandler_console::set_output_mode (tty::cons_mode m, const termios *t, flags |= DISABLE_NEWLINE_AUTO_RETURN; break; } - if (con.curr_output_mode != tty::cygwin && m == tty::cygwin) - { - prev_output_mode_backup = con.prev_output_mode; - GetConsoleMode (p->output_handle, &con.prev_output_mode); - } con.curr_output_mode = m; acquire_attach_mutex (mutex_timeout); DWORD resume_pid = attach_console (con.owner); @@ -1836,6 +1828,12 @@ fhandler_console::open (int flags, mode_t) handle_set.output_handle = h; release_output_mutex (); + if (con.owner == GetCurrentProcessId ()) + { + GetConsoleMode (get_handle (), &con.prev_input_mode); + GetConsoleMode (get_output_handle (), &con.prev_output_mode); + } + wpbuf.init (); handle_set.input_mutex = input_mutex; @@ -1881,6 +1879,19 @@ fhandler_console::open (int flags, mode_t) setenv ("TERM", "cygwin", 1); } + if (con.curr_input_mode != tty::cygwin) + { + prev_input_mode_backup = con.prev_input_mode; + GetConsoleMode (get_handle (), &con.prev_input_mode); + set_input_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set); + } + if (con.curr_output_mode != tty::cygwin) + { + prev_output_mode_backup = con.prev_output_mode; + GetConsoleMode (get_output_handle (), &con.prev_output_mode); + set_output_mode (tty::cygwin, &get_ttyp ()->ti, &handle_set); + } + debug_printf ("opened conin$ %p, conout$ %p", get_handle (), get_output_handle ()); @@ -4720,7 +4731,7 @@ fhandler_console::cons_mode_on_close (handle_set_t *p) NTSTATUS status = NtQueryInformationProcess (GetCurrentProcess (), ProcessBasicInformation, &pbi, sizeof (pbi), NULL); - if (NT_SUCCESS (status) + if (NT_SUCCESS (status) && cygwin_pid (con.owner) && !process_alive ((DWORD) pbi.InheritedFromUniqueProcessId)) /* Execed from normal cygwin process and the parent has been exited. */ return tty::cygwin;