Skip to content

Commit 6e4c43c

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

File tree

4 files changed

+182
-75
lines changed

4 files changed

+182
-75
lines changed

.github/workflows/ci.yml

Lines changed: 74 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ 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_PATH: '${{ github.workspace }}\tests\Files.UnitTests\Files.UnitTests.csproj'
4041
ARTIFACTS_STAGING_DIR: '${{ github.workspace }}\artifacts'
4142
APPX_PACKAGE_DIR: '${{ github.workspace }}\artifacts\AppxPackages'
4243
APPX_SELFSIGNED_CERT_PATH: '${{ github.workspace }}\.github\workflows\FilesApp_SelfSigned.pfx'
@@ -118,7 +119,7 @@ jobs:
118119
-p:PublishReadyToRun=true `
119120
-v:quiet
120121
121-
- if: env.CONFIGURATION != env.AUTOMATED_TESTS_CONFIGURATION || env.ARCHITECTURE != env.AUTOMATED_TESTS_ARCHITECTURE
122+
- if: env.CONFIGURATION != env.TESTS_CONFIGURATION || env.ARCHITECTURE != env.TESTS_ARCHITECTURE
122123
name: Build Files
123124
run: |
124125
msbuild `
@@ -129,11 +130,11 @@ jobs:
129130
-p:AppxBundle=Never `
130131
-v:quiet
131132
132-
- if: env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION && env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE
133+
- if: env.CONFIGURATION == env.TESTS_CONFIGURATION && env.ARCHITECTURE == env.TESTS_ARCHITECTURE
133134
name: Create self signed cert as a pfx file
134135
run: ./.github/scripts/Generate-SelfCertPfx.ps1 -Destination "$env:APPX_SELFSIGNED_CERT_PATH"
135136

136-
- if: env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION && env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE
137+
- if: env.CONFIGURATION == env.TESTS_CONFIGURATION && env.ARCHITECTURE == env.TESTS_ARCHITECTURE
137138
name: Build & package Files
138139
run: |
139140
msbuild `
@@ -142,7 +143,7 @@ jobs:
142143
-t:_GenerateAppxPackage `
143144
-p:Configuration=$env:CONFIGURATION `
144145
-p:Platform=$env:ARCHITECTURE `
145-
-p:AppxBundlePlatforms=$env:AUTOMATED_TESTS_ARCHITECTURE `
146+
-p:AppxBundlePlatforms=$env:TESTS_ARCHITECTURE `
146147
-p:AppxBundle=Always `
147148
-p:UapAppxPackageBuildMode=SideloadOnly `
148149
-p:AppxPackageDir=$env:APPX_PACKAGE_DIR `
@@ -152,24 +153,24 @@ jobs:
152153
-p:PackageCertificateThumbprint="" `
153154
-v:quiet
154155
155-
- if: env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE && env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION
156+
- if: env.ARCHITECTURE == env.TESTS_ARCHITECTURE && env.CONFIGURATION == env.TESTS_CONFIGURATION
156157
name: Build interaction tests
157158
run: |
158-
msbuild $env:AUTOMATED_TESTS_PROJECT_PATH `
159+
msbuild $env:AXE_TESTS_PROJECT_PATH `
159160
-t:Build `
160161
-p:Configuration=$env:CONFIGURATION `
161-
-p:Platform=$env:AUTOMATED_TESTS_ARCHITECTURE `
162+
-p:Platform=$env:TESTS_ARCHITECTURE `
162163
-v:quiet
163164
164-
- if: env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE && env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION
165+
- if: env.ARCHITECTURE == env.TESTS_ARCHITECTURE && env.CONFIGURATION == env.TESTS_CONFIGURATION
165166
name: Copy tests bin to the artifacts dir
166167
shell: pwsh
167168
run: |
168169
Copy-Item `
169-
-Path "$env:AUTOMATED_TESTS_PROJECT_DIR\bin" `
170-
-Destination "$env:AUTOMATED_TESTS_ASSEMBLY_DIR" -Recurse
170+
-Path "$env:AXE_TESTS_PROJECT_DIR\bin" `
171+
-Destination "$env:TESTS_ASSEMBLY_DIR" -Recurse
171172
172-
- if: env.ARCHITECTURE == env.AUTOMATED_TESTS_ARCHITECTURE && env.CONFIGURATION == env.AUTOMATED_TESTS_CONFIGURATION
173+
- if: env.ARCHITECTURE == env.TESTS_ARCHITECTURE && env.CONFIGURATION == env.TESTS_CONFIGURATION
173174
name: Upload the packages to the Artifacts
174175
uses: actions/upload-artifact@v4
175176
with:
@@ -189,6 +190,7 @@ jobs:
189190
platform: [x64]
190191
env:
191192
CONFIGURATION: ${{ matrix.configuration }}
193+
PLATFORM: ${{ matrix.platform }}
192194
permissions:
193195
contents: read
194196
pull-requests: write
@@ -209,26 +211,21 @@ jobs:
209211
- name: Download the packages from the Artifacts
210212
uses: actions/download-artifact@v4
211213
with:
212-
name: 'Appx Packages (${{ env.CONFIGURATION }}, ${{ env.AUTOMATED_TESTS_ARCHITECTURE }})'
214+
name: 'Appx Packages (${{ env.CONFIGURATION }}, ${{ env.PLATFORM }})'
213215
path: ${{ env.ARTIFACTS_STAGING_DIR }}
214216

215-
- name: Install Files
217+
- name: Prepare for the tests
216218
shell: powershell
217219
run: |
218220
Set-Location "$env:APPX_PACKAGE_DIR"
219221
$AppxPackageBundleDir = Get-ChildItem -Filter Files.Package_*_Test -Name
220222
Set-Location $AppxPackageBundleDir
221223
./Install.ps1 -Force
222224
Get-AppxPackage
225+
Set-DisplayResolution -Width 1920 -Height 1080 -Force
226+
Start-Process -FilePath "$env:WINAPPDRIVER_EXE86_PATH"
223227
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
228+
# Retry the tests if first attempt fails
232229
- name: Run interaction tests
233230
uses: nick-fields/retry@v3
234231
with:
@@ -237,30 +234,75 @@ jobs:
237234
shell: pwsh
238235
command: |
239236
dotnet test `
240-
$env:AUTOMATED_TESTS_ASSEMBLY_DIR\**\Files.InteractionTests.dll `
241-
--logger "trx;LogFileName=$env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.trx"
237+
$env:TESTS_ASSEMBLY_DIR\**\Files.InteractionTests.dll `
238+
--logger "trx;LogFileName=$env:TESTS_ASSEMBLY_DIR\testResults.trx"
242239
243240
- if: github.event_name == 'pull_request'
244241
uses: geekyeggo/delete-artifact@v5
245242
with:
246243
name: '*'
247244

245+
unit-test:
246+
247+
if: github.repository_owner == 'files-community' && always()
248+
249+
needs: [build]
250+
runs-on: windows-latest
251+
strategy:
252+
fail-fast: false
253+
matrix:
254+
configuration: [Release]
255+
platform: [x64]
256+
env:
257+
CONFIGURATION: ${{ matrix.configuration }}
258+
259+
steps:
260+
261+
- if: contains(join(needs.*.result, ','), 'failure')
262+
name: Fail if necessary
263+
run: exit 1
264+
265+
- name: Checkout the repository
266+
uses: actions/checkout@v4
267+
- name: Setup .NET
268+
uses: actions/setup-dotnet@v4
269+
with:
270+
global-json-file: global.json
271+
272+
- name: Build unit tests
273+
run: |
274+
msbuild $env:UNIT_TESTS_PROJECT_PATH `
275+
-t:Build `
276+
-p:Configuration=$env:CONFIGURATION `
277+
-p:Platform=$env:PLATFORM `
278+
-v:quiet
279+
280+
# Retry the tests if first attempt fails
281+
- name: Run unit tests
282+
uses: nick-fields/retry@v3
283+
with:
284+
timeout_minutes: 15
285+
max_attempts: 2
286+
shell: pwsh
287+
command: |
288+
dotnet test $env:TESTS_ASSEMBLY_DIR\**\Files.UnitTests.dll`
289+
248290
# - name: Generate markdown from the tests result
249291
# shell: pwsh
250292
# run: |
251293
# . './scripts/Convert-TrxToMarkdown.ps1' `
252-
# -Source "$env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.trx" `
253-
# -Destination "$env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.md"
294+
# -Source "$env:TESTS_ASSEMBLY_DIR\testResults.trx" `
295+
# -Destination "$env:TESTS_ASSEMBLY_DIR\testResults.md"
254296
# env:
255297
# PULL_REQUEST_ID: ${{ github.event.pull_request_id }}
256298

257299
# - name: Display the markdown on the output (temp)
258300
# shell: pwsh
259301
# run: |
260-
# Get-Content $env:AUTOMATED_TESTS_ASSEMBLY_DIR\testResults.md
302+
# Get-Content $env:TESTS_ASSEMBLY_DIR\testResults.md
261303

262304
# - name: Publish tests result
263305
# uses: marocchino/sticky-pull-request-comment@v2
264306
# with:
265307
# header: test-result
266-
# path: '${{ env.AUTOMATED_TESTS_ASSEMBLY_DIR }}\testResults.md'
308+
# path: '${{ env.TESTS_ASSEMBLY_DIR }}\testResults.md'

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)