From fc08735caeed622fee9deb6b32cb7491d686e857 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 00:36:41 +0200
Subject: [PATCH 01/33] Update sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 53 ++++++--------------------------
1 file changed, 9 insertions(+), 44 deletions(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index e7840696..72940bfd 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -1,31 +1,3 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# This workflow helps you trigger a SonarCloud analysis of your code and populates
-# GitHub Code Scanning alerts with the vulnerabilities found.
-# Free for open source project.
-
-# 1. Login to SonarCloud.io using your GitHub account
-
-# 2. Import your project on SonarCloud
-# * Add your GitHub organization first, then add your repository as a new project.
-# * Please note that many languages are eligible for automatic analysis,
-# which means that the analysis will start automatically without the need to set up GitHub Actions.
-# * This behavior can be changed in Administration > Analysis Method.
-#
-# 3. Follow the SonarCloud in-product tutorial
-# * a. Copy/paste the Project Key and the Organization Key into the args parameter below
-# (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
-#
-# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
-# (On SonarCloud, click on your avatar on top-right > My account > Security
-# or go directly to https://sonarcloud.io/account/security/)
-
-# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
-# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
-
name: SonarCloud analysis
on:
@@ -36,28 +8,27 @@ on:
workflow_dispatch:
permissions:
- pull-requests: read # allows SonarCloud to decorate PRs with analysis results
+ pull-requests: read
jobs:
sonar-check:
name: Sonar Check
- runs-on: windows-latest # безпечно для будь-яких .NET проектів
+ runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- with: { fetch-depth: 0 }
+ with:
+ fetch-depth: 0
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- # 1) BEGIN: SonarScanner for .NET
- name: SonarScanner Begin
run: |
dotnet tool install --global dotnet-sonarscanner
- echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
dotnet sonarscanner begin `
- /k:"ppanchen_NetSdrClient" `
- /o:"ppanchen" `
+ /d:sonar.projectKey="ppanchen_NetSdrClient" `
+ /d:sonar.organization="ppanchen-org" `
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
/d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
/d:sonar.cpd.cs.minimumTokens=40 `
@@ -65,19 +36,13 @@ jobs:
/d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml `
/d:sonar.qualitygate.wait=true
shell: pwsh
- # 2) BUILD & TEST
+
- name: Restore
run: dotnet restore NetSdrClient.sln
+
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
- #- name: Tests with coverage (OpenCover)
- # run: |
- # dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
- # /p:CollectCoverage=true `
- # /p:CoverletOutput=TestResults/coverage.xml `
- # /p:CoverletOutputFormat=opencover
- # shell: pwsh
- # 3) END: SonarScanner
+
- name: SonarScanner End
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
shell: pwsh
From 1bb56023910d41ae0af7b34249c9e9a25db3f7e1 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:03:01 +0200
Subject: [PATCH 02/33] Move 'UdpTimedSender' into a named namespace.Program.cs
---
EchoTcpServer/Program.cs | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs
index 5966c579..7a6f595a 100644
--- a/EchoTcpServer/Program.cs
+++ b/EchoTcpServer/Program.cs
@@ -112,21 +112,22 @@ public static async Task Main(string[] args)
}
}
-
-public class UdpTimedSender : IDisposable
+namespace EchoServerNamespace
{
- private readonly string _host;
- private readonly int _port;
- private readonly UdpClient _udpClient;
- private Timer _timer;
-
- public UdpTimedSender(string host, int port)
+ public class UdpTimedSender : IDisposable
{
- _host = host;
- _port = port;
- _udpClient = new UdpClient();
- }
+ private readonly string _host;
+ private readonly int _port;
+ private readonly UdpClient _udpClient;
+ private Timer _timer;
+ public UdpTimedSender(string host, int port)
+ {
+ _host = host;
+ _port = port;
+ _udpClient = new UdpClient();
+ }
+ }
public void StartSending(int intervalMilliseconds)
{
if (_timer != null)
@@ -170,4 +171,4 @@ public void Dispose()
StopSending();
_udpClient.Dispose();
}
-}
\ No newline at end of file
+}
From 165f3ac7bedfbe0521344f85036a33856ee08c2a Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:11:05 +0200
Subject: [PATCH 03/33] static handle Program.cs
---
EchoTcpServer/Program.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs
index 7a6f595a..7f47378b 100644
--- a/EchoTcpServer/Program.cs
+++ b/EchoTcpServer/Program.cs
@@ -47,7 +47,7 @@ public async Task StartAsync()
Console.WriteLine("Server shutdown.");
}
- private async Task HandleClientAsync(TcpClient client, CancellationToken token)
+ private static async Task HandleClientAsync(TcpClient client, CancellationToken token)
{
using (NetworkStream stream = client.GetStream())
{
From 3b5c4e45a7f9beabb44406e34c787f9e5236797b Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:22:40 +0200
Subject: [PATCH 04/33] coverlet.msbuild sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index e7840696..c90de044 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -70,14 +70,16 @@ jobs:
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
- #- name: Tests with coverage (OpenCover)
- # run: |
- # dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
- # /p:CollectCoverage=true `
- # /p:CoverletOutput=TestResults/coverage.xml `
- # /p:CoverletOutputFormat=opencover
- # shell: pwsh
- # 3) END: SonarScanner
+ name: Tests with coverage (OpenCover)
+ run: |
+ dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
+ /p:CollectCoverage=true `
+ /p:CoverletOutput=TestResults/coverage.xml `
+ /p:CoverletOutputFormat=opencover
+ shell: pwsh
+ 3) END: SonarScanner
- name: SonarScanner End
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
shell: pwsh
+
+
From 0b1b87bdbf6901a0460b3bedd63cd2b79702e69d Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:24:00 +0200
Subject: [PATCH 05/33] coverage sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index c90de044..1236c4cc 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -70,14 +70,14 @@ jobs:
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
- name: Tests with coverage (OpenCover)
- run: |
- dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
- /p:CollectCoverage=true `
- /p:CoverletOutput=TestResults/coverage.xml `
- /p:CoverletOutputFormat=opencover
- shell: pwsh
- 3) END: SonarScanner
+ - name: Tests with coverage (OpenCover)
+ run: |
+ dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
+ /p:CollectCoverage=true `
+ /p:CoverletOutput=TestResults/coverage.xml `
+ /p:CoverletOutputFormat=opencover
+ shell: pwsh
+ # 3) END: SonarScanner
- name: SonarScanner End
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
shell: pwsh
From 6e8bc311ba070e8c50937217d1f8ddf485efee8f Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:37:34 +0200
Subject: [PATCH 06/33] coverage sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index 1236c4cc..a083dd99 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -70,7 +70,7 @@ jobs:
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
- - name: Tests with coverage (OpenCover)
+ - name: Tests with coverage (OpenCover)
run: |
dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
/p:CollectCoverage=true `
From 2f7289c20a20db2e677bbcfd1a5b9c7845698d75 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:38:03 +0200
Subject: [PATCH 07/33] coverage sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index 1236c4cc..a083dd99 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -70,7 +70,7 @@ jobs:
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
- - name: Tests with coverage (OpenCover)
+ - name: Tests with coverage (OpenCover)
run: |
dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
/p:CollectCoverage=true `
From 9d333e20121c06745551cb5b59d13a89f5dce5ef Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:43:46 +0200
Subject: [PATCH 08/33] Update NetSdrClientAppTests.csproj
---
NetSdrClientAppTests/NetSdrClientAppTests.csproj | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
index 3cbc46af..e1f5e652 100644
--- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj
+++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
@@ -1,17 +1,18 @@
-
net8.0
enable
enable
-
false
true
-
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
@@ -19,11 +20,10 @@
-
+
-
From a8c55e879fa9a9f0986f5f4dcbc14c4cb0f38b1b Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:44:07 +0200
Subject: [PATCH 09/33] Update NetSdrClientAppTests.csproj
---
NetSdrClientAppTests/NetSdrClientAppTests.csproj | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/NetSdrClientAppTests/NetSdrClientAppTests.csproj b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
index 3cbc46af..e1f5e652 100644
--- a/NetSdrClientAppTests/NetSdrClientAppTests.csproj
+++ b/NetSdrClientAppTests/NetSdrClientAppTests.csproj
@@ -1,17 +1,18 @@
-
net8.0
enable
enable
-
false
true
-
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
@@ -19,11 +20,10 @@
-
+
-
From 77a217eea9bbe5a50686a24de1cd3ffbf9987ac1 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 01:47:37 +0200
Subject: [PATCH 10/33] Update sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index a083dd99..14308d2f 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -66,17 +66,26 @@ jobs:
/d:sonar.qualitygate.wait=true
shell: pwsh
# 2) BUILD & TEST
- - name: Restore
+ - name: Restore
run: dotnet restore NetSdrClient.sln
- name: Build
run: dotnet build NetSdrClient.sln -c Release --no-restore
- - name: Tests with coverage (OpenCover)
- run: |
- dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
- /p:CollectCoverage=true `
- /p:CoverletOutput=TestResults/coverage.xml `
- /p:CoverletOutputFormat=opencover
- shell: pwsh
+ #coverage
+ - name: Test with coverage
+ run: |
+ dotnet test NetSdrClient.Tests/NetSdrClient.Tests.csproj -c Release \
+ --logger "trx;LogFileName=test-results.trx" \
+ /p:CollectCoverage=true \
+ /p:CoverletOutput=TestResults/ \
+ /p:CoverletOutputFormat=opencover \
+ /p:Exclude="[nunit.*]*%2c[*.Tests]*%2c[Moq]*%2c[NUnit.*]*"
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-coverage-results
+ path: NetSdrClient.Tests/TestResults/
+ retention-days: 30
# 3) END: SonarScanner
- name: SonarScanner End
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
From 0574e6652cde8b0f19f7d7d7e3065f32fc504abf Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 02:00:26 +0200
Subject: [PATCH 11/33] Update sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 125 ++++++++++++++-----------------
1 file changed, 55 insertions(+), 70 deletions(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index a083dd99..ed2f0a23 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -1,81 +1,66 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# This workflow helps you trigger a SonarCloud analysis of your code and populates
-# GitHub Code Scanning alerts with the vulnerabilities found.
-# Free for open source project.
-
-# 1. Login to SonarCloud.io using your GitHub account
-
-# 2. Import your project on SonarCloud
-# * Add your GitHub organization first, then add your repository as a new project.
-# * Please note that many languages are eligible for automatic analysis,
-# which means that the analysis will start automatically without the need to set up GitHub Actions.
-# * This behavior can be changed in Administration > Analysis Method.
-#
-# 3. Follow the SonarCloud in-product tutorial
-# * a. Copy/paste the Project Key and the Organization Key into the args parameter below
-# (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
-#
-# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
-# (On SonarCloud, click on your avatar on top-right > My account > Security
-# or go directly to https://sonarcloud.io/account/security/)
-
-# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
-# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
-
-name: SonarCloud analysis
-
+name: SonarQube
on:
push:
- branches: [ "master" ]
+ branches:
+ - master
pull_request:
- branches: [ "master" ]
- workflow_dispatch:
-
-permissions:
- pull-requests: read # allows SonarCloud to decorate PRs with analysis results
-
+ types: [opened, synchronize, reopened]
jobs:
- sonar-check:
- name: Sonar Check
- runs-on: windows-latest # безпечно для будь-яких .NET проектів
+ build:
+ name: Build and analyze
+ runs-on: windows-latest
steps:
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: 17
+ distribution: 'zulu' # Alternative distribution options are available.
- uses: actions/checkout@v4
- with: { fetch-depth: 0 }
-
- - uses: actions/setup-dotnet@v4
with:
- dotnet-version: '8.0.x'
-
- # 1) BEGIN: SonarScanner for .NET
- - name: SonarScanner Begin
+ fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
+ - name: Cache SonarQube Cloud packages
+ uses: actions/cache@v4
+ with:
+ path: ~\sonar\cache
+ key: ${{ runner.os }}-sonar
+ restore-keys: ${{ runner.os }}-sonar
+ - name: Cache SonarQube Cloud scanner
+ id: cache-sonar-scanner
+ uses: actions/cache@v4
+ with:
+ path: ${{ runner.temp }}\scanner
+ key: ${{ runner.os }}-sonar-scanner
+ restore-keys: ${{ runner.os }}-sonar-scanner
+ - name: Install SonarQube Cloud scanner
+ if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
+ shell: powershell
run: |
- dotnet tool install --global dotnet-sonarscanner
- echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
- dotnet sonarscanner begin `
- /k:"ppanchen_NetSdrClient" `
- /o:"ppanchen" `
- /d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
- /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
- /d:sonar.cpd.cs.minimumTokens=40 `
- /d:sonar.cpd.cs.minimumLines=5 `
- /d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml `
- /d:sonar.qualitygate.wait=true
- shell: pwsh
- # 2) BUILD & TEST
- - name: Restore
- run: dotnet restore NetSdrClient.sln
- - name: Build
- run: dotnet build NetSdrClient.sln -c Release --no-restore
- - name: Tests with coverage (OpenCover)
- run: |
- dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
- /p:CollectCoverage=true `
- /p:CoverletOutput=TestResults/coverage.xml `
- /p:CoverletOutputFormat=opencover
+ New-Item -Path ${{ runner.temp }}\scanner -ItemType Directory
+ dotnet tool update dotnet-sonarscanner --tool-path ${{ runner.temp }}\scanner
+ - name: Build and analyze
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ shell: powershell
+ run: |
+ ${{ runner.temp }}\scanner\dotnet-sonarscanner begin /k:"Yegres546_NetSdrClient" /o:"yegres546-1" /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
+ dotnet build
+ ${{ runner.temp }}\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
+
+ - name: Test with coverage
+ run: |
+ dotnet test NetSdrClient.Tests/NetSdrClient.Tests.csproj -c Release \
+ --logger "trx;LogFileName=test-results.trx" \
+ /p:CollectCoverage=true \
+ /p:CoverletOutput=TestResults/ \
+ /p:CoverletOutputFormat=opencover \
+ /p:Exclude="[nunit.*]*%2c[*.Tests]*%2c[Moq]*%2c[NUnit.*]*"
+
+ - name: Upload test results
+ uses: actions/upload-artifact@v4
+ with:
+ name: test-coverage-results
+ path: NetSdrClient.Tests/TestResults/
+ retention-days: 30
shell: pwsh
# 3) END: SonarScanner
- name: SonarScanner End
From a9464dc9ca1ecf7209e0eaa0550cae1896d0da55 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:37:59 +0200
Subject: [PATCH 12/33] ref sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 127 +++++++++++++++++--------------
1 file changed, 70 insertions(+), 57 deletions(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index ed2f0a23..aca8b48d 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -1,70 +1,83 @@
-name: SonarQube
+# This workflow uses actions that are not certified by GitHub.
+# They are provided by a third-party and are governed by
+# separate terms of service, privacy policy, and support
+# documentation.
+
+# This workflow helps you trigger a SonarCloud analysis of your code and populates
+# GitHub Code Scanning alerts with the vulnerabilities found.
+# Free for open source project.
+
+# 1. Login to SonarCloud.io using your GitHub account
+
+# 2. Import your project on SonarCloud
+# * Add your GitHub organization first, then add your repository as a new project.
+# * Please note that many languages are eligible for automatic analysis,
+# which means that the analysis will start automatically without the need to set up GitHub Actions.
+# * This behavior can be changed in Administration > Analysis Method.
+#
+# 3. Follow the SonarCloud in-product tutorial
+# * a. Copy/paste the Project Key and the Organization Key into the args parameter below
+# (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
+#
+# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
+# (On SonarCloud, click on your avatar on top-right > My account > Security
+# or go directly to https://sonarcloud.io/account/security/)
+
+# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
+# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
+
+name: SonarCloud analysis
+
on:
push:
- branches:
- - master
+ branches: [ "master" ]
pull_request:
- types: [opened, synchronize, reopened]
+ branches: [ "master" ]
+ workflow_dispatch:
+
+permissions:
+ pull-requests: read # allows SonarCloud to decorate PRs with analysis results
+
jobs:
- build:
- name: Build and analyze
- runs-on: windows-latest
+ sonar-check:
+ name: Sonar Check
+ runs-on: windows-latest # безпечно для будь-яких .NET проектів
steps:
- - name: Set up JDK 17
- uses: actions/setup-java@v4
- with:
- java-version: 17
- distribution: 'zulu' # Alternative distribution options are available.
- uses: actions/checkout@v4
+ with: { fetch-depth: 0 }
+
+ - uses: actions/setup-dotnet@v4
with:
- fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- - name: Cache SonarQube Cloud packages
- uses: actions/cache@v4
- with:
- path: ~\sonar\cache
- key: ${{ runner.os }}-sonar
- restore-keys: ${{ runner.os }}-sonar
- - name: Cache SonarQube Cloud scanner
- id: cache-sonar-scanner
- uses: actions/cache@v4
- with:
- path: ${{ runner.temp }}\scanner
- key: ${{ runner.os }}-sonar-scanner
- restore-keys: ${{ runner.os }}-sonar-scanner
- - name: Install SonarQube Cloud scanner
- if: steps.cache-sonar-scanner.outputs.cache-hit != 'true'
- shell: powershell
+ dotnet-version: '8.0.x'
+
+ # 1) BEGIN: SonarScanner for .NET
+ - name: SonarScanner Begin
run: |
- New-Item -Path ${{ runner.temp }}\scanner -ItemType Directory
- dotnet tool update dotnet-sonarscanner --tool-path ${{ runner.temp }}\scanner
- - name: Build and analyze
- env:
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- shell: powershell
+ dotnet tool install --global dotnet-sonarscanner
+ echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
+ dotnet sonarscanner begin `
+ /k:"ppanchen_NetSdrClient" `
+ /o:"ppanchen" `
+ /d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
+ /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
+ /d:sonar.cpd.cs.minimumTokens=40 `
+ /d:sonar.cpd.cs.minimumLines=5 `
+ /d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml `
+ /d:sonar.qualitygate.wait=true
+ shell: pwsh
+ # 2) BUILD & TEST
+ - name: Restore
+ run: dotnet restore NetSdrClient.sln
+ - name: Build
+ run: dotnet build NetSdrClient.sln -c Release --no-restore
+ - name: Tests with coverage (OpenCover)
run: |
- ${{ runner.temp }}\scanner\dotnet-sonarscanner begin /k:"Yegres546_NetSdrClient" /o:"yegres546-1" /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
- dotnet build
- ${{ runner.temp }}\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
-
- - name: Test with coverage
- run: |
- dotnet test NetSdrClient.Tests/NetSdrClient.Tests.csproj -c Release \
- --logger "trx;LogFileName=test-results.trx" \
- /p:CollectCoverage=true \
- /p:CoverletOutput=TestResults/ \
- /p:CoverletOutputFormat=opencover \
- /p:Exclude="[nunit.*]*%2c[*.Tests]*%2c[Moq]*%2c[NUnit.*]*"
-
- - name: Upload test results
- uses: actions/upload-artifact@v4
- with:
- name: test-coverage-results
- path: NetSdrClient.Tests/TestResults/
- retention-days: 30
- shell: pwsh
+ dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
+ /p:CollectCoverage=true `
+ /p:CoverletOutput=TestResults/coverage.xml `
+ /p:CoverletOutputFormat=opencover
+ shell: pwsh
# 3) END: SonarScanner
- name: SonarScanner End
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
shell: pwsh
-
-
From 49bdb5caad8d2a5bd144b17d4565224f8243f943 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:39:15 +0200
Subject: [PATCH 13/33] Create coverlet.msbuild
---
coverlet.msbuild | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 coverlet.msbuild
diff --git a/coverlet.msbuild b/coverlet.msbuild
new file mode 100644
index 00000000..5f923fc2
--- /dev/null
+++ b/coverlet.msbuild
@@ -0,0 +1,3 @@
+dotnet add NetSdrClientAppTests package coverlet.msbuild
+dotnet add NetSdrClientAppTests package Microsoft.NET.Test.Sdk
+dotnet test NetSdrClientAppTests -c Release /p:CollectCoverage=true /p:CoverletOutput=TestResults/coverage.xml /p:CoverletOutputFormat=opencover
From 9d4bea6dfbd8b2d6218cce2a4657144d7183e68a Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:46:18 +0200
Subject: [PATCH 14/33] Create SDRDeviceTests.cs
---
NetSdrClient.Tests/SDRDeviceTests.cs | 55 ++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 NetSdrClient.Tests/SDRDeviceTests.cs
diff --git a/NetSdrClient.Tests/SDRDeviceTests.cs b/NetSdrClient.Tests/SDRDeviceTests.cs
new file mode 100644
index 00000000..89295dd1
--- /dev/null
+++ b/NetSdrClient.Tests/SDRDeviceTests.cs
@@ -0,0 +1,55 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NetSdrClient.Models;
+
+namespace NetSdrClient.Tests
+{
+ [TestClass]
+ public class SDRDeviceTests
+ {
+ [TestMethod]
+ public void SDRDevice_Constructor_ShouldInitializeProperties()
+ {
+ // Arrange & Act
+ var device = new SDRDevice();
+
+ // Assert
+ Assert.IsNotNull(device);
+ Assert.AreEqual(0, device.Id);
+ Assert.IsNull(device.Name);
+ Assert.IsNull(device.Description);
+ Assert.IsFalse(device.IsConnected);
+ }
+
+ [TestMethod]
+ public void SDRDevice_Properties_ShouldSetAndGetCorrectly()
+ {
+ // Arrange
+ var device = new SDRDevice();
+
+ // Act
+ device.Id = 1;
+ device.Name = "Test Device";
+ device.Description = "Test Description";
+ device.IsConnected = true;
+
+ // Assert
+ Assert.AreEqual(1, device.Id);
+ Assert.AreEqual("Test Device", device.Name);
+ Assert.AreEqual("Test Description", device.Description);
+ Assert.IsTrue(device.IsConnected);
+ }
+
+ [TestMethod]
+ public void SDRDevice_ToString_ShouldReturnName()
+ {
+ // Arrange
+ var device = new SDRDevice { Name = "Test SDR" };
+
+ // Act
+ var result = device.ToString();
+
+ // Assert
+ Assert.AreEqual("Test SDR", result);
+ }
+ }
+}
From d0da030c4292c8ef1f0a624edc442203896cf2d0 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:46:37 +0200
Subject: [PATCH 15/33] Create SDRClientTests.cs
---
NetSdrClient.Tests/SDRClientTests.cs | 97 ++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
create mode 100644 NetSdrClient.Tests/SDRClientTests.cs
diff --git a/NetSdrClient.Tests/SDRClientTests.cs b/NetSdrClient.Tests/SDRClientTests.cs
new file mode 100644
index 00000000..05cb679d
--- /dev/null
+++ b/NetSdrClient.Tests/SDRClientTests.cs
@@ -0,0 +1,97 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NetSdrClient.Models;
+using NetSdrClient.Services;
+using System.Linq;
+
+namespace NetSdrClient.Tests
+{
+ [TestClass]
+ public class SDRClientTests
+ {
+ [TestMethod]
+ public void SDRClient_Constructor_ShouldInitializeDevicesList()
+ {
+ // Arrange & Act
+ var client = new SDRClient();
+
+ // Assert
+ Assert.IsNotNull(client.Devices);
+ Assert.AreEqual(0, client.Devices.Count);
+ }
+
+ [TestMethod]
+ public void AddDevice_ShouldAddDeviceToList()
+ {
+ // Arrange
+ var client = new SDRClient();
+ var device = new SDRDevice { Id = 1, Name = "Test Device" };
+
+ // Act
+ client.AddDevice(device);
+
+ // Assert
+ Assert.AreEqual(1, client.Devices.Count);
+ Assert.AreEqual(device, client.Devices[0]);
+ }
+
+ [TestMethod]
+ public void RemoveDevice_ShouldRemoveDeviceFromList()
+ {
+ // Arrange
+ var client = new SDRClient();
+ var device = new SDRDevice { Id = 1, Name = "Test Device" };
+ client.AddDevice(device);
+
+ // Act
+ var result = client.RemoveDevice(1);
+
+ // Assert
+ Assert.IsTrue(result);
+ Assert.AreEqual(0, client.Devices.Count);
+ }
+
+ [TestMethod]
+ public void RemoveDevice_WithInvalidId_ShouldReturnFalse()
+ {
+ // Arrange
+ var client = new SDRClient();
+
+ // Act
+ var result = client.RemoveDevice(999);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ public void GetDevice_ShouldReturnCorrectDevice()
+ {
+ // Arrange
+ var client = new SDRClient();
+ var device1 = new SDRDevice { Id = 1, Name = "Device 1" };
+ var device2 = new SDRDevice { Id = 2, Name = "Device 2" };
+ client.AddDevice(device1);
+ client.AddDevice(device2);
+
+ // Act
+ var result = client.GetDevice(2);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.AreEqual("Device 2", result.Name);
+ }
+
+ [TestMethod]
+ public void GetDevice_WithInvalidId_ShouldReturnNull()
+ {
+ // Arrange
+ var client = new SDRClient();
+
+ // Act
+ var result = client.GetDevice(999);
+
+ // Assert
+ Assert.IsNull(result);
+ }
+ }
+}
From e9bfced7d57689589c6e83e03b55bfcd583b07e1 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:49:10 +0200
Subject: [PATCH 16/33] Create ProtocolHandlerTests.cs
---
NetSdrClient.Tests/ProtocolHandlerTests.cs | 63 ++++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 NetSdrClient.Tests/ProtocolHandlerTests.cs
diff --git a/NetSdrClient.Tests/ProtocolHandlerTests.cs b/NetSdrClient.Tests/ProtocolHandlerTests.cs
new file mode 100644
index 00000000..60b17bc4
--- /dev/null
+++ b/NetSdrClient.Tests/ProtocolHandlerTests.cs
@@ -0,0 +1,63 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NetSdrClient.Services;
+
+namespace NetSdrClient.Tests
+{
+ [TestClass]
+ public class ProtocolHandlerTests
+ {
+ [TestMethod]
+ public void ProtocolHandler_Constructor_ShouldInitialize()
+ {
+ // Arrange & Act
+ var handler = new ProtocolHandler();
+
+ // Assert
+ Assert.IsNotNull(handler);
+ }
+
+ [TestMethod]
+ public void CreateCommand_ShouldReturnValidCommand()
+ {
+ // Arrange
+ var handler = new ProtocolHandler();
+ var expectedCommand = "CONNECT";
+
+ // Act
+ var result = handler.CreateCommand(expectedCommand);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Contains(expectedCommand));
+ }
+
+ [TestMethod]
+ public void ParseResponse_WithValidData_ShouldReturnParsedResponse()
+ {
+ // Arrange
+ var handler = new ProtocolHandler();
+ var testData = "OK:CONNECTED";
+
+ // Act
+ var result = handler.ParseResponse(testData);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Contains("CONNECTED"));
+ }
+
+ [TestMethod]
+ public void ParseResponse_WithNullData_ShouldReturnErrorMessage()
+ {
+ // Arrange
+ var handler = new ProtocolHandler();
+
+ // Act
+ var result = handler.ParseResponse(null);
+
+ // Assert
+ Assert.IsNotNull(result);
+ Assert.IsTrue(result.Contains("ERROR"));
+ }
+ }
+}
From e4531ee91514e78bf0c3925be7448ff94216389e Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:49:28 +0200
Subject: [PATCH 17/33] Create NetworkServiceTests.cs
---
NetSdrClient.Tests/NetworkServiceTests.cs | 75 +++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 NetSdrClient.Tests/NetworkServiceTests.cs
diff --git a/NetSdrClient.Tests/NetworkServiceTests.cs b/NetSdrClient.Tests/NetworkServiceTests.cs
new file mode 100644
index 00000000..3d29fd9e
--- /dev/null
+++ b/NetSdrClient.Tests/NetworkServiceTests.cs
@@ -0,0 +1,75 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NetSdrClient.Services;
+
+namespace NetSdrClient.Tests
+{
+ [TestClass]
+ public class NetworkServiceTests
+ {
+ [TestMethod]
+ public void NetworkService_Constructor_ShouldInitialize()
+ {
+ // Arrange & Act
+ var service = new NetworkService();
+
+ // Assert
+ Assert.IsNotNull(service);
+ }
+
+ [TestMethod]
+ public void IsValidIpAddress_WithValidIp_ShouldReturnTrue()
+ {
+ // Arrange
+ var service = new NetworkService();
+ var validIp = "192.168.1.1";
+
+ // Act
+ var result = service.IsValidIpAddress(validIp);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public void IsValidIpAddress_WithInvalidIp_ShouldReturnFalse()
+ {
+ // Arrange
+ var service = new NetworkService();
+ var invalidIp = "999.999.999.999";
+
+ // Act
+ var result = service.IsValidIpAddress(invalidIp);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+
+ [TestMethod]
+ public void IsValidPort_WithValidPort_ShouldReturnTrue()
+ {
+ // Arrange
+ var service = new NetworkService();
+ var validPort = 8080;
+
+ // Act
+ var result = service.IsValidPort(validPort);
+
+ // Assert
+ Assert.IsTrue(result);
+ }
+
+ [TestMethod]
+ public void IsValidPort_WithInvalidPort_ShouldReturnFalse()
+ {
+ // Arrange
+ var service = new NetworkService();
+ var invalidPort = 99999;
+
+ // Act
+ var result = service.IsValidPort(invalidPort);
+
+ // Assert
+ Assert.IsFalse(result);
+ }
+ }
+}
From ba9bceeab537e96dbe95c3e2ce9898257003f308 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:49:55 +0200
Subject: [PATCH 18/33] Create NetSdrClient.Tests.csproj
---
NetSdrClient.Tests/NetSdrClient.Tests.csproj | 22 ++++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 NetSdrClient.Tests/NetSdrClient.Tests.csproj
diff --git a/NetSdrClient.Tests/NetSdrClient.Tests.csproj b/NetSdrClient.Tests/NetSdrClient.Tests.csproj
new file mode 100644
index 00000000..140ede72
--- /dev/null
+++ b/NetSdrClient.Tests/NetSdrClient.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 84c9c4279e1a4c9cc10883575abd68503f234ee6 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 11:52:33 +0200
Subject: [PATCH 19/33] Create coverlet.msbuild
---
.github/workflows/coverlet.msbuild | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 .github/workflows/coverlet.msbuild
diff --git a/.github/workflows/coverlet.msbuild b/.github/workflows/coverlet.msbuild
new file mode 100644
index 00000000..5f923fc2
--- /dev/null
+++ b/.github/workflows/coverlet.msbuild
@@ -0,0 +1,3 @@
+dotnet add NetSdrClientAppTests package coverlet.msbuild
+dotnet add NetSdrClientAppTests package Microsoft.NET.Test.Sdk
+dotnet test NetSdrClientAppTests -c Release /p:CollectCoverage=true /p:CoverletOutput=TestResults/coverage.xml /p:CoverletOutputFormat=opencover
From 64e97d4e2f88ddbcb30d7ad75102aeff2d5f0e50 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:05:37 +0200
Subject: [PATCH 20/33] Update sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 118 ++++++++++++-------------------
1 file changed, 47 insertions(+), 71 deletions(-)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index aca8b48d..bfb501f2 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -1,83 +1,59 @@
-# This workflow uses actions that are not certified by GitHub.
-# They are provided by a third-party and are governed by
-# separate terms of service, privacy policy, and support
-# documentation.
-
-# This workflow helps you trigger a SonarCloud analysis of your code and populates
-# GitHub Code Scanning alerts with the vulnerabilities found.
-# Free for open source project.
-
-# 1. Login to SonarCloud.io using your GitHub account
-
-# 2. Import your project on SonarCloud
-# * Add your GitHub organization first, then add your repository as a new project.
-# * Please note that many languages are eligible for automatic analysis,
-# which means that the analysis will start automatically without the need to set up GitHub Actions.
-# * This behavior can be changed in Administration > Analysis Method.
-#
-# 3. Follow the SonarCloud in-product tutorial
-# * a. Copy/paste the Project Key and the Organization Key into the args parameter below
-# (You'll find this information in SonarCloud. Click on "Information" at the bottom left)
-#
-# * b. Generate a new token and add it to your Github repository's secrets using the name SONAR_TOKEN
-# (On SonarCloud, click on your avatar on top-right > My account > Security
-# or go directly to https://sonarcloud.io/account/security/)
-
-# Feel free to take a look at our documentation (https://docs.sonarcloud.io/getting-started/github/)
-# or reach out to our community forum if you need some help (https://community.sonarsource.com/c/help/sc/9)
-
-name: SonarCloud analysis
+name: SonarCloud Analysis
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
- workflow_dispatch:
-
-permissions:
- pull-requests: read # allows SonarCloud to decorate PRs with analysis results
jobs:
- sonar-check:
- name: Sonar Check
- runs-on: windows-latest # безпечно для будь-яких .NET проектів
+ build-and-analyze:
+ name: Build and Analyze
+ runs-on: windows-latest
+
steps:
- - uses: actions/checkout@v4
- with: { fetch-depth: 0 }
-
- - uses: actions/setup-dotnet@v4
- with:
- dotnet-version: '8.0.x'
-
- # 1) BEGIN: SonarScanner for .NET
- - name: SonarScanner Begin
- run: |
- dotnet tool install --global dotnet-sonarscanner
- echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
- dotnet sonarscanner begin `
- /k:"ppanchen_NetSdrClient" `
- /o:"ppanchen" `
- /d:sonar.token="${{ secrets.SONAR_TOKEN }}" `
- /d:sonar.cs.opencover.reportsPaths="**/coverage.xml" `
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.x'
+
+ - name: Install SonarScanner
+ run: |
+ dotnet tool install --global dotnet-sonarscanner
+ echo "$env:USERPROFILE\.dotnet\tools" >> $env:GITHUB_PATH
+
+ - name: Install Coverlet
+ run: dotnet tool install --global coverlet.console
+
+ - name: Build and test with SonarCloud
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ run: |
+ # Start SonarScanner
+ dotnet sonarscanner begin `
+ /k:"Yegres546_NetSdrClient" `
+ /o:"yegres546" `
+ /d:sonar.token="$env:SONAR_TOKEN" `
+ /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml" `
/d:sonar.cpd.cs.minimumTokens=40 `
/d:sonar.cpd.cs.minimumLines=5 `
- /d:sonar.exclusions=**/bin/**,**/obj/**,**/sonarcloud.yml `
+ /d:sonar.exclusions="**/bin/**,**/obj/**,**/TestResults/**,**/*.Tests.cs" `
+ /d:sonar.coverage.exclusions="**Test*.cs" `
/d:sonar.qualitygate.wait=true
- shell: pwsh
- # 2) BUILD & TEST
- - name: Restore
- run: dotnet restore NetSdrClient.sln
- - name: Build
- run: dotnet build NetSdrClient.sln -c Release --no-restore
- - name: Tests with coverage (OpenCover)
- run: |
- dotnet test NetSdrClientAppTests/NetSdrClientAppTests.csproj -c Release --no-build `
- /p:CollectCoverage=true `
- /p:CoverletOutput=TestResults/coverage.xml `
- /p:CoverletOutputFormat=opencover
- shell: pwsh
- # 3) END: SonarScanner
- - name: SonarScanner End
- run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
- shell: pwsh
+
+ # Build the solution
+ dotnet build --configuration Release
+
+ # Run tests with coverage
+ dotnet test --configuration Release `
+ --no-build `
+ --verbosity normal `
+ --collect:"XPlat Code Coverage" `
+ --results-directory ./TestResults `
+ -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
+
+ # End SonarScanner
+ dotnet sonarscanner end /d:sonar.token="$env:SONAR_TOKEN"
From 71b909b510e6b28f084cf950903acc42f7048da1 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:14:42 +0200
Subject: [PATCH 21/33] Create coverlet.runsettings
---
coverlet.runsettings | 14 ++++++++++++++
1 file changed, 14 insertions(+)
create mode 100644 coverlet.runsettings
diff --git a/coverlet.runsettings b/coverlet.runsettings
new file mode 100644
index 00000000..9815d295
--- /dev/null
+++ b/coverlet.runsettings
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+ opencover
+ [NetSdrClient]*
+ [NetSdrClient.Tests]*
+
+
+
+
+
From d799a0852ba9d263071a9ff406f6614830fbb6f7 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:43:19 +0200
Subject: [PATCH 22/33] Create ArchitectureTests.cs
---
NetSdrClient.Tests/ArchitectureTests.cs | 148 ++++++++++++++++++++++++
1 file changed, 148 insertions(+)
create mode 100644 NetSdrClient.Tests/ArchitectureTests.cs
diff --git a/NetSdrClient.Tests/ArchitectureTests.cs b/NetSdrClient.Tests/ArchitectureTests.cs
new file mode 100644
index 00000000..a664fae4
--- /dev/null
+++ b/NetSdrClient.Tests/ArchitectureTests.cs
@@ -0,0 +1,148 @@
+using NetArchTest.Rules;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace NetSdrClient.Tests
+{
+ [TestClass]
+ public class ArchitectureTests
+ {
+ private const string ApplicationNamespace = "NetSdrClient";
+ private const string ModelsNamespace = "NetSdrClient.Models";
+ private const string ServicesNamespace = "NetSdrClient.Services";
+ private const string InterfacesNamespace = "NetSdrClient.Interfaces";
+ private const string InfrastructureNamespace = "NetSdrClient.Infrastructure";
+ private const string UINamespace = "NetSdrClient.UI";
+
+ [TestMethod]
+ public void ServicesLayer_ShouldNotDependOnUI()
+ {
+ // Arrange
+ var assembly = typeof(Services.SDRClient).Assembly;
+
+ // Act
+ var result = Types
+ .InAssembly(assembly)
+ .That()
+ .ResideInNamespace(ServicesNamespace)
+ .ShouldNot()
+ .HaveDependencyOn(UINamespace)
+ .GetResult();
+
+ // Assert
+ Assert.IsTrue(result.IsSuccessful,
+ $"Services layer should not depend on UI: {string.Join(", ", result.FailingTypes)}");
+ }
+
+ [TestMethod]
+ public void Models_ShouldNotReferenceServices()
+ {
+ // Arrange
+ var assembly = typeof(Models.SDRDevice).Assembly;
+
+ // Act
+ var result = Types
+ .InAssembly(assembly)
+ .That()
+ .ResideInNamespace(ModelsNamespace)
+ .ShouldNot()
+ .HaveDependencyOn(ServicesNamespace)
+ .GetResult();
+
+ // Assert
+ Assert.IsTrue(result.IsSuccessful,
+ $"Models should not depend on Services: {string.Join(", ", result.FailingTypes)}");
+ }
+
+ [TestMethod]
+ public void Interfaces_ShouldNotHaveDependencies()
+ {
+ // Arrange
+ var assembly = typeof(Services.SDRClient).Assembly;
+
+ // Act
+ var result = Types
+ .InAssembly(assembly)
+ .That()
+ .ResideInNamespace(InterfacesNamespace)
+ .Should()
+ .NotHaveDependencyOnAny(
+ ServicesNamespace,
+ ModelsNamespace,
+ InfrastructureNamespace,
+ UINamespace)
+ .GetResult();
+
+ // Assert
+ Assert.IsTrue(result.IsSuccessful,
+ $"Interfaces should not have dependencies: {string.Join(", ", result.FailingTypes)}");
+ }
+
+ [TestMethod]
+ public void AllClasses_ShouldHaveNamesEndingWithService_IfInServicesNamespace()
+ {
+ // Arrange
+ var assembly = typeof(Services.SDRClient).Assembly;
+
+ // Act
+ var result = Types
+ .InAssembly(assembly)
+ .That()
+ .ResideInNamespace(ServicesNamespace)
+ .And()
+ .AreClasses()
+ .Should()
+ .HaveNameEndingWith("Service")
+ .Or()
+ .HaveNameEndingWith("Client")
+ .Or()
+ .HaveNameEndingWith("Handler")
+ .GetResult();
+
+ // Assert
+ Assert.IsTrue(result.IsSuccessful,
+ $"Services should have proper naming: {string.Join(", ", result.FailingTypes)}");
+ }
+
+ [TestMethod]
+ public void Models_ShouldBeSealed()
+ {
+ // Arrange
+ var assembly = typeof(Models.SDRDevice).Assembly;
+
+ // Act
+ var result = Types
+ .InAssembly(assembly)
+ .That()
+ .ResideInNamespace(ModelsNamespace)
+ .And()
+ .AreClasses()
+ .Should()
+ .BeSealed()
+ .GetResult();
+
+ // Assert
+ Assert.IsTrue(result.IsSuccessful,
+ $"Models should be sealed: {string.Join(", ", result.FailingTypes)}");
+ }
+
+ [TestMethod]
+ public void Services_ShouldNotDependOnInfrastructureDirectly()
+ {
+ // Arrange
+ var assembly = typeof(Services.SDRClient).Assembly;
+
+ // Act
+ var result = Types
+ .InAssembly(assembly)
+ .That()
+ .ResideInNamespace(ServicesNamespace)
+ .ShouldNot()
+ .HaveDependencyOn(InfrastructureNamespace)
+ .GetResult();
+
+ // Assert
+ Assert.IsTrue(result.IsSuccessful,
+ $"Services should not depend directly on Infrastructure: {string.Join(", ", result.FailingTypes)}");
+ }
+ }
+}
From d01b5554b7db4a3b753771ca2f1d81591f692f04 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:43:42 +0200
Subject: [PATCH 23/33] Update NetSdrClient.Tests.csproj
---
NetSdrClient.Tests/NetSdrClient.Tests.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/NetSdrClient.Tests/NetSdrClient.Tests.csproj b/NetSdrClient.Tests/NetSdrClient.Tests.csproj
index 140ede72..7697a375 100644
--- a/NetSdrClient.Tests/NetSdrClient.Tests.csproj
+++ b/NetSdrClient.Tests/NetSdrClient.Tests.csproj
@@ -4,7 +4,6 @@
net6.0
enable
enable
-
false
@@ -13,6 +12,7 @@
+
From 8e1e35ebb132cd99c7bedb0182d5352ee5551687 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:45:07 +0200
Subject: [PATCH 24/33] Create BadService.cs
---
NetSdrClient/Services/BadService.cs | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
create mode 100644 NetSdrClient/Services/BadService.cs
diff --git a/NetSdrClient/Services/BadService.cs b/NetSdrClient/Services/BadService.cs
new file mode 100644
index 00000000..51da3ba7
--- /dev/null
+++ b/NetSdrClient/Services/BadService.cs
@@ -0,0 +1,20 @@
+using NetSdrClient.Models;
+using NetSdrClient.UI; // Навмисне порушення - Services залежить від UI
+
+namespace NetSdrClient.Services
+{
+ public class BadService
+ {
+ private readonly UIComponent _uiComponent; // Порушення!
+
+ public BadService()
+ {
+ _uiComponent = new UIComponent();
+ }
+
+ public void DoSomething()
+ {
+ _uiComponent.ShowMessage("This violates architecture rules!");
+ }
+ }
+}
From 178b8eaceeb7b06eebb8e747592be374cb636815 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:45:57 +0200
Subject: [PATCH 25/33] Create UIComponent.cs
---
UI/UIComponent.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
create mode 100644 UI/UIComponent.cs
diff --git a/UI/UIComponent.cs b/UI/UIComponent.cs
new file mode 100644
index 00000000..347ef573
--- /dev/null
+++ b/UI/UIComponent.cs
@@ -0,0 +1,10 @@
+namespace NetSdrClient.UI
+{
+ public class UIComponent
+ {
+ public void ShowMessage(string message)
+ {
+ // UI logic here - this should not be called from Services
+ }
+ }
+}
From cd16e6d50c8e2c4b88256d8eb3aaf0d83ff79683 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:46:49 +0200
Subject: [PATCH 26/33] Create DatabaseService.cs
---
Infrastructure/DatabaseService.cs | 10 ++++++++++
1 file changed, 10 insertions(+)
create mode 100644 Infrastructure/DatabaseService.cs
diff --git a/Infrastructure/DatabaseService.cs b/Infrastructure/DatabaseService.cs
new file mode 100644
index 00000000..2fcf4980
--- /dev/null
+++ b/Infrastructure/DatabaseService.cs
@@ -0,0 +1,10 @@
+namespace NetSdrClient.Infrastructure
+{
+ public class DatabaseService
+ {
+ public void SaveData(object data)
+ {
+ // Database operations
+ }
+ }
+}
From 1031a89aee84a3c921651b34bd1c069f6a150d58 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:47:19 +0200
Subject: [PATCH 27/33] Create IDeviceService.cs
---
Interfaces/IDeviceService.cs | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 Interfaces/IDeviceService.cs
diff --git a/Interfaces/IDeviceService.cs b/Interfaces/IDeviceService.cs
new file mode 100644
index 00000000..b19adbd7
--- /dev/null
+++ b/Interfaces/IDeviceService.cs
@@ -0,0 +1,8 @@
+namespace NetSdrClient.Interfaces
+{
+ public interface IDeviceService
+ {
+ void Connect();
+ void Disconnect();
+ }
+}
From 1d6ed29f5cdb1f09781f22be44b7ab9360771a41 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:53:52 +0200
Subject: [PATCH 28/33] Update NetSdrClient.cs
---
NetSdrClientApp/NetSdrClient.cs | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/NetSdrClientApp/NetSdrClient.cs b/NetSdrClientApp/NetSdrClient.cs
index b0a7c058..d4466ac5 100644
--- a/NetSdrClientApp/NetSdrClient.cs
+++ b/NetSdrClientApp/NetSdrClient.cs
@@ -9,7 +9,29 @@
using System.Threading.Tasks;
using static NetSdrClientApp.Messages.NetSdrMessageHelper;
using static System.Runtime.InteropServices.JavaScript.JSType;
+using NetSdrClient.Models;
+using NetSdrClient.Interfaces;
+namespace NetSdrClient.Services
+{
+ public class SDRClient : IDeviceService
+ {
+ public List Devices { get; private set; }
+
+ // Реалізація інтерфейсу
+ public void Connect()
+ {
+ // Connection logic
+ }
+
+ public void Disconnect()
+ {
+ // Disconnection logic
+ }
+
+ // Інші методи...
+ }
+}
namespace NetSdrClientApp
{
public class NetSdrClient
From 0eac29fe625eb3305f82650fe88e1e90e6762dd0 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 12:55:23 +0200
Subject: [PATCH 29/33] Update sonarcloud.yml
---
.github/workflows/sonarcloud.yml | 33 ++++++++++++++++++++++++++++++++
1 file changed, 33 insertions(+)
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index bfb501f2..d23eba70 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -57,3 +57,36 @@ jobs:
# End SonarScanner
dotnet sonarscanner end /d:sonar.token="$env:SONAR_TOKEN"
+name: Architecture Rules Validation
+
+on:
+ push:
+ branches: [ "lab5" ]
+ pull_request:
+ branches: [ "lab5" ]
+
+jobs:
+ architecture-tests:
+ name: Architecture Rules Validation
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v3
+ with:
+ dotnet-version: '6.0.x'
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --no-restore --configuration Release
+
+ - name: Run Architecture Tests
+ run: dotnet test --configuration Release --no-build --verbosity normal --filter "Category=Architecture"
+
+ - name: Run All Tests
+ run: dotnet test --configuration Release --no-build --verbosity normal
From 2601b5199de29fbd2e301b00fca9de1fe9c5b79c Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 13:30:55 +0200
Subject: [PATCH 30/33] Create DisposeTests.cs
---
NetSdrClient.Tests/DisposeTests.cs | 92 ++++++++++++++++++++++++++++++
1 file changed, 92 insertions(+)
create mode 100644 NetSdrClient.Tests/DisposeTests.cs
diff --git a/NetSdrClient.Tests/DisposeTests.cs b/NetSdrClient.Tests/DisposeTests.cs
new file mode 100644
index 00000000..add7ee6f
--- /dev/null
+++ b/NetSdrClient.Tests/DisposeTests.cs
@@ -0,0 +1,92 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using NetSdrClient.Services;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NetSdrClient.Tests
+{
+ [TestClass]
+ public class DisposeTests
+ {
+ [TestMethod]
+ public void SDRClient_Dispose_ShouldCancelCancellationToken()
+ {
+ // Arrange
+ var client = new SDRClient();
+
+ // Act
+ client.Dispose();
+
+ // Assert
+ // Перевіряємо, що токен скасований після Dispose
+ Assert.IsTrue(client.IsDisposed);
+ }
+
+ [TestMethod]
+ public async Task SDRClient_DisposeDuringOperation_ShouldCancelOperation()
+ {
+ // Arrange
+ var client = new SDRClient();
+ var operationCompleted = false;
+
+ // Act
+ var task = Task.Run(async () =>
+ {
+ try
+ {
+ await client.StartAsync();
+ operationCompleted = true;
+ }
+ catch (OperationCanceledException)
+ {
+ // Очікувана поведінка
+ }
+ });
+
+ // Даємо трохи часу на початок операції
+ await Task.Delay(100);
+
+ // Dispose має скасувати операцію
+ client.Dispose();
+
+ await task;
+
+ // Assert
+ Assert.IsFalse(operationCompleted, "Operation should be cancelled by Dispose");
+ }
+
+ [TestMethod]
+ public void SDRClient_MultipleDispose_ShouldNotThrowException()
+ {
+ // Arrange
+ var client = new SDRClient();
+
+ // Act & Assert
+ try
+ {
+ client.Dispose();
+ client.Dispose(); // Другий виклик не має кидати виняток
+ }
+ catch (Exception ex)
+ {
+ Assert.Fail($"Multiple Dispose calls should not throw exception: {ex.Message}");
+ }
+ }
+
+ [TestMethod]
+ public void CancellationTokenSource_InUsingBlock_ShouldBeDisposedAutomatically()
+ {
+ // Arrange & Act & Assert
+ // Не має бути помилок або попереджень
+ using (var cts = new CancellationTokenSource())
+ {
+ var token = cts.Token;
+ cts.CancelAfter(100);
+
+ Assert.IsTrue(token.CanBeCanceled);
+ }
+ // cts автоматично видаляється тут
+ }
+ }
+}
From 9f8bb8998fe4a70282e030ad68a96d8618f5892c Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 13:34:43 +0200
Subject: [PATCH 31/33] Update TcpClientWrapper.cs
---
.../Networking/TcpClientWrapper.cs | 69 +++++++++++++++----
1 file changed, 54 insertions(+), 15 deletions(-)
diff --git a/NetSdrClientApp/Networking/TcpClientWrapper.cs b/NetSdrClientApp/Networking/TcpClientWrapper.cs
index 1f37e2e5..62dd5c1e 100644
--- a/NetSdrClientApp/Networking/TcpClientWrapper.cs
+++ b/NetSdrClientApp/Networking/TcpClientWrapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -10,15 +10,16 @@
namespace NetSdrClientApp.Networking
{
- public class TcpClientWrapper : ITcpClient
+ public class TcpClientWrapper : ITcpClient, IDisposable
{
private string _host;
private int _port;
private TcpClient? _tcpClient;
private NetworkStream? _stream;
private CancellationTokenSource _cts;
+ private bool _disposed = false;
- public bool Connected => _tcpClient != null && _tcpClient.Connected && _stream != null;
+ public bool Connected => !_disposed && _tcpClient != null && _tcpClient.Connected && _stream != null;
public event EventHandler? MessageReceived;
@@ -26,10 +27,13 @@ public TcpClientWrapper(string host, int port)
{
_host = host;
_port = port;
+ _cts = new CancellationTokenSource();
}
public void Connect()
{
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
if (Connected)
{
Console.WriteLine($"Already connected to {_host}:{_port}");
@@ -40,7 +44,6 @@ public void Connect()
try
{
- _cts = new CancellationTokenSource();
_tcpClient.Connect(_host, _port);
_stream = _tcpClient.GetStream();
Console.WriteLine($"Connected to {_host}:{_port}");
@@ -49,6 +52,8 @@ public void Connect()
catch (Exception ex)
{
Console.WriteLine($"Failed to connect: {ex.Message}");
+ CleanupResources();
+ throw;
}
}
@@ -57,12 +62,7 @@ public void Disconnect()
if (Connected)
{
_cts?.Cancel();
- _stream?.Close();
- _tcpClient?.Close();
-
- _cts = null;
- _tcpClient = null;
- _stream = null;
+ CleanupResources();
Console.WriteLine("Disconnected.");
}
else
@@ -73,6 +73,8 @@ public void Disconnect()
public async Task SendMessageAsync(byte[] data)
{
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
if (Connected && _stream != null && _stream.CanWrite)
{
Console.WriteLine($"Message sent: " + data.Select(b => Convert.ToString(b, toBase: 16)).Aggregate((l, r) => $"{l} {r}"));
@@ -86,6 +88,8 @@ public async Task SendMessageAsync(byte[] data)
public async Task SendMessageAsync(string str)
{
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
var data = Encoding.UTF8.GetBytes(str);
if (Connected && _stream != null && _stream.CanWrite)
{
@@ -104,7 +108,7 @@ private async Task StartListeningAsync()
{
try
{
- Console.WriteLine($"Starting listening for incomming messages.");
+ Console.WriteLine($"Starting listening for incoming messages.");
while (!_cts.Token.IsCancellationRequested)
{
@@ -117,13 +121,16 @@ private async Task StartListeningAsync()
}
}
}
- catch (OperationCanceledException ex)
+ catch (OperationCanceledException)
{
- //empty
+ // Expected when cancellation is requested
}
catch (Exception ex)
{
- Console.WriteLine($"Error in listening loop: {ex.Message}");
+ if (!_disposed)
+ {
+ Console.WriteLine($"Error in listening loop: {ex.Message}");
+ }
}
finally
{
@@ -135,6 +142,38 @@ private async Task StartListeningAsync()
throw new InvalidOperationException("Not connected to a server.");
}
}
- }
+ private void CleanupResources()
+ {
+ _stream?.Close();
+ _stream?.Dispose();
+ _stream = null;
+
+ _tcpClient?.Close();
+ _tcpClient?.Dispose();
+ _tcpClient = null;
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ // Dispose managed resources
+ _cts?.Cancel();
+ _cts?.Dispose();
+ CleanupResources();
+ }
+
+ _disposed = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
}
From 28c05a8ad5961799256fe0035b0228589afe9bfb Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 13:36:46 +0200
Subject: [PATCH 32/33] Update UdpClientWrapper.cs
---
.../Networking/UdpClientWrapper.cs | 75 +++++++++++++++----
1 file changed, 59 insertions(+), 16 deletions(-)
diff --git a/NetSdrClientApp/Networking/UdpClientWrapper.cs b/NetSdrClientApp/Networking/UdpClientWrapper.cs
index 31e0b798..b1e35c03 100644
--- a/NetSdrClientApp/Networking/UdpClientWrapper.cs
+++ b/NetSdrClientApp/Networking/UdpClientWrapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
@@ -6,22 +6,25 @@
using System.Threading;
using System.Threading.Tasks;
-public class UdpClientWrapper : IUdpClient
+public class UdpClientWrapper : IUdpClient, IDisposable
{
private readonly IPEndPoint _localEndPoint;
- private CancellationTokenSource? _cts;
+ private CancellationTokenSource _cts;
private UdpClient? _udpClient;
+ private bool _disposed = false;
public event EventHandler? MessageReceived;
public UdpClientWrapper(int port)
{
_localEndPoint = new IPEndPoint(IPAddress.Any, port);
+ _cts = new CancellationTokenSource();
}
public async Task StartListeningAsync()
{
- _cts = new CancellationTokenSource();
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
Console.WriteLine("Start listening for UDP messages...");
try
@@ -35,18 +38,27 @@ public async Task StartListeningAsync()
Console.WriteLine($"Received from {result.RemoteEndPoint}");
}
}
- catch (OperationCanceledException ex)
+ catch (OperationCanceledException)
+ {
+ // Expected when cancellation is requested
+ }
+ catch (ObjectDisposedException)
{
- //empty
+ // Expected when UdpClient is disposed
}
catch (Exception ex)
{
- Console.WriteLine($"Error receiving message: {ex.Message}");
+ if (!_disposed)
+ {
+ Console.WriteLine($"Error receiving message: {ex.Message}");
+ }
}
}
public void StopListening()
{
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
try
{
_cts?.Cancel();
@@ -55,31 +67,62 @@ public void StopListening()
}
catch (Exception ex)
{
- Console.WriteLine($"Error while stopping: {ex.Message}");
+ if (!_disposed)
+ {
+ Console.WriteLine($"Error while stopping: {ex.Message}");
+ }
}
}
public void Exit()
+ {
+ // Exit - це по суті те саме що StopListening, але для узгодженості
+ StopListening();
+ }
+
+ public override int GetHashCode()
+ {
+ var payload = $"{nameof(UdpClientWrapper)}|{_localEndPoint.Address}|{_localEndPoint.Port}";
+
+ using var md5 = MD5.Create();
+ var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(payload));
+
+ return BitConverter.ToInt32(hash, 0);
+ }
+
+ private void CleanupResources()
{
try
{
_cts?.Cancel();
_udpClient?.Close();
- Console.WriteLine("Stopped listening for UDP messages.");
+ _udpClient?.Dispose();
+ _udpClient = null;
}
catch (Exception ex)
{
- Console.WriteLine($"Error while stopping: {ex.Message}");
+ Console.WriteLine($"Error during cleanup: {ex.Message}");
}
}
- public override int GetHashCode()
+ protected virtual void Dispose(bool disposing)
{
- var payload = $"{nameof(UdpClientWrapper)}|{_localEndPoint.Address}|{_localEndPoint.Port}";
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ // Dispose managed resources
+ CleanupResources();
+ _cts?.Dispose();
+ }
- using var md5 = MD5.Create();
- var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(payload));
+ _disposed = true;
+ }
+ }
- return BitConverter.ToInt32(hash, 0);
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
-}
\ No newline at end of file
+}
From 59afae96c1677720ce77fbcde28622a496135034 Mon Sep 17 00:00:00 2001
From: Yegres546 <126583849+Yegres546@users.noreply.github.com>
Date: Wed, 26 Nov 2025 13:39:47 +0200
Subject: [PATCH 33/33] Update Program.cs
---
EchoTcpServer/Program.cs | 187 +++++++++++++++++++++++++++------------
1 file changed, 128 insertions(+), 59 deletions(-)
diff --git a/EchoTcpServer/Program.cs b/EchoTcpServer/Program.cs
index 7f47378b..0e78cbe7 100644
--- a/EchoTcpServer/Program.cs
+++ b/EchoTcpServer/Program.cs
@@ -1,4 +1,5 @@
-using System;
+using System;
+using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
@@ -9,22 +10,24 @@
/// This program was designed for test purposes only
/// Not for a review
///
-public class EchoServer
+public class EchoServer : IDisposable
{
private readonly int _port;
private TcpListener _listener;
private CancellationTokenSource _cancellationTokenSource;
-
+ private bool _disposed = false;
public EchoServer(int port)
{
_port = port;
_cancellationTokenSource = new CancellationTokenSource();
+ _listener = new TcpListener(IPAddress.Any, _port);
}
public async Task StartAsync()
{
- _listener = new TcpListener(IPAddress.Any, _port);
+ ObjectDisposedException.ThrowIf(_disposed, this);
+
_listener.Start();
Console.WriteLine($"Server started on port {_port}.");
@@ -42,6 +45,14 @@ public async Task StartAsync()
// Listener has been closed
break;
}
+ catch (Exception ex) when (ex is SocketException || ex is InvalidOperationException)
+ {
+ if (!_disposed)
+ {
+ Console.WriteLine($"Server error: {ex.Message}");
+ }
+ break;
+ }
}
Console.WriteLine("Server shutdown.");
@@ -49,6 +60,7 @@ public async Task StartAsync()
private static async Task HandleClientAsync(TcpClient client, CancellationToken token)
{
+ using (client)
using (NetworkStream stream = client.GetStream())
{
try
@@ -56,7 +68,8 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
byte[] buffer = new byte[8192];
int bytesRead;
- while (!token.IsCancellationRequested && (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
+ while (!token.IsCancellationRequested &&
+ (bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
{
// Echo back the received message
await stream.WriteAsync(buffer, 0, bytesRead, token);
@@ -69,7 +82,6 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
}
finally
{
- client.Close();
Console.WriteLine("Client disconnected.");
}
}
@@ -77,37 +89,64 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken
public void Stop()
{
- _cancellationTokenSource.Cancel();
- _listener.Stop();
- _cancellationTokenSource.Dispose();
- Console.WriteLine("Server stopped.");
+ if (!_disposed)
+ {
+ _cancellationTokenSource.Cancel();
+ _listener.Stop();
+ Console.WriteLine("Server stopped.");
+ }
}
- public static async Task Main(string[] args)
+ protected virtual void Dispose(bool disposing)
{
- EchoServer server = new EchoServer(5000);
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+ _listener?.Stop();
+ // TcpListener doesn't implement IDisposable in .NET Core
+ }
- // Start the server in a separate task
- _ = Task.Run(() => server.StartAsync());
+ _disposed = true;
+ }
+ }
- string host = "127.0.0.1"; // Target IP
- int port = 60000; // Target Port
- int intervalMilliseconds = 5000; // Send every 3 seconds
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- using (var sender = new UdpTimedSender(host, port))
+ public static async Task Main(string[] args)
+ {
+ using (var server = new EchoServer(5000))
{
- Console.WriteLine("Press any key to stop sending...");
- sender.StartSending(intervalMilliseconds);
+ // Start the server in a separate task
+ var serverTask = Task.Run(() => server.StartAsync());
- Console.WriteLine("Press 'q' to quit...");
- while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q)
+ string host = "127.0.0.1"; // Target IP
+ int port = 60000; // Target Port
+ int intervalMilliseconds = 5000; // Send every 5 seconds
+
+ using (var sender = new UdpTimedSender(host, port))
{
- // Just wait until 'q' is pressed
+ Console.WriteLine("Press any key to stop sending...");
+ sender.StartSending(intervalMilliseconds);
+
+ Console.WriteLine("Press 'q' to quit...");
+ while (Console.ReadKey(intercept: true).Key != ConsoleKey.Q)
+ {
+ // Just wait until 'q' is pressed
+ }
+
+ sender.StopSending();
+ server.Stop();
+ Console.WriteLine("Sender stopped.");
}
- sender.StopSending();
- server.Stop();
- Console.WriteLine("Sender stopped.");
+ await serverTask;
}
}
}
@@ -120,55 +159,85 @@ public class UdpTimedSender : IDisposable
private readonly int _port;
private readonly UdpClient _udpClient;
private Timer _timer;
+ private ushort _counter = 0;
+ private readonly Random _random;
+ private bool _disposed = false;
public UdpTimedSender(string host, int port)
{
_host = host;
_port = port;
_udpClient = new UdpClient();
+ _random = new Random();
}
- }
- public void StartSending(int intervalMilliseconds)
- {
- if (_timer != null)
- throw new InvalidOperationException("Sender is already running.");
- _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds);
- }
+ public void StartSending(int intervalMilliseconds)
+ {
+ ObjectDisposedException.ThrowIf(_disposed, this);
- ushort i = 0;
+ if (_timer != null)
+ throw new InvalidOperationException("Sender is already running.");
- private void SendMessageCallback(object state)
- {
- try
- {
- //dummy data
- Random rnd = new Random();
- byte[] samples = new byte[1024];
- rnd.NextBytes(samples);
- i++;
+ _timer = new Timer(SendMessageCallback, null, 0, intervalMilliseconds);
+ }
- byte[] msg = (new byte[] { 0x04, 0x84 }).Concat(BitConverter.GetBytes(i)).Concat(samples).ToArray();
- var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port);
+ private void SendMessageCallback(object state)
+ {
+ if (_disposed) return;
- _udpClient.Send(msg, msg.Length, endpoint);
- Console.WriteLine($"Message sent to {_host}:{_port} ");
+ try
+ {
+ // Thread-safe counter increment
+ ushort currentCounter = (ushort)Interlocked.Increment(ref _counter);
+
+ // Generate dummy data
+ byte[] samples = new byte[1024];
+ _random.NextBytes(samples); // Random is thread-safe for this usage
+
+ // Create message: 0x04, 0x84 + counter + samples
+ byte[] msg = new byte[] { 0x04, 0x84 }
+ .Concat(BitConverter.GetBytes(currentCounter))
+ .Concat(samples)
+ .ToArray();
+
+ var endpoint = new IPEndPoint(IPAddress.Parse(_host), _port);
+
+ _udpClient.Send(msg, msg.Length, endpoint);
+ Console.WriteLine($"Message #{currentCounter} sent to {_host}:{_port}");
+ }
+ catch (Exception ex)
+ {
+ if (!_disposed)
+ {
+ Console.WriteLine($"Error sending message: {ex.Message}");
+ }
+ }
}
- catch (Exception ex)
+
+ public void StopSending()
{
- Console.WriteLine($"Error sending message: {ex.Message}");
+ _timer?.Dispose();
+ _timer = null;
}
- }
- public void StopSending()
- {
- _timer?.Dispose();
- _timer = null;
- }
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ StopSending();
+ _udpClient?.Dispose();
+ }
- public void Dispose()
- {
- StopSending();
- _udpClient.Dispose();
+ _disposed = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
}
}