-
Notifications
You must be signed in to change notification settings - Fork 20
Assignment9+10 multithreading #101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
RyanHirte
wants to merge
65
commits into
IntelliTect-Samples:Assignment9+10-Multithreading
Choose a base branch
from
ColtonKnopik:Assignment9+10-Multithreading
base: Assignment9+10-Multithreading
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
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 d8fef4f
moved files for pipeline
RyanHirte 1233f7d
Step 1 Done
RyanHirte 46c4777
Test for step 2 done
RyanHirte 6f62568
make RunAsync and RunLongRunningAsync static
ColtonKnopik 8ec35a7
make RunAsync static
ColtonKnopik dd0deb9
revert statics
ColtonKnopik 048c906
RunLongRunningAsync(
ColtonKnopik abe2f66
Merge pull request #8 from ColtonKnopik/Ryan-work
RyanHirte a0c5ee5
Merge branch 'Colton-Methods-RunAsync' into Assignment9+10-Multithrea…
RyanHirte fa26b72
Task 5 Implementation and Tests
ColtonKnopik f1b833e
suprress warning temporarily
ColtonKnopik a074035
Suppress warnings in wildacardpattern
ColtonKnopik 2fc8ecd
Kill warnings in WildCardPattern
ColtonKnopik d94ea92
Proper exception. Rename method in String Extensions
ColtonKnopik 3674ef7
Fix Tests and implement RunAsync(params string[] hostNameOrAddresses)
ColtonKnopik dda4a3e
Add SetPingArguments and change tests to make for safe testing betwee…
ColtonKnopik 59055bc
remove unused test
ColtonKnopik 12cfecc
remove unused test PingProcessTests.AssertValidPingOutput
ColtonKnopik b4da6ef
Adds wait for exit so tests fails instead of hanging
ColtonKnopik 473cc39
add 5 secnond max on waitforexit
ColtonKnopik 699de3b
update Start_pingprocess test
ColtonKnopik 075c57c
remove 5 second timer, setarguments to empty in SetPingArgurments bef…
ColtonKnopik 253c440
Update Google test, update Run, update StartInfo
ColtonKnopik 72116d0
Remove Google test, update error messages in invalidaddressoutput
ColtonKnopik 12f7771
remove localhost test
ColtonKnopik 692862d
use await in tests
ColtonKnopik 3d24dd5
Add DoNotParallelize in Tests
ColtonKnopik 2e301ef
change localhost to 127.0.0.1 in tests
ColtonKnopik 941b812
refactor PingProcess, Disable Parallel in tests and build props
ColtonKnopik 1b32e2b
update finally block in RunProcessInternal
ColtonKnopik 00ee9e6
PingProcess refactor
ColtonKnopik 6ab73b6
make methods static
ColtonKnopik 9b8011d
Suppress static warning and provided justification
ColtonKnopik 2aa20fa
suppress warning on RunLongRunningAsync
ColtonKnopik 1a9549d
test refactor test file
ColtonKnopik ee9db39
full refactor test
ColtonKnopik 90efa75
refactior for build
ColtonKnopik c3c72f0
add token to wait
ColtonKnopik 14f336f
Add FakePingProcess
ColtonKnopik 7a54396
mark ValidatePingSuccess static
ColtonKnopik 26f6e6a
Merge pull request #13 from ColtonKnopik/Colton-TestFaluire-Testbranch
ColtonKnopik 3679f60
remove unused imports in `FakePingProcess`
ColtonKnopik 5059b4f
creates `WrapProgress` function to consoldidate duplicate logic in Ru…
ColtonKnopik f01c1f5
Add to process kill in `RunProcessInternal` to ensure all of the chil…
ColtonKnopik 5112c77
Improve null/empty handling
ColtonKnopik 559a714
Merge pull request #14 from ColtonKnopik/Colton-Code-Cleanup
RyanHirte c03141e
Fixed and implemented extra credit tests
RyanHirte 1b2fd5b
Fix Test Name `RunAsync_UsingTpl_Success`
ColtonKnopik 20cb6ba
remove async and await from `RunTaskAsync_Success()`
ColtonKnopik 5e43a51
remove async and await from `RunAsync_UsingTaskReturn_Success()`
ColtonKnopik 30e5a04
Merge pull request #16 from ColtonKnopik/9+10-test-fixes
ColtonKnopik 35066f7
dispose of calcellation token source with a `using` statement
ColtonKnopik 4ee2599
Merge pull request #17 from ColtonKnopik/9+10-test-fixes
ColtonKnopik 7b9edca
fix assert statement in StringBuilder test
ColtonKnopik 459016f
fix naming and cancellation token overhead in RunAsync
ColtonKnopik 47f220a
fix naming conventions
ColtonKnopik 52ab02c
commit copilot suggestions in RunAsync
ColtonKnopik 9de4ad9
copilot suggestions, using ManualResetEventSlim and use Default inste…
ColtonKnopik 90da0ed
Merge pull request #18 from ColtonKnopik/9+10-test-fixes
ColtonKnopik 1a96601
Fixed review comment with stringbuilder
RyanHirte 18760a9
Merge pull request #19 from ColtonKnopik/hirte-review-work
RyanHirte 9649567
Added missing tests
RyanHirte 17cf357
Added throwifcancellationrequested
RyanHirte 0023a8f
Merge pull request #20 from ColtonKnopik/TestImprovements
RyanHirte File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
File renamed without changes.
39 changes: 20 additions & 19 deletions
39
.../Assignment.Tests/Assignment.Tests.csproj → Assignment.Tests/Assignment.Tests.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
|
|
||
| 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; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"); | ||
|
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}"); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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. TheWaitmethod blocks the calling thread synchronously, which defeats the purpose of usingTask.Delayfor 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: