Skip to content

Commit f7a1525

Browse files
authored
Merge pull request #10 from rgdevment/feat/pipelines
feat: add SonarCloud integration and CodeQL analysis workflow
2 parents 8cbdbf9 + 86ae2d0 commit f7a1525

10 files changed

Lines changed: 174 additions & 16 deletions

File tree

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
github: rgdevment
2+
buy_me_a_coffee: rgdevment

.github/workflows/ci.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,63 @@ jobs:
4242

4343
- name: Run Tests
4444
run: dotnet test CopyPaste.slnx -c Release -p:Platform=x64 --no-build --verbosity normal --logger "console;verbosity=detailed"
45+
46+
sonarcloud:
47+
name: SonarCloud
48+
runs-on: windows-latest
49+
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
50+
steps:
51+
- uses: actions/checkout@v4
52+
with:
53+
fetch-depth: 0
54+
55+
- name: Setup .NET
56+
uses: actions/setup-dotnet@v4
57+
with:
58+
dotnet-version: "10.0.x"
59+
dotnet-quality: "preview"
60+
61+
- name: Setup Java 17
62+
uses: actions/setup-java@v4
63+
with:
64+
distribution: "temurin"
65+
java-version: "17"
66+
67+
- name: Cache NuGet packages
68+
uses: actions/cache@v4
69+
with:
70+
path: ~/.nuget/packages
71+
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
72+
restore-keys: |
73+
${{ runner.os }}-nuget-
74+
75+
- name: Install SonarScanner
76+
run: dotnet tool install --global dotnet-sonarscanner
77+
78+
- name: SonarScanner Begin
79+
env:
80+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
81+
run: >
82+
dotnet sonarscanner begin
83+
/k:"${{ vars.SONAR_PROJECT_KEY }}"
84+
/o:"${{ vars.SONAR_ORGANIZATION }}"
85+
/d:sonar.host.url="https://sonarcloud.io"
86+
/d:sonar.token="${{ secrets.SONAR_TOKEN }}"
87+
/d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml"
88+
89+
- name: Restore dependencies
90+
run: dotnet restore -p:Platform=x64
91+
92+
- name: Build Solution
93+
run: dotnet build CopyPaste.slnx -c Release -p:Platform=x64 --no-restore
94+
95+
- name: Run Tests with Coverage
96+
run: >
97+
dotnet test CopyPaste.slnx -c Release -p:Platform=x64 --no-build
98+
--collect:"XPlat Code Coverage"
99+
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
100+
101+
- name: SonarScanner End
102+
env:
103+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
104+
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"

.github/workflows/codeql.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: CodeQL
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
- cron: '39 11 * * 5'
10+
11+
jobs:
12+
analyze:
13+
name: Analyze C#
14+
runs-on: windows-latest
15+
timeout-minutes: 120
16+
permissions:
17+
security-events: write
18+
packages: read
19+
actions: read
20+
contents: read
21+
22+
steps:
23+
- name: Checkout repository
24+
uses: actions/checkout@v4
25+
26+
- name: Setup .NET
27+
uses: actions/setup-dotnet@v4
28+
with:
29+
dotnet-version: "10.0.x"
30+
dotnet-quality: "preview"
31+
32+
- name: Cache NuGet packages
33+
uses: actions/cache@v4
34+
with:
35+
path: ~/.nuget/packages
36+
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
37+
restore-keys: |
38+
${{ runner.os }}-nuget-
39+
40+
- name: Initialize CodeQL
41+
uses: github/codeql-action/init@v4
42+
with:
43+
languages: csharp
44+
build-mode: manual
45+
46+
- name: Build projects (CodeQL traced)
47+
shell: pwsh
48+
run: |
49+
# Build Core and Listener libraries only.
50+
# CopyPaste.UI is excluded because WinRT source generators and the
51+
# XAML compiler are incompatible with the CodeQL build tracer.
52+
# Security-relevant code (SQLite, file I/O, networking, clipboard)
53+
# lives in Core and Listener.
54+
dotnet build CopyPaste.Core/CopyPaste.Core.csproj -c Release -p:Platform=x64
55+
dotnet build CopyPaste.Listener/CopyPaste.Listener.csproj -c Release -p:Platform=x64
56+
57+
- name: Perform CodeQL Analysis
58+
uses: github/codeql-action/analyze@v4
59+
with:
60+
category: '/language:csharp'

CopyPaste.Core.Tests/CopyPaste.Core.Tests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
<!-- CA1707: Underscores in identifiers are the project-wide naming convention -->
77
<!-- CA1054: Tests use string URLs to verify parsing behavior -->
88
<!-- CA1515: xUnit requires public test classes for discovery/reflection -->
9-
<NoWarn>IDE0022;CA1707;CA1054;CA1515</NoWarn>
9+
<!-- S4144: Test methods intentionally share similar structure with different data -->
10+
<!-- S2925: Thread.Sleep is needed for timing-sensitive integration tests -->
11+
<NoWarn>IDE0022;CA1707;CA1054;CA1515;S4144;S2925</NoWarn>
1012
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
1113
</PropertyGroup>
1214
<ItemGroup>

CopyPaste.Core.Tests/UpdateCheckerTests.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,11 @@ public void UpdateChecker_CanBeCreatedAndDisposed()
9797
{
9898
var checker = new UpdateChecker();
9999
checker.Dispose();
100+
100101
// Double dispose should not throw
101-
checker.Dispose();
102+
var exception = Record.Exception(() => checker.Dispose());
103+
104+
Assert.Null(exception);
102105
}
103106

104107
#endregion

CopyPaste.Listener.Tests/CopyPaste.Listener.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
<!-- CA1707: Underscores in identifiers are the project-wide naming convention -->
77
<!-- CA1054: Tests use string URLs to verify parsing behavior -->
88
<!-- CA1515: xUnit requires public test classes for discovery/reflection -->
9-
<NoWarn>IDE0022;CA1707;CA1054;CA1515</NoWarn>
9+
<!-- S4144: Test methods intentionally share similar structure with different data -->
10+
<NoWarn>IDE0022;CA1707;CA1054;CA1515;S4144</NoWarn>
1011
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
1112
</PropertyGroup>
1213
<ItemGroup>

CopyPaste.Listener.Tests/WindowsClipboardListenerTests.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -304,19 +304,27 @@ public void DetectFileCollectionType_NonExistentFile_ReturnsBasedOnExtension()
304304
[Fact]
305305
public void Dispose_CanBeCalledMultipleTimes_DoesNotThrow()
306306
{
307-
var listener = new WindowsClipboardListener(new StubClipboardService());
307+
using var listener = new WindowsClipboardListener(new StubClipboardService());
308308

309-
listener.Dispose();
310-
listener.Dispose();
311-
listener.Dispose();
309+
var exception = Record.Exception(() =>
310+
{
311+
listener.Dispose();
312+
listener.Dispose();
313+
listener.Dispose();
314+
});
315+
316+
Assert.Null(exception);
312317
}
313318

314319
[Fact]
315320
public void NewInstance_BeforeStart_CanBeDisposed()
316321
{
317322
// Listener that was never started should dispose cleanly
318-
var listener = new WindowsClipboardListener(new StubClipboardService());
319-
listener.Dispose();
323+
using var listener = new WindowsClipboardListener(new StubClipboardService());
324+
325+
var exception = Record.Exception(() => listener.Dispose());
326+
327+
Assert.Null(exception);
320328
}
321329

322330
#endregion
@@ -437,9 +445,11 @@ public void Dispose()
437445

438446
private sealed class StubClipboardService : IClipboardService
439447
{
448+
#pragma warning disable CS0067 // Events required by interface but unused in stub
440449
public event Action<ClipboardItem>? OnItemAdded;
441450
public event Action<ClipboardItem>? OnThumbnailReady;
442451
public event Action<ClipboardItem>? OnItemReactivated;
452+
#pragma warning restore CS0067
443453
public int PasteIgnoreWindowMs { get; set; } = 450;
444454

445455
public void AddText(string? text, ClipboardContentType type, string? source, byte[]? rtfBytes = null) { }
@@ -452,8 +462,5 @@ public void UpdatePin(Guid id, bool isPinned) { }
452462
public void UpdateLabelAndColor(Guid id, string? label, CardColor color) { }
453463
public ClipboardItem? MarkItemUsed(Guid id) => null;
454464
public void NotifyPasteInitiated(Guid itemId) { }
455-
456-
// Suppress unused event warnings
457-
internal void SuppressWarnings() { OnItemAdded?.Invoke(null!); OnThumbnailReady?.Invoke(null!); OnItemReactivated?.Invoke(null!); }
458465
}
459466
}

CopyPaste.UI.Tests/CopyPaste.UI.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
<IsTestProject>true</IsTestProject>
99
<!-- CA1707: Underscores in identifiers are the project-wide naming convention -->
1010
<!-- CA1515: xUnit requires public test classes for discovery/reflection -->
11-
<NoWarn>IDE0022;CA1707;CA1515</NoWarn>
11+
<!-- S4144: Test methods intentionally share similar structure with different data -->
12+
<NoWarn>IDE0022;CA1707;CA1515;S4144</NoWarn>
1213
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
1314
</PropertyGroup>
1415

CopyPaste.UI.Tests/MainViewModelTests.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,13 @@ public void Cleanup_MultipleCalls_DoesNotThrow()
391391
{
392392
var viewModel = CreateViewModel();
393393

394-
viewModel.Cleanup();
395-
viewModel.Cleanup();
394+
var exception = Record.Exception(() =>
395+
{
396+
viewModel.Cleanup();
397+
viewModel.Cleanup();
398+
});
399+
400+
Assert.Null(exception);
396401
}
397402

398403
#endregion

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@
1212
<a href="https://github.com/rgdevment/CopyPaste/actions">
1313
<img src="https://img.shields.io/github/actions/workflow/status/rgdevment/CopyPaste/ci.yml?style=flat-square&logo=github-actions&label=Build" alt="Build Status"/>
1414
</a>
15+
<a href="https://github.com/rgdevment/CopyPaste/actions/workflows/codeql.yml">
16+
<img src="https://img.shields.io/github/actions/workflow/status/rgdevment/CopyPaste/codeql.yml?style=flat-square&logo=github&label=CodeQL" alt="CodeQL"/>
17+
</a>
18+
<a href="https://sonarcloud.io/summary/overall?id=rgdevment_CopyPaste">
19+
<img src="https://img.shields.io/sonar/quality_gate/rgdevment_CopyPaste?server=https%3A%2F%2Fsonarcloud.io&style=flat-square&logo=sonarcloud&label=Quality%20Gate" alt="Quality Gate"/>
20+
</a>
21+
<a href="https://sonarcloud.io/component_measures?id=rgdevment_CopyPaste&metric=coverage">
22+
<img src="https://img.shields.io/sonar/coverage/rgdevment_CopyPaste?server=https%3A%2F%2Fsonarcloud.io&style=flat-square&logo=sonarcloud&label=Coverage" alt="Coverage"/>
23+
</a>
1524
<a href="https://github.com/rgdevment/CopyPaste/releases">
1625
<img src="https://img.shields.io/github/v/release/rgdevment/CopyPaste?include_prereleases&style=flat-square&label=Latest&color=0078D4" alt="Latest Release"/>
1726
</a>
@@ -28,7 +37,16 @@
2837
</p>
2938

3039
<p align="center">
31-
<sub>Also available as a <a href="https://github.com/rgdevment/CopyPaste/releases/latest">standalone installer</a> from GitHub Releases.</sub>
40+
<sub><strong>Also available as a <a href="https://github.com/rgdevment/CopyPaste/releases/latest">standalone installer</a> from GitHub Releases.</strong></sub>
41+
</p>
42+
43+
<p>
44+
<a href="https://github.com/sponsors/rgdevment">
45+
<img src="https://img.shields.io/badge/Sponsor-♥-ea4aaa?style=flat-square&logo=github" alt="GitHub Sponsors"/>
46+
</a>
47+
<a href="https://buymeacoffee.com/rgdevment">
48+
<img src="https://img.shields.io/badge/Buy%20Me%20a%20Coffee-☕-FFDD00?style=flat-square&logo=buy-me-a-coffee&logoColor=black" alt="Buy Me a Coffee"/>
49+
</a>
3250
</p>
3351
</div>
3452

0 commit comments

Comments
 (0)