Skip to content

Commit dcac317

Browse files
committed
Added unit testing in the CI
1 parent 39f288d commit dcac317

File tree

5 files changed

+172
-92
lines changed

5 files changed

+172
-92
lines changed

.github/workflows/ci.yml

Lines changed: 62 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ env:
3232
SOLUTION_PATH: '${{ github.workspace }}\Files.slnx'
3333
PACKAGE_PROJECT_DIR: '${{ github.workspace }}\src\Files.App (Package)'
3434
PACKAGE_PROJECT_PATH: '${{ github.workspace }}\src\Files.App (Package)\Files.Package.wapproj'
35-
AUTOMATED_TESTS_ARCHITECTURE: 'x64'
36-
AUTOMATED_TESTS_CONFIGURATION: 'Release'
37-
AUTOMATED_TESTS_PROJECT_DIR: '${{ github.workspace }}\tests\Files.InteractionTests'
38-
AUTOMATED_TESTS_PROJECT_PATH: '${{ github.workspace }}\tests\Files.InteractionTests\Files.InteractionTests.csproj'
39-
AUTOMATED_TESTS_ASSEMBLY_DIR: '${{ github.workspace }}\artifacts\TestsAssembly'
35+
TESTS_ARCHITECTURE: 'x64'
36+
TESTS_CONFIGURATION: 'Release'
37+
TESTS_ASSEMBLY_DIR: '${{ github.workspace }}\artifacts\TestsAssembly'
38+
AXE_TESTS_PROJECT_DIR: '${{ github.workspace }}\tests\Files.InteractionTests'
39+
AXE_TESTS_PROJECT_PATH: '${{ github.workspace }}\tests\Files.InteractionTests\Files.InteractionTests.csproj'
40+
UNIT_TESTS_PROJECT_DIR: '${{ github.workspace }}\tests\Files.App.UnitTests'
41+
UNIT_TESTS_PROJECT_PATH: '${{ github.workspace }}\tests\Files.App.UnitTests\Files.App.UnitTests.csproj'
4042
ARTIFACTS_STAGING_DIR: '${{ github.workspace }}\artifacts'
4143
APPX_PACKAGE_DIR: '${{ github.workspace }}\artifacts\AppxPackages'
4244
APPX_SELFSIGNED_CERT_PATH: '${{ github.workspace }}\.github\workflows\FilesApp_SelfSigned.pfx'
@@ -118,7 +120,7 @@ jobs:
118120
-p:PublishReadyToRun=true `
119121
-v:quiet
120122
121-
- if: env.CONFIGURATION != env.AUTOMATED_TESTS_CONFIGURATION || env.ARCHITECTURE != env.AUTOMATED_TESTS_ARCHITECTURE
123+
- if: env.CONFIGURATION != env.TESTS_CONFIGURATION || env.ARCHITECTURE != env.TESTS_ARCHITECTURE
122124
name: Build Files
123125
run: |
124126
msbuild `
@@ -129,11 +131,11 @@ jobs:
129131
-p:AppxBundle=Never `
130132
-v:quiet
131133
132-
- if: env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION && env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE
134+
- if: env.CONFIGURATION == env.TESTS_CONFIGURATION && env.ARCHITECTURE == env.TESTS_ARCHITECTURE
133135
name: Create self signed cert as a pfx file
134136
run: ./.github/scripts/Generate-SelfCertPfx.ps1 -Destination "$env:APPX_SELFSIGNED_CERT_PATH"
135137

136-
- if: env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION && env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE
138+
- if: env.CONFIGURATION == env.TESTS_CONFIGURATION && env.ARCHITECTURE == env.TESTS_ARCHITECTURE
137139
name: Build & package Files
138140
run: |
139141
msbuild `
@@ -142,7 +144,7 @@ jobs:
142144
-t:_GenerateAppxPackage `
143145
-p:Configuration=$env:CONFIGURATION `
144146
-p:Platform=$env:ARCHITECTURE `
145-
-p:AppxBundlePlatforms=$env:AUTOMATED_TESTS_ARCHITECTURE `
147+
-p:AppxBundlePlatforms=$env:TESTS_ARCHITECTURE `
146148
-p:AppxBundle=Always `
147149
-p:UapAppxPackageBuildMode=SideloadOnly `
148150
-p:AppxPackageDir=$env:APPX_PACKAGE_DIR `
@@ -152,31 +154,31 @@ jobs:
152154
-p:PackageCertificateThumbprint="" `
153155
-v:quiet
154156
155-
- if: env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE && env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION
157+
- if: env.ARCHITECTURE == env.TESTS_ARCHITECTURE && env.CONFIGURATION == env.TESTS_CONFIGURATION
156158
name: Build interaction tests
157159
run: |
158-
msbuild $env:AUTOMATED_TESTS_PROJECT_PATH `
160+
msbuild $env:AXE_TESTS_PROJECT_PATH `
159161
-t:Build `
160162
-p:Configuration=$env:CONFIGURATION `
161-
-p:Platform=$env:AUTOMATED_TESTS_ARCHITECTURE `
163+
-p:Platform=$env:TESTS_ARCHITECTURE `
162164
-v:quiet
163165
164-
- if: env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE && env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION
166+
- if: env.ARCHITECTURE == env.TESTS_ARCHITECTURE && env.CONFIGURATION == env.TESTS_CONFIGURATION
165167
name: Copy tests bin to the artifacts dir
166168
shell: pwsh
167169
run: |
168170
Copy-Item `
169-
-Path "$env:AUTOMATED_TESTS_PROJECT_DIR\bin" `
170-
-Destination "$env:AUTOMATED_TESTS_ASSEMBLY_DIR" -Recurse
171+
-Path "$env:AXE_TESTS_PROJECT_DIR\bin" `
172+
-Destination "$env:TESTS_ASSEMBLY_DIR" -Recurse
171173
172-
- if: env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE && env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION
174+
- if: env.ARCHITECTURE == env.TESTS_ARCHITECTURE && env.CONFIGURATION == env.TESTS_CONFIGURATION
173175
name: Upload the packages to the Artifacts
174176
uses: actions/upload-artifact@v4
175177
with:
176178
name: 'Appx Packages (${{ env.CONFIGURATION }}, ${{ env.ARCHITECTURE }})'
177179
path: ${{ env.ARTIFACTS_STAGING_DIR }}
178180

179-
test:
181+
axe-test:
180182

181183
if: github.repository_owner == 'files-community' && always()
182184

@@ -189,6 +191,7 @@ jobs:
189191
platform: [x64]
190192
env:
191193
CONFIGURATION: ${{ matrix.configuration }}
194+
PLATFORM: ${{ matrix.platform }}
192195
permissions:
193196
contents: read
194197
pull-requests: write
@@ -209,26 +212,21 @@ jobs:
209212
- name: Download the packages from the Artifacts
210213
uses: actions/download-artifact@v4
211214
with:
212-
name: 'Appx Packages (${{ env.CONFIGURATION }}, ${{ env.AUTOMATED_TESTS_ARCHITECTURE }})'
215+
name: 'Appx Packages (${{ env.CONFIGURATION }}, ${{ env.PLATFORM }})'
213216
path: ${{ env.ARTIFACTS_STAGING_DIR }}
214217

215-
- name: Install Files
218+
- name: Prepare for the tests
216219
shell: powershell
217220
run: |
218221
Set-Location "$env:APPX_PACKAGE_DIR"
219222
$AppxPackageBundleDir = Get-ChildItem -Filter Files.Package_*_Test -Name
220223
Set-Location $AppxPackageBundleDir
221224
./Install.ps1 -Force
222225
Get-AppxPackage
226+
Set-DisplayResolution -Width 1920 -Height 1080 -Force
227+
Start-Process -FilePath "$env:WINAPPDRIVER_EXE86_PATH"
223228
224-
- name: Set full HD resolution
225-
run: Set-DisplayResolution -Width 1920 -Height 1080 -Force
226-
227-
- name: Start WinAppDriver process
228-
shell: pwsh
229-
run: Start-Process -FilePath "$env:WINAPPDRIVER_EXE86_PATH"
230-
231-
# Retry integration tests if first attempt fails
229+
# Retry the tests if first attempt fails
232230
- name: Run interaction tests
233231
uses: nick-fields/retry@v3
234232
with:
@@ -237,30 +235,46 @@ jobs:
237235
shell: pwsh
238236
command: |
239237
dotnet test `
240-
$env:AUTOMATED_TESTS_ASSEMBLY_DIR\**\Files.InteractionTests.dll `
241-
--logger "trx;LogFileName=$env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.trx"
238+
$env:TESTS_ASSEMBLY_DIR\**\Files.InteractionTests.dll `
239+
--logger "trx;LogFileName=$env:TESTS_ASSEMBLY_DIR\testResults.trx"
242240
243241
- if: github.event_name == 'pull_request'
244242
uses: geekyeggo/delete-artifact@v5
245243
with:
246244
name: '*'
247245

248-
# - name: Generate markdown from the tests result
249-
# shell: pwsh
250-
# run: |
251-
# . './scripts/Convert-TrxToMarkdown.ps1' `
252-
# -Source "$env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.trx" `
253-
# -Destination "$env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.md"
254-
# env:
255-
# PULL_REQUEST_ID: ${{ github.event.pull_request_id }}
256-
257-
# - name: Display the markdown on the output (temp)
258-
# shell: pwsh
259-
# run: |
260-
# Get-Content $env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.md
261-
262-
# - name: Publish tests result
263-
# uses: marocchino/sticky-pull-request-comment@v2
264-
# with:
265-
# header: test-result
266-
# path: '${{ env.AUTOMATED_TESTS_ASSEMBLY_DIR }}\testResults.md'
246+
unit-test:
247+
248+
if: github.repository_owner == 'files-community' && always()
249+
250+
needs: [build]
251+
runs-on: windows-latest
252+
strategy:
253+
fail-fast: false
254+
matrix:
255+
configuration: [Release]
256+
platform: [x64]
257+
env:
258+
CONFIGURATION: ${{ matrix.configuration }}
259+
PLATFORM: ${{ matrix.platform }}
260+
261+
steps:
262+
263+
- if: contains(join(needs.*.result, ','), 'failure')
264+
name: Fail if necessary
265+
run: exit 1
266+
267+
- name: Checkout the repository
268+
uses: actions/checkout@v4
269+
- name: Setup .NET
270+
uses: actions/setup-dotnet@v4
271+
with:
272+
global-json-file: global.json
273+
274+
- name: Build the unit tests
275+
run: |
276+
dotnet build $env:UNIT_TESTS_PROJECT_PATH -c $env:CONFIGURATION -a $env:PLATFORM
277+
278+
- name: Run the unit tests
279+
run: |
280+
dotnet test $env:UNIT_TESTS_PROJECT_DIR\bin\**\Files.App.UnitTests.dll`

src/Files.App.CsWin32/ComHeapPtr`1.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Runtime.CompilerServices;
6+
using Windows.Win32.System.Com;
67

78
namespace Windows.Win32
89
{

src/Files.App.Storage/Storables/WindowsStorage/WindowsStorableHelpers.Shell.cs

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -204,72 +204,69 @@ public static HRESULT TryUnpinFolderFromQuickAccess(this IWindowsFolder @this)
204204
return HRESULT.S_OK;
205205
}
206206

207-
public static IEnumerable<WindowsContextMenuItem> GetShellNewItems(this IWindowsFolder @this)
207+
public static IEnumerable<WindowsContextMenuItem>? GetShellNewMenuItems(this IWindowsFolder @this)
208208
{
209209
HRESULT hr = default;
210210

211211
IContextMenu* pNewMenu = default;
212212
using ComPtr<IShellExtInit> pShellExtInit = default;
213213
using ComPtr<IContextMenu2> pContextMenu2 = default;
214+
using ComHeapPtr<ITEMIDLIST> pShellFolderPidl = default;
214215

215216
hr = PInvoke.CoCreateInstance(CLSID.CLSID_NewMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)&pNewMenu);
216-
if (hr.ThrowIfFailedOnDebug().Failed)
217-
return [];
217+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
218218

219219
hr = pNewMenu->QueryInterface(IID.IID_IContextMenu2, (void**)pContextMenu2.GetAddressOf());
220-
if (hr.ThrowIfFailedOnDebug().Failed)
221-
return [];
220+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
222221

223222
hr = pNewMenu->QueryInterface(IID.IID_IShellExtInit, (void**)pShellExtInit.GetAddressOf());
224-
if (hr.ThrowIfFailedOnDebug().Failed)
225-
return [];
223+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
226224

227225
@this.ShellNewMenu = pNewMenu;
228226

229-
ITEMIDLIST* pFolderPidl = default;
230-
hr = PInvoke.SHGetIDListFromObject((IUnknown*)@this.ThisPtr, &pFolderPidl);
231-
if (hr.ThrowIfFailedOnDebug().Failed)
232-
return [];
227+
hr = PInvoke.SHGetIDListFromObject((IUnknown*)@this.ThisPtr, pShellFolderPidl.GetAddressOf());
228+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
233229

234-
hr = pShellExtInit.Get()->Initialize(pFolderPidl, null, default);
235-
if (hr.ThrowIfFailedOnDebug().Failed)
236-
return [];
230+
hr = pShellExtInit.Get()->Initialize(pShellFolderPidl.Get(), null, default);
231+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
237232

238233
// Inserts "New (&W)"
239234
HMENU hMenu = PInvoke.CreatePopupMenu();
240235
hr = pNewMenu->QueryContextMenu(hMenu, 0, 1, 256, 0);
241-
if (hr.ThrowIfFailedOnDebug().Failed)
242-
return [];
236+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
243237

244-
// Invokes CNewMenu::_InitMenuPopup(), which populates the hSubMenu
238+
// Populates the hSubMenu
245239
HMENU hSubMenu = PInvoke.GetSubMenu(hMenu, 0);
246240
hr = pContextMenu2.Get()->HandleMenuMsg(PInvoke.WM_INITMENUPOPUP, (WPARAM)(nuint)hSubMenu.Value, 0);
247-
if (hr.ThrowIfFailedOnDebug().Failed)
248-
return [];
241+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
249242

250243
uint dwCount = unchecked((uint)PInvoke.GetMenuItemCount(hSubMenu));
251-
if (dwCount is unchecked((uint)-1))
252-
return [];
244+
if (dwCount is unchecked((uint)-1)) return null;
253245

254-
// Enumerates and populates the list
255-
List<WindowsContextMenuItem> shellNewItems = [];
246+
// Enumerates the menu items
247+
List<WindowsContextMenuItem> items = [];
256248
for (uint dwIndex = 0; dwIndex < dwCount; dwIndex++)
257249
{
258250
MENUITEMINFOW mii = default;
259251
mii.cbSize = (uint)sizeof(MENUITEMINFOW);
260-
mii.fMask = MENU_ITEM_MASK.MIIM_STRING | MENU_ITEM_MASK.MIIM_ID | MENU_ITEM_MASK.MIIM_STATE | MENU_ITEM_MASK.MIIM_TYPE;
252+
mii.fMask = MENU_ITEM_MASK.MIIM_STRING | MENU_ITEM_MASK.MIIM_ID | MENU_ITEM_MASK.MIIM_STATE | MENU_ITEM_MASK.MIIM_FTYPE;
261253
mii.dwTypeData = (char*)NativeMemory.Alloc(256U);
262254
mii.cch = 256;
263255

264256
if (PInvoke.GetMenuItemInfo(hSubMenu, dwIndex, true, &mii))
265257
{
266-
shellNewItems.Add(new(mii.wID, new(mii.dwTypeData), null, (WindowsContextMenuType)mii.fType, (WindowsContextMenuState)mii.fState));
258+
byte[]? rawImageData = null;
259+
GpBitmap* gpBitmap = ConvertHBITMAPToGpBitmap(mii.hbmpItem);
260+
if (gpBitmap is not null)
261+
rawImageData = ConvertGpBitmapToByteArray(gpBitmap);
262+
263+
items.Add(new(mii.wID, new(mii.dwTypeData), rawImageData, (WindowsContextMenuType)mii.fType, (WindowsContextMenuState)mii.fState));
267264
}
268265

269266
NativeMemory.Free(mii.dwTypeData);
270267
}
271268

272-
return shellNewItems;
269+
return items;
273270
}
274271

275272
public static bool InvokeShellNewItem(this IWindowsFolder @this, WindowsContextMenuItem item)
@@ -299,7 +296,7 @@ public static bool InvokeShellNewItem(this IWindowsFolder @this, WindowsContextM
299296
return false;
300297
}
301298

302-
public static IEnumerable<WindowsContextMenuItem> GetOpenWithMenuItems(this IWindowsFile @this)
299+
public static IEnumerable<WindowsContextMenuItem>? GetOpenWithMenuItems(this IWindowsFile @this)
303300
{
304301
HRESULT hr = default;
305302

@@ -312,37 +309,46 @@ public static IEnumerable<WindowsContextMenuItem> GetOpenWithMenuItems(this IWin
312309
using ComHeapPtr<ITEMIDLIST> pThisAbsolutePidl = default;
313310
ComHeapPtr<ITEMIDLIST> pThisRelativePidl = default;
314311

315-
PInvoke.CoCreateInstance(CLSID.CLSID_OpenWithMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)pOpenWithContextMenu.GetAddressOf());
312+
hr = PInvoke.CoCreateInstance(CLSID.CLSID_OpenWithMenu, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.IID_IContextMenu, (void**)pOpenWithContextMenu.GetAddressOf());
313+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
314+
316315
hr = pOpenWithContextMenu.Get()->QueryInterface(IID.IID_IShellExtInit, (void**)pShellExtInit.GetAddressOf());
316+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
317+
317318
hr = pOpenWithContextMenu.Get()->QueryInterface(IID.IID_IContextMenu2, (void**)pOpenWithContextMenu2.GetAddressOf());
319+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
318320

319321
// Get the absolute PIDL of the parent folder
320322
@this.ThisPtr->GetParent(pParentFolderShellItem.GetAddressOf());
321-
PInvoke.SHGetIDListFromObject((IUnknown*)pParentFolderShellItem.Get(), pParentAbsolutePidl.GetAddressOf());
323+
hr = PInvoke.SHGetIDListFromObject((IUnknown*)pParentFolderShellItem.Get(), pParentAbsolutePidl.GetAddressOf());
324+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
322325

323326
// Get the relative PIDL of the current item
324-
PInvoke.SHGetIDListFromObject((IUnknown*)@this.ThisPtr, pThisAbsolutePidl.GetAddressOf());
327+
hr = PInvoke.SHGetIDListFromObject((IUnknown*)@this.ThisPtr, pThisAbsolutePidl.GetAddressOf());
328+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
325329
pThisRelativePidl.Attach(PInvoke.ILFindLastID(pThisAbsolutePidl.Get()));
326330

327331
hr = PInvoke.SHCreateDataObject(pParentAbsolutePidl.Get(), 1U, pThisRelativePidl.GetAddressOf(), null, IID.IID_IDataObject, (void**)pDataObject.GetAddressOf());
332+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
328333

329-
// The 2nd parameter must not be null, others aren't used.
330334
hr = pShellExtInit.Get()->Initialize(null, pDataObject.Get(), HKEY.Null);
335+
if (hr.ThrowIfFailedOnDebug().Failed) return [];
331336

332-
// Inserts "New (&W)"
337+
// Inserts "Open With(&H)" or "Open With(&H)..."
333338
HMENU hMenu = PInvoke.CreatePopupMenu();
334339
hr = pOpenWithContextMenu.Get()->QueryContextMenu(hMenu, 0, 1, 256, 0);
340+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
335341

336-
// Invokes CNewMenu::_InitMenuPopup(), which populates the hSubMenu
342+
// Populates the hSubMenu
337343
HMENU hSubMenu = PInvoke.GetSubMenu(hMenu, 0);
338344
hr = pOpenWithContextMenu2.Get()->HandleMenuMsg(PInvoke.WM_INITMENUPOPUP, (WPARAM)(nuint)hSubMenu.Value, 0);
345+
if (hr.ThrowIfFailedOnDebug().Failed) return null;
339346

340347
uint dwCount = unchecked((uint)PInvoke.GetMenuItemCount(hSubMenu));
341-
if (dwCount is unchecked((uint)-1)) return [];
348+
if (dwCount is unchecked((uint)-1)) return null;
342349

350+
// Enumerates the menu items
343351
List<WindowsContextMenuItem> items = [];
344-
345-
// Enumerates and populates the list
346352
for (uint dwIndex = 0U; dwIndex < dwCount; dwIndex++)
347353
{
348354
MENUITEMINFOW mii = default;

0 commit comments

Comments
 (0)