-
Notifications
You must be signed in to change notification settings - Fork 474
379 lines (325 loc) · 14.4 KB
/
Copy pathupgrade-tests.yaml
File metadata and controls
379 lines (325 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
name: Upgrade Tests
on:
workflow_call:
inputs:
skip:
description: 'URL of a previous successful run; if non-empty, all steps are skipped'
required: false
type: string
default: ''
lkg_release_tag:
description: 'Tag of the last known good release to upgrade from (default: latest release)'
required: false
type: string
default: ''
permissions:
contents: read
actions: read
jobs:
upgrade_test:
runs-on: windows-2025
name: Upgrade
timeout-minutes: 30
strategy:
matrix:
configuration: [ Release ]
scenario:
- staging-upgrade
- clean-upgrade
- double-staging
- staging-then-clean
- mount-safety-deferral
- unmount-all-triggers-upgrade
fail-fast: false
steps:
- name: Skip this job if there is a previous successful run
if: inputs.skip != ''
id: skip
uses: actions/github-script@v9
with:
script: |
core.info(`Skipping: There already is a successful run: ${{ inputs.skip }}`)
return true
# -- Artifacts --
- name: Download LKG release installer
if: steps.skip.outputs.result != 'true'
shell: pwsh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
$tag = "${{ inputs.lkg_release_tag }}"
if (-not $tag) {
$tag = gh api repos/microsoft/VFSForGit/releases/latest --jq '.tag_name'
Write-Host "Auto-detected latest release: $tag"
}
New-Item -ItemType Directory -Path gvfs-lkg -Force | Out-Null
gh release download $tag --repo microsoft/VFSForGit --pattern "SetupGVFS*.exe" --dir gvfs-lkg
- name: Download Git installer
id: download-git
if: steps.skip.outputs.result != 'true'
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: MicrosoftGit
path: git
- name: Download Git installer (retry)
if: steps.skip.outputs.result != 'true' && steps.download-git.outcome == 'failure'
uses: actions/download-artifact@v8
with:
name: MicrosoftGit
path: git
- name: Download current GVFS installer
id: download-gvfs
if: steps.skip.outputs.result != 'true'
continue-on-error: true
uses: actions/download-artifact@v8
with:
name: GVFS_${{ matrix.configuration }}_x64
path: gvfs-new
- name: Download current GVFS installer (retry)
if: steps.skip.outputs.result != 'true' && steps.download-gvfs.outcome == 'failure'
uses: actions/download-artifact@v8
with:
name: GVFS_${{ matrix.configuration }}_x64
path: gvfs-new
# -- Setup --
- name: Install Git
if: steps.skip.outputs.result != 'true'
shell: cmd
run: git\install.bat
- name: Enable ProjFS
if: steps.skip.outputs.result != 'true'
shell: pwsh
run: |
$feature = Get-WindowsOptionalFeature -Online -FeatureName Client-ProjFS
if ($feature.State -ne 'Enabled') {
Enable-WindowsOptionalFeature -Online -FeatureName Client-ProjFS -NoRestart
}
# -- Test Execution --
- name: Run upgrade test - ${{ matrix.scenario }}
if: steps.skip.outputs.result != 'true'
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$lkgInstaller = (Get-ChildItem gvfs-lkg\SetupGVFS*.exe).FullName
$newInstaller = (Get-ChildItem gvfs-new\SetupGVFS*.exe).FullName
$installDir = "C:\Program Files\VFS for Git"
$testRepo = "https://dev.azure.com/gvfs/ci/_git/ForTests"
$enlistment = "C:\gvfs-upgrade-test"
function Install-GVFS($installer, [string[]]$extraArgs = @()) {
$logDir = "C:\temp\gvfs-install-logs"
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
$logFile = Join-Path $logDir "gvfs-$(Get-Date -Format 'yyyyMMdd-HHmmss').log"
$allArgs = @("/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART", "/LOG=$logFile") + $extraArgs
Write-Host "Installing: $installer $($allArgs -join ' ')"
# Start without -Wait: Inno Setup launches child processes
# (e.g. GVFS.Service.UI) that stay running, causing -Wait to
# hang. Instead, wait only for the installer process itself.
$proc = Start-Process -FilePath $installer -ArgumentList $allArgs -PassThru
$proc.WaitForExit()
if ($proc.ExitCode -ne 0) {
Get-Content $logFile -Tail 30 -ErrorAction SilentlyContinue
throw "Installer failed with exit code $($proc.ExitCode)"
}
Write-Host "Installed successfully"
}
function Assert-ServiceRunning {
$svc = sc.exe query GVFS.Service 2>&1 | Select-String "STATE"
if ($svc -notmatch "RUNNING") { throw "GVFS.Service is not running: $svc" }
}
function Mount-TestRepo {
if (Test-Path $enlistment) {
& "$installDir\gvfs.exe" mount $enlistment 2>&1 | Write-Host
} else {
& "$installDir\gvfs.exe" clone $testRepo $enlistment 2>&1 | Write-Host
}
if ($LASTEXITCODE -ne 0) { throw "Mount/clone failed" }
$mountProc = Get-Process -Name "GVFS.Mount" -ErrorAction SilentlyContinue
if (-not $mountProc) { throw "No GVFS.Mount process after mount" }
return $mountProc.Id
}
function Assert-MountAlive($expectedPid) {
$proc = Get-Process -Id $expectedPid -ErrorAction SilentlyContinue
if (-not $proc -or $proc.ProcessName -ne "GVFS.Mount") {
throw "Mount process $expectedPid is no longer running"
}
# Verify the mount is functional by accessing a file
$readmePath = Join-Path $enlistment "src\Readme.md"
if (-not (Test-Path $readmePath)) {
throw "Mount is running but cannot access $readmePath"
}
}
function Unmount-TestRepo {
& "$installDir\gvfs.exe" unmount $enlistment 2>&1
Start-Sleep -Seconds 3
}
function Restart-Service {
sc.exe stop GVFS.Service | Out-Null
Start-Sleep -Seconds 10
sc.exe start GVFS.Service | Out-Null
Start-Sleep -Seconds 10
Assert-ServiceRunning
}
function Assert-PendingUpgrade($expected) {
$exists = Test-Path "$installDir\PendingUpgrade"
if ($exists -ne $expected) {
throw "PendingUpgrade directory: expected=$expected, actual=$exists"
}
}
function Assert-HookVersionsMatch {
Write-Host "Verifying hook binary versions in enlistment..."
$hooksDir = Join-Path $enlistment "src\.git\hooks"
# Native hooks: each has its own source binary in the install directory
# Command hooks: copies of GitHooksLoader.exe
$hooks = @(
@{ Source = "GVFS.ReadObjectHook.exe"; Target = "read-object.exe" }
@{ Source = "GVFS.VirtualFileSystemHook.exe"; Target = "virtual-filesystem.exe" }
@{ Source = "GVFS.PostIndexChangedHook.exe"; Target = "post-index-change.exe" }
@{ Source = "GitHooksLoader.exe"; Target = "pre-command.exe" }
@{ Source = "GitHooksLoader.exe"; Target = "post-command.exe" }
)
foreach ($hook in $hooks) {
$sourcePath = Join-Path $installDir $hook.Source
$targetPath = Join-Path $hooksDir $hook.Target
if (-not (Test-Path $targetPath)) {
throw "Hook not found: $targetPath"
}
$sourceVer = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($sourcePath).FileVersion
$targetVer = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($targetPath).FileVersion
if ($sourceVer -ne $targetVer) {
throw "Hook version mismatch: $($hook.Target) is $targetVer, expected $sourceVer (from $($hook.Source))"
}
Write-Host " $($hook.Target): $targetVer OK"
}
Write-Host "All hook binary versions match"
}
# =============================================
# Test scenarios
# =============================================
switch ("${{ matrix.scenario }}") {
"staging-upgrade" {
Write-Host "=== Scenario: Staging upgrade e2e ==="
# Install LKG, mount, staging upgrade, unmount, verify completion
Install-GVFS $lkgInstaller
Assert-ServiceRunning
$mountPid = Mount-TestRepo
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true")
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true
Unmount-TestRepo
Restart-Service
Assert-PendingUpgrade $false
# Remount and verify all hooks were updated to new version
$null = Mount-TestRepo
Assert-HookVersionsMatch
Unmount-TestRepo
Write-Host "PASS: Staging upgrade completed"
}
"clean-upgrade" {
Write-Host "=== Scenario: Clean upgrade (traditional) ==="
Install-GVFS $lkgInstaller
Assert-ServiceRunning
Mount-TestRepo | Write-Host
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=false")
Assert-PendingUpgrade $false
Assert-ServiceRunning
# Remount and verify all hooks were updated to new version
$null = Mount-TestRepo
Assert-HookVersionsMatch
Unmount-TestRepo
Write-Host "PASS: Clean upgrade completed"
}
"double-staging" {
Write-Host "=== Scenario: Double staging install ==="
# Install LKG, mount, staging install twice, verify second overwrites
Install-GVFS $lkgInstaller
Assert-ServiceRunning
$mountPid = Mount-TestRepo
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true")
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true
# Second staging install should overwrite PendingUpgrade
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true")
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true
Unmount-TestRepo
Restart-Service
Assert-PendingUpgrade $false
Write-Host "PASS: Double staging handled correctly"
}
"staging-then-clean" {
Write-Host "=== Scenario: Staging then clean install ==="
# Install LKG, mount, staging install, unmount, clean install
# Verify PendingUpgrade is cleaned up by clean install
Install-GVFS $lkgInstaller
Assert-ServiceRunning
$mountPid = Mount-TestRepo
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true")
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true
Unmount-TestRepo
# Now clean install — should remove PendingUpgrade
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=false")
Assert-PendingUpgrade $false
Assert-ServiceRunning
Write-Host "PASS: Staging then clean install handled correctly"
}
"mount-safety-deferral" {
Write-Host "=== Scenario: Mount safety deferral ==="
# Install LKG, mount, staging install, restart service WITH mount
# running — upgrade should be deferred
Install-GVFS $lkgInstaller
Assert-ServiceRunning
$mountPid = Mount-TestRepo
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true")
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true
# Restart service WITHOUT unmounting — upgrade should defer
Restart-Service
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true
Write-Host "Upgrade correctly deferred while mount running"
# Now unmount and restart — should complete
Unmount-TestRepo
Restart-Service
Assert-PendingUpgrade $false
Write-Host "PASS: Mount safety deferral works correctly"
}
"unmount-all-triggers-upgrade" {
Write-Host "=== Scenario: unmount-all triggers staged upgrade ==="
# Install LKG, mount, staging upgrade with new installer (which
# replaces GVFS.Service.exe in-place with the new version that
# includes PendingUpgradeMonitor). Then unmount via --unmount-all.
# The new service monitors mount process exits and applies the
# upgrade automatically — no pipe message from gvfs.exe needed.
Install-GVFS $lkgInstaller
Assert-ServiceRunning
$mountPid = Mount-TestRepo
Install-GVFS $newInstaller @("/STAGEIFMOUNTED=true")
Assert-MountAlive $mountPid
Assert-PendingUpgrade $true
# Unmount via --unmount-all (uses LKG gvfs.exe — no new pipe msg)
& "$installDir\gvfs.exe" service --unmount-all 2>&1 | Write-Host
if ($LASTEXITCODE -ne 0) { throw "unmount-all failed" }
# The monitor's debounce timer fires 5s after the last mount
# process exits, then applies the upgrade. Wait for completion.
$deadline = (Get-Date).AddSeconds(30)
while ((Test-Path "$installDir\PendingUpgrade") -and (Get-Date) -lt $deadline) {
Start-Sleep -Seconds 2
}
Assert-PendingUpgrade $false
Write-Host "PASS: unmount-all triggers staged upgrade via process monitor"
}
default {
throw "Unknown scenario: ${{ matrix.scenario }}"
}
}
- name: Upload service logs
if: always() && steps.skip.outputs.result != 'true'
uses: actions/upload-artifact@v7
continue-on-error: true
with:
name: UpgradeTest_Logs_${{ matrix.scenario }}
path: |
C:\ProgramData\GVFS\GVFS.Service\Logs\
C:\temp\gvfs-install-logs\