Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
bbc5df9
update directory.build.props
RyanHirte Nov 23, 2025
d8fef4f
moved files for pipeline
RyanHirte Nov 23, 2025
1233f7d
Step 1 Done
RyanHirte Nov 23, 2025
46c4777
Test for step 2 done
RyanHirte Nov 23, 2025
6f62568
make RunAsync and RunLongRunningAsync static
ColtonKnopik Nov 23, 2025
8ec35a7
make RunAsync static
ColtonKnopik Nov 23, 2025
dd0deb9
revert statics
ColtonKnopik Nov 23, 2025
048c906
RunLongRunningAsync(
ColtonKnopik Nov 23, 2025
abe2f66
Merge pull request #8 from ColtonKnopik/Ryan-work
RyanHirte Nov 24, 2025
a0c5ee5
Merge branch 'Colton-Methods-RunAsync' into Assignment9+10-Multithrea…
RyanHirte Nov 24, 2025
fa26b72
Task 5 Implementation and Tests
ColtonKnopik Nov 25, 2025
f1b833e
suprress warning temporarily
ColtonKnopik Nov 25, 2025
a074035
Suppress warnings in wildacardpattern
ColtonKnopik Nov 25, 2025
2fc8ecd
Kill warnings in WildCardPattern
ColtonKnopik Nov 25, 2025
d94ea92
Proper exception. Rename method in String Extensions
ColtonKnopik Nov 25, 2025
3674ef7
Fix Tests and implement RunAsync(params string[] hostNameOrAddresses)
ColtonKnopik Nov 30, 2025
dda4a3e
Add SetPingArguments and change tests to make for safe testing betwee…
ColtonKnopik Nov 30, 2025
59055bc
remove unused test
ColtonKnopik Nov 30, 2025
12cfecc
remove unused test PingProcessTests.AssertValidPingOutput
ColtonKnopik Nov 30, 2025
b4da6ef
Adds wait for exit so tests fails instead of hanging
ColtonKnopik Nov 30, 2025
473cc39
add 5 secnond max on waitforexit
ColtonKnopik Nov 30, 2025
699de3b
update Start_pingprocess test
ColtonKnopik Nov 30, 2025
075c57c
remove 5 second timer, setarguments to empty in SetPingArgurments bef…
ColtonKnopik Nov 30, 2025
253c440
Update Google test, update Run, update StartInfo
ColtonKnopik Nov 30, 2025
72116d0
Remove Google test, update error messages in invalidaddressoutput
ColtonKnopik Nov 30, 2025
12f7771
remove localhost test
ColtonKnopik Nov 30, 2025
692862d
use await in tests
ColtonKnopik Nov 30, 2025
3d24dd5
Add DoNotParallelize in Tests
ColtonKnopik Nov 30, 2025
2e301ef
change localhost to 127.0.0.1 in tests
ColtonKnopik Dec 1, 2025
941b812
refactor PingProcess, Disable Parallel in tests and build props
ColtonKnopik Dec 1, 2025
1b32e2b
update finally block in RunProcessInternal
ColtonKnopik Dec 1, 2025
00ee9e6
PingProcess refactor
ColtonKnopik Dec 1, 2025
6ab73b6
make methods static
ColtonKnopik Dec 1, 2025
9b8011d
Suppress static warning and provided justification
ColtonKnopik Dec 1, 2025
2aa20fa
suppress warning on RunLongRunningAsync
ColtonKnopik Dec 1, 2025
1a9549d
test refactor test file
ColtonKnopik Dec 1, 2025
ee9db39
full refactor test
ColtonKnopik Dec 1, 2025
90efa75
refactior for build
ColtonKnopik Dec 1, 2025
c3c72f0
add token to wait
ColtonKnopik Dec 1, 2025
14f336f
Add FakePingProcess
ColtonKnopik Dec 2, 2025
7a54396
mark ValidatePingSuccess static
ColtonKnopik Dec 2, 2025
26f6e6a
Merge pull request #13 from ColtonKnopik/Colton-TestFaluire-Testbranch
ColtonKnopik Dec 2, 2025
3679f60
remove unused imports in `FakePingProcess`
ColtonKnopik Dec 2, 2025
5059b4f
creates `WrapProgress` function to consoldidate duplicate logic in Ru…
ColtonKnopik Dec 2, 2025
f01c1f5
Add to process kill in `RunProcessInternal` to ensure all of the chil…
ColtonKnopik Dec 2, 2025
5112c77
Improve null/empty handling
ColtonKnopik Dec 2, 2025
559a714
Merge pull request #14 from ColtonKnopik/Colton-Code-Cleanup
RyanHirte Dec 2, 2025
c03141e
Fixed and implemented extra credit tests
RyanHirte Dec 2, 2025
1b2fd5b
Fix Test Name `RunAsync_UsingTpl_Success`
ColtonKnopik Dec 4, 2025
20cb6ba
remove async and await from `RunTaskAsync_Success()`
ColtonKnopik Dec 4, 2025
5e43a51
remove async and await from `RunAsync_UsingTaskReturn_Success()`
ColtonKnopik Dec 4, 2025
30e5a04
Merge pull request #16 from ColtonKnopik/9+10-test-fixes
ColtonKnopik Dec 4, 2025
35066f7
dispose of calcellation token source with a `using` statement
ColtonKnopik Dec 4, 2025
4ee2599
Merge pull request #17 from ColtonKnopik/9+10-test-fixes
ColtonKnopik Dec 4, 2025
7b9edca
fix assert statement in StringBuilder test
ColtonKnopik Dec 4, 2025
459016f
fix naming and cancellation token overhead in RunAsync
ColtonKnopik Dec 4, 2025
47f220a
fix naming conventions
ColtonKnopik Dec 4, 2025
52ab02c
commit copilot suggestions in RunAsync
ColtonKnopik Dec 4, 2025
9de4ad9
copilot suggestions, using ManualResetEventSlim and use Default inste…
ColtonKnopik Dec 4, 2025
90da0ed
Merge pull request #18 from ColtonKnopik/9+10-test-fixes
ColtonKnopik Dec 4, 2025
1a96601
Fixed review comment with stringbuilder
RyanHirte Dec 4, 2025
18760a9
Merge pull request #19 from ColtonKnopik/hirte-review-work
RyanHirte Dec 5, 2025
9649567
Added missing tests
RyanHirte Dec 5, 2025
17cf357
Added throwifcancellationrequested
RyanHirte Dec 5, 2025
0023a8f
Merge pull request #20 from ColtonKnopik/TestImprovements
RyanHirte Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="4.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="4.0.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<ProjectReference Include="..\Assignment\Assignment.csproj" />
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>false</IsPackable>
<ParallelizeTestCollections>false</ParallelizeTestCollections>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="MSTest.TestAdapter" Version="4.0.2" />
<PackageReference Include="MSTest.TestFramework" Version="4.0.2" />
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<ProjectReference Include="..\Assignment\Assignment.csproj" />
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
72 changes: 72 additions & 0 deletions Assignment.Tests/FakePingProcess.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;


namespace Assignment.Tests;

internal sealed class FakePingProcess : PingProcess
{
readonly string _pingOutputTemplate = @"
Pinging * with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms

Ping statistics for ::1:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms".Trim();

protected override int RunProcessInternal(
ProcessStartInfo startInfo,
Action<string?>? progressOutput,
Action<string?>? progressError,
CancellationToken token)
{
Task.Delay(500, token).Wait(token);
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Task.Delay(...).Wait(token) can lead to deadlocks or blocking issues. The Wait method blocks the calling thread synchronously, which defeats the purpose of using Task.Delay for async delays.

Since this method is not async and appears to intentionally simulate blocking work, consider using Thread.Sleep(500) instead, which is more straightforward for synchronous delays. Alternatively, if you need cancellation support, use:

try
{
    Task.Delay(500, token).GetAwaiter().GetResult();
}
catch (TaskCanceledException)
{
    // Handle cancellation
}
Suggested change
Task.Delay(500, token).Wait(token);
try
{
Task.Delay(500, token).GetAwaiter().GetResult();
}
catch (TaskCanceledException)
{
// Handle cancellation if needed
}

Copilot uses AI. Check for mistakes.

string host = startInfo.Arguments;

if (host == "badaddress")
{
string msg =
"Ping request could not find host badaddress. Please check the name and try again.";

progressOutput?.Invoke(msg);
progressOutput?.Invoke(null);
progressError?.Invoke(null);

return 1;
}

foreach (string line in GetLinesForHost(host))
{
progressOutput?.Invoke(line);
}

progressOutput?.Invoke(null);
progressError?.Invoke(null);

return 0;
}

private string[] GetLinesForHost(string host)
{
string[] lines = _pingOutputTemplate.Split(
Environment.NewLine, StringSplitOptions.None);

if (lines.Length > 0)
{
string first = lines[0];
if (first.Contains('*'))
{
lines[0] = first.Replace("*", host);
}
}

return lines;
}
}
238 changes: 238 additions & 0 deletions Assignment.Tests/PingProcessTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
using IntelliTect.TestTools;
using System;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Assignment.Tests;

[TestClass]
[DoNotParallelize]
public class PingProcessTests
{
private FakePingProcess Sut { get; set; } = new();

[TestInitialize]
public void TestInitialize()
{
Sut = new FakePingProcess();
}

[TestMethod]
public void Start_PingProcess_Success()
{
var psi = OperatingSystem.IsWindows()
? new ProcessStartInfo("ping", "127.0.0.1 -n 1")
: new ProcessStartInfo("ping", "127.0.0.1 -c 1");

using Process process = Process.Start(psi)!;
process.WaitForExit();
Assert.AreEqual(0, process.ExitCode);
}

[TestMethod]
public void Run_InvalidAddressOutput_Success()
{
var result = Sut.Run("badaddress");

Assert.IsFalse(string.IsNullOrWhiteSpace(result.StdOutput));

string normalized = result.StdOutput!.Trim().ToLowerInvariant();

// Accept common OS ping error messages
bool matches = normalized.Contains("temporary failure")
|| normalized.Contains("name or service not known")
|| normalized.Contains("could not find host");

Assert.IsTrue(matches, $"Unexpected output: {result.StdOutput}");
Assert.AreNotEqual(0, result.ExitCode);
}

[TestMethod]
public void Run_CaptureStdOutput_Success()
{
var result = Sut.Run("localhost");
ValidatePingSuccess(result);
}

[TestMethod]
public void RunTaskAsync_Success()
{
var result = Sut.RunTaskAsync("localhost").Result;
ValidatePingSuccess(result);
}

[TestMethod]
public void RunAsync_UsingTaskReturn_Success()
{
var result = Sut.RunAsync("localhost").Result;
ValidatePingSuccess(result);
}

[TestMethod]
public async Task RunAsync_MultipleHostAddresses_True()
{
string[] hosts = { "localhost", "127.0.0.1" };
var result = await Sut.RunAsync(hosts);
ValidatePingSuccess(result);
}

[TestMethod]
async public Task RunAsync_UsingTpl_Success()
{
var result = await Sut.RunAsync("localhost");
Comment thread
RyanHirte marked this conversation as resolved.
ValidatePingSuccess(result);
}

[TestMethod]
public void RunAsync_UsingTplWithCancellation_CatchAggregateExceptionWrapping()
{
using var cts = new CancellationTokenSource();
cts.Cancel();
var task = Sut.RunAsync("localhost", cts.Token);
try
{
task.Wait();
Assert.Fail("Expected AggregateException was not thrown.");
}
catch (AggregateException ex)
{
Assert.IsInstanceOfType<AggregateException>(ex);
}
}

[TestMethod]
public void RunAsync_UsingTplWithCancellation_CatchAggregateExceptionWrappingTaskCanceledException()
{
using var cts = new CancellationTokenSource();
cts.Cancel();
var task = Sut.RunAsync("localhost", cts.Token);
try
{
task.Wait();
Assert.Fail("Expected AggregateException was not thrown.");
}
catch (AggregateException ex)
{
Assert.IsInstanceOfType<TaskCanceledException>(ex.InnerException);
}
}

[TestMethod]
public void StringBuilderAppendLine_InParallel_DemonstratesNonThreadSafeBehavior()
{
var numbers = Enumerable.Range(0, 1000);
var stringBuilder = new StringBuilder();
Exception? capturedException = null;

try
{
numbers.AsParallel().ForAll(i => stringBuilder.AppendLine("X"));
}
catch (Exception ex)
{
capturedException = ex;
}

int expectedLength = numbers.Count() * ("X" + Environment.NewLine).Length;
Assert.IsTrue(
capturedException != null || stringBuilder.Length != expectedLength,
"StringBuilder is not thread-safe: either an exception occurs or the content is corrupted."
);
}

[TestMethod]
public async Task RunLongRunningAsync_ValidPing_ReturnsZero()
{
var psi = OperatingSystem.IsWindows()
? new ProcessStartInfo("ping", "127.0.0.1 -n 1")
: new ProcessStartInfo("ping", "127.0.0.1 -c 1");

int result = await Sut.RunLongRunningAsync(
psi,
progressOutput: _ => { },
progressError: _ => { },
token: CancellationToken.None);

Assert.AreEqual(0, result);
}

[TestMethod]
public async Task RunLongRunningAsync_OutputProduced_ProgressOutputInvoked()
{
int count = 0;
void output(string? line) { if (line != null) count++; }

var psi = OperatingSystem.IsWindows()
? new ProcessStartInfo("ping", "127.0.0.1 -n 1")
: new ProcessStartInfo("ping", "127.0.0.1 -c 1");

await Sut.RunLongRunningAsync(
psi,
progressOutput: output,
progressError: _ => { },
token: CancellationToken.None);

Assert.IsGreaterThan(0, count, "Expected progressOutput to be invoked at least once.");
}

[TestMethod]
public void RunLongRunningAsync_Cancelled_ThrowsAggregateException()
{
var psi = OperatingSystem.IsWindows()
? new ProcessStartInfo("ping", "127.0.0.1 -n 1")
: new ProcessStartInfo("ping", "127.0.0.1 -c 1");

using var cts = new CancellationTokenSource();

var task = Sut.RunLongRunningAsync(
psi,
progressOutput: _ => { },
progressError: _ => { },
token: cts.Token);

cts.Cancel();

try
{
task.Wait();
Assert.Fail("Expected AggregateException was not thrown.");
}
catch (AggregateException ex)
{
Assert.IsInstanceOfType<TaskCanceledException>(ex.InnerException);
}
}

[TestMethod]
public async Task RunAsync_WithProgress_CapturesOutputAsItOccurs()
{
StringBuilder outputBuilder = new();
void ProgressHandler(string? line)
{
if (line != null)
{
outputBuilder.AppendLine(line);
}
}

var result = await Sut.RunAsync("localhost", new Progress<string?>(ProgressHandler), CancellationToken.None);
ValidatePingSuccess(result);
}

// --- Helper for validating ping success across OSes ---
private static void ValidatePingSuccess(PingResult result)
{
Assert.IsNotNull(result.StdOutput);
Assert.IsGreaterThan(0, result.StdOutput!.Length, "Output should not be empty.");
Assert.AreEqual(0, result.ExitCode);

string[] successMarkers = { "Reply from", "bytes from" };
bool containsMarker = successMarkers.Any(marker =>
result.StdOutput.Contains(marker, StringComparison.OrdinalIgnoreCase));

Assert.IsTrue(containsMarker, $"Output did not contain expected markers. Actual output:\n{result.StdOutput}");
}
}
Loading