diff --git a/src/Docker.DotNet/Endpoints/StreamUtil.cs b/src/Docker.DotNet/Endpoints/StreamUtil.cs index 03e1c1b62..e64e027cf 100644 --- a/src/Docker.DotNet/Endpoints/StreamUtil.cs +++ b/src/Docker.DotNet/Endpoints/StreamUtil.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.IO; using System.Net.Http; using System.Text; @@ -32,7 +31,7 @@ internal static async Task MonitorStreamForMessagesAsync(Task streamT var tcs = new TaskCompletionSource(); using (var stream = await streamTask) - using (var reader = new StreamReader(stream, new UTF8Encoding(false))) + using (var reader = new StreamReader(stream, new UTF8Encoding(false), false)) using (var jsonReader = new JsonTextReader(reader) { SupportMultipleContent = true }) using (cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken))) { @@ -44,12 +43,14 @@ internal static async Task MonitorStreamForMessagesAsync(Task streamT } } - internal static async Task MonitorResponseForMessagesAsync(Task responseTask, DockerClient client, CancellationToken cancel, IProgress progress) + internal static async Task MonitorResponseForMessagesAsync(Task responseTask, DockerClient client, CancellationToken cancellationToken, IProgress progress) { - using (var response = await responseTask) + using (var response = await responseTask + .ConfigureAwait(false)) { - await MonitorStreamForMessagesAsync(response.Content.ReadAsStreamAsync(), client, cancel, progress); + await MonitorStreamForMessagesAsync(response.Content.ReadAsStreamAsync(), client, cancellationToken, progress) + .ConfigureAwait(false); } } } -} +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs index 3da5a8763..4b8e9068d 100644 --- a/test/Docker.DotNet.Tests/IContainerOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IContainerOperationsTests.cs @@ -13,7 +13,7 @@ namespace Docker.DotNet.Tests { [Collection(nameof(TestCollection))] - public class IContainerOperationsTests + public sealed class IContainerOperationsTests : IDisposable { private readonly CancellationTokenSource _cts; @@ -107,6 +107,8 @@ await _dockerClient.Containers.StartContainerAsync( _cts.Token ); + await Task.Delay(TimeSpan.FromSeconds(5), default); + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync( @@ -154,6 +156,8 @@ await _dockerClient.Containers.StartContainerAsync( _cts.Token ); + await Task.Delay(TimeSpan.FromSeconds(5), default); + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync( @@ -203,6 +207,8 @@ await _dockerClient.Containers.StartContainerAsync( _cts.Token ); + await Task.Delay(TimeSpan.FromSeconds(5), default); + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync( @@ -215,11 +221,9 @@ await _dockerClient.Containers.StartContainerAsync( Follow = false }, containerLogsCts.Token, - new Progress(m => { _output.WriteLine(m); logList.Add(m); }) + new Progress(m => { logList.Add(m); _output.WriteLine(m); }) ); - await Task.Delay(TimeSpan.FromSeconds(5)); - await _dockerClient.Containers.StopContainerAsync( createContainerResponse.ID, new ContainerStopParameters(), @@ -253,9 +257,11 @@ await _dockerClient.Containers.StartContainerAsync( _cts.Token ); + await Task.Delay(TimeSpan.FromSeconds(5), default); + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); - await Assert.ThrowsAsync(() => _dockerClient.Containers.GetContainerLogsAsync( + var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync( createContainerResponse.ID, new ContainerLogsParameters { @@ -266,7 +272,9 @@ await Assert.ThrowsAsync(() => _dockerClient.Containers.G }, containerLogsCts.Token, new Progress(m => _output.WriteLine(m)) - )); + ); + + await Assert.ThrowsAsync(() => containerLogsTask); } [Fact] @@ -290,6 +298,8 @@ await _dockerClient.Containers.StartContainerAsync( _cts.Token ); + await Task.Delay(TimeSpan.FromSeconds(5), default); + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync( @@ -330,6 +340,8 @@ await _dockerClient.Containers.StartContainerAsync( _cts.Token ); + await Task.Delay(TimeSpan.FromSeconds(5), default); + containerLogsCts.CancelAfter(TimeSpan.FromSeconds(5)); var containerLogsTask = _dockerClient.Containers.GetContainerLogsAsync( @@ -342,18 +354,9 @@ await _dockerClient.Containers.StartContainerAsync( Follow = true }, containerLogsCts.Token, - new Progress(m => { _output.WriteLine(m); logList.Add(m); }) - ); - - await Task.Delay(TimeSpan.FromSeconds(5)); - - await _dockerClient.Containers.StopContainerAsync( - createContainerResponse.ID, - new ContainerStopParameters(), - _cts.Token + new Progress(m => { logList.Add(m); _output.WriteLine(m); }) ); - await Assert.ThrowsAsync(() => containerLogsTask); _output.WriteLine($"Line count: {logList.Count}"); @@ -808,5 +811,10 @@ public async Task MultiplexedStreamWriteAsync_DoesNotThrowAnException() // Then Assert.Null(exception); } + + public void Dispose() + { + _cts.Dispose(); + } } -} +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/IImageOperationsTests.cs b/test/Docker.DotNet.Tests/IImageOperationsTests.cs index 0e3d795ea..edd18fe39 100644 --- a/test/Docker.DotNet.Tests/IImageOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IImageOperationsTests.cs @@ -9,7 +9,7 @@ namespace Docker.DotNet.Tests { [Collection(nameof(TestCollection))] - public class IImageOperationsTests + public sealed class IImageOperationsTests : IDisposable { private readonly CancellationTokenSource _cts; @@ -34,7 +34,7 @@ public IImageOperationsTests(TestFixture testFixture, ITestOutputHelper outputHe } [Fact] - public async Task CreateImageAsync_TaskCancelled_ThowsTaskCanceledException() + public async Task CreateImageAsync_TaskCancelled_ThrowsTaskCanceledException() { using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token); @@ -114,5 +114,10 @@ await _dockerClient.Images.DeleteImageAsync( Assert.NotNull(inspectExistingImageResponse); await Assert.ThrowsAsync(() => inspectDeletedImageTask); } + + public void Dispose() + { + _cts.Dispose(); + } } -} +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs index 61f805a46..232b1c7db 100644 --- a/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs +++ b/test/Docker.DotNet.Tests/ISystemOperations.Tests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Docker.DotNet.Models; @@ -13,7 +12,7 @@ namespace Docker.DotNet.Tests { [Collection(nameof(TestCollection))] - public class ISystemOperationsTests + public sealed class ISystemOperationsTests : IDisposable { private readonly CancellationTokenSource _cts; @@ -92,7 +91,7 @@ public async Task MonitorEventsAsync_Succeeds() var progressMessage = new Progress((m) => { - _output.WriteLine($"MonitorEventsAsync_Succeeds: Message - {m.Action} - {m.Status} {m.From} - {m.Type}"); + _output.WriteLine($"MonitorEventsAsync_Succeeds: Message - {m.Action} - {m.Status} {m.Actor.Attributes["name"]} - {m.Type}"); wasProgressCalled = true; Assert.NotNull(m); }); @@ -115,11 +114,13 @@ await _dockerClient.Images.DeleteImageAsync( _cts.Token); // Give it some time for output operation to complete before cancelling task - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1), cts.Token) + .ConfigureAwait(false); cts.Cancel(); - await Assert.ThrowsAsync(() => task).ConfigureAwait(false); + await Assert.ThrowsAsync(() => task) + .ConfigureAwait(false); Assert.True(wasProgressCalled); } @@ -127,99 +128,72 @@ await _dockerClient.Images.DeleteImageAsync( [Fact] public async Task MonitorEventsAsync_IsCancelled_NoStreamCorruption() { - var rand = new Random(); - var sw = new Stopwatch(); + const int iterationCount = 10; - for (int i = 0; i < 20; ++i) + ICollection events = new List(); + + for (var i = 0; i < iterationCount; ++i) { - try - { - // (1) Create monitor task - using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token); - string newImageTag = Guid.NewGuid().ToString(); + using var eventsSync = new AutoResetEvent(false); - var monitorTask = _dockerClient.System.MonitorEventsAsync( - new ContainerEventsParameters(), - new Progress((value) => _output.WriteLine($"DockerSystemEvent: {JsonConvert.SerializeObject(value)}")), - cts.Token); + var imageTagParameters = new ImageTagParameters(); + imageTagParameters.RepositoryName = _repositoryName; + imageTagParameters.Tag = Guid.NewGuid().ToString(); - // (2) Wait for some time to make sure we get into blocking IO call - await Task.Delay(100); + var progress = new Progress(message => _output.WriteLine($"DockerSystemEvent: {JsonConvert.SerializeObject(message)}")); + progress.ProgressChanged += (_, message) => events.Add(message.Status); + progress.ProgressChanged += (_, _) => eventsSync.Set(); - // (3) Invoke another request that will attempt to grab the same buffer - var listImagesTask1 = _dockerClient.Images.TagImageAsync( - $"{_repositoryName}:{_tag}", - new ImageTagParameters - { - RepositoryName = _repositoryName, - Tag = newImageTag, - Force = true - }, - default); + var monitorTask = _dockerClient.System.MonitorEventsAsync(new ContainerEventsParameters(), progress, cts.Token); - // (4) Wait for a short bit again and cancel the monitor task - if we get lucky, we the list images call will grab the same buffer while - sw.Restart(); - var iterations = rand.Next(15000000); + var tagImage = (CancellationToken ct) => _dockerClient.Images.TagImageAsync($"{_repositoryName}:{_tag}", imageTagParameters, ct); - for (int j = 0; j < iterations; j++) - { - // noop - } - _output.WriteLine($"Waited for {sw.Elapsed.TotalMilliseconds} ms"); + _ = tagImage.Invoke(default); - cts.Cancel(); + _ = eventsSync.WaitOne(TimeSpan.FromSeconds(1)); - listImagesTask1.GetAwaiter().GetResult(); + cts.Cancel(); - _dockerClient.Images.TagImageAsync( - $"{_repositoryName}:{_tag}", - new ImageTagParameters - { - RepositoryName = _repositoryName, - Tag = newImageTag, - Force = true - } - ).GetAwaiter().GetResult(); + await tagImage.Invoke(default) + .ConfigureAwait(false); - monitorTask.GetAwaiter().GetResult(); - } - catch (TaskCanceledException) - { - // Exceptions other than this causes test to fail - } + await Assert.ThrowsAsync(() => monitorTask) + .ConfigureAwait(false); } + + Assert.Equal(iterationCount, events.Count); } [Fact] public async Task MonitorEventsFiltered_Succeeds() { - string newTag = $"MonitorTests-{Guid.NewGuid().ToString().Substring(1, 10)}"; - string newImageRespositoryName = Guid.NewGuid().ToString(); + using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token); - await _dockerClient.Images.TagImageAsync( - $"{_repositoryName}:{_tag}", - new ImageTagParameters - { - RepositoryName = newImageRespositoryName, - Tag = newTag - }, - _cts.Token - ); + using var eventsSync = new AutoResetEvent(false); + + ICollection events = new List(); - ImageInspectResponse image = await _dockerClient.Images.InspectImageAsync( - $"{newImageRespositoryName}:{newTag}", - _cts.Token - ); + var sourceImageTag = $"{_repositoryName}:{_tag}"; - var progressCalledCounter = 0; + var targetImageTag = $"{_repositoryName}:{Guid.NewGuid().ToString()}"; - var eventsParams = new ContainerEventsParameters() + var image = await _dockerClient.Images.InspectImageAsync(sourceImageTag, _cts.Token) + .ConfigureAwait(false); + + var imageTagParameters = new ImageTagParameters + { + RepositoryName = targetImageTag.Split(':').First(), + Tag = targetImageTag.Split(':').Last() + }; + + var containerEventsParameters = new ContainerEventsParameters { - Filters = new Dictionary>() + Filters = new Dictionary> { { - "event", new Dictionary() + "event", new Dictionary { { "tag", true @@ -230,7 +204,7 @@ await _dockerClient.Images.TagImageAsync( } }, { - "type", new Dictionary() + "type", new Dictionary { { "image", true @@ -238,7 +212,7 @@ await _dockerClient.Images.TagImageAsync( } }, { - "image", new Dictionary() + "image", new Dictionary { { image.ID, true @@ -248,35 +222,51 @@ await _dockerClient.Images.TagImageAsync( } }; - var progress = new Progress((m) => - { - Interlocked.Increment(ref progressCalledCounter); - Assert.True(m.Status == "tag" || m.Status == "untag"); - _output.WriteLine($"MonitorEventsFiltered_Succeeds: Message received: {m.Action} - {m.Status} {m.From} - {m.Type}"); - }); + var progress = new Progress(message => _output.WriteLine($"MonitorEventsFiltered_Succeeds: Message received: {message.Action} - {message.Status} {message.Actor.Attributes["name"]} - {message.Type}")); + progress.ProgressChanged += (_, message) => events.Add(message.Status); + progress.ProgressChanged += (_, _) => eventsSync.Set(); - using var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token); - var task = Task.Run(() => _dockerClient.System.MonitorEventsAsync(eventsParams, progress, cts.Token)); + var monitorTask = _dockerClient.System.MonitorEventsAsync(containerEventsParameters, progress, cts.Token); + + await _dockerClient.Images.TagImageAsync(sourceImageTag, imageTagParameters, cts.Token) + .ConfigureAwait(false); - await _dockerClient.Images.TagImageAsync($"{_repositoryName}:{_tag}", new ImageTagParameters { RepositoryName = _repositoryName, Tag = newTag }); - await _dockerClient.Images.DeleteImageAsync($"{_repositoryName}:{newTag}", new ImageDeleteParameters()); + _ = eventsSync.WaitOne(TimeSpan.FromSeconds(1)); - var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters { Image = $"{_repositoryName}:{_tag}" }); - await _dockerClient.Containers.RemoveContainerAsync(createContainerResponse.ID, new ContainerRemoveParameters(), cts.Token); + await _dockerClient.Images.DeleteImageAsync(targetImageTag, new ImageDeleteParameters(), cts.Token) + .ConfigureAwait(false); + + _ = eventsSync.WaitOne(TimeSpan.FromSeconds(1)); + + var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters { Image = sourceImageTag }, cts.Token) + .ConfigureAwait(false); + + await _dockerClient.Containers.RemoveContainerAsync(createContainerResponse.ID, new ContainerRemoveParameters(), cts.Token) + .ConfigureAwait(false); - await Task.Delay(TimeSpan.FromSeconds(1)); cts.Cancel(); - await Assert.ThrowsAsync(() => task); + await Assert.ThrowsAsync(() => monitorTask) + .ConfigureAwait(false); - Assert.Equal(2, progressCalledCounter); - Assert.True(task.IsCanceled); + Assert.True(monitorTask.IsCanceled); + Assert.Equal(2, events.Count); + Assert.Contains("tag", events); + Assert.Contains("untag", events); } [Fact] public async Task PingAsync_Succeeds() { - await _dockerClient.System.PingAsync(); + var exception = await Record.ExceptionAsync(() => _dockerClient.System.PingAsync()) + .ConfigureAwait(false); + + Assert.Null(exception); + } + + public void Dispose() + { + _cts.Dispose(); } } -} +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs b/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs index 0bc8633b0..5679d0e7c 100644 --- a/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs +++ b/test/Docker.DotNet.Tests/IVolumeOperationsTests.cs @@ -4,14 +4,13 @@ using System.Threading; using System.Threading.Tasks; using Docker.DotNet.Models; -using Newtonsoft.Json; using Xunit; using Xunit.Abstractions; namespace Docker.DotNet.Tests { [Collection(nameof(TestCollection))] - public class IVolumeOperationsTests + public sealed class IVolumeOperationsTests : IDisposable { private readonly CancellationTokenSource _cts; @@ -55,5 +54,10 @@ await _dockerClient.Volumes.CreateAsync(new VolumesCreateParameters await _dockerClient.Volumes.RemoveAsync(volumeName, force: true, _cts.Token); } } + + public void Dispose() + { + _cts.Dispose(); + } } -} +} \ No newline at end of file diff --git a/test/Docker.DotNet.Tests/TestFixture.cs b/test/Docker.DotNet.Tests/TestFixture.cs index 1feef7764..eeefce9ea 100644 --- a/test/Docker.DotNet.Tests/TestFixture.cs +++ b/test/Docker.DotNet.Tests/TestFixture.cs @@ -16,11 +16,11 @@ public sealed class TestFixture : IAsyncLifetime, IDisposable /// The Docker image name. /// private const string Name = "nats"; - + private static readonly Progress WriteProgressOutput; private bool _hasInitializedSwarm; - + static TestFixture() { WriteProgressOutput = new Progress(jsonMessage => @@ -30,7 +30,7 @@ static TestFixture() Debug.WriteLine(message); }); } - + /// /// Initializes a new instance of the class. /// @@ -39,7 +39,7 @@ public TestFixture() { DockerClientConfiguration = new DockerClientConfiguration(); DockerClient = DockerClientConfiguration.CreateClient(); - Cts = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + Cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)); Cts.Token.Register(() => throw new TimeoutException("Docker.DotNet test timeout exception")); } diff --git a/test/Docker.DotNet.Tests/TestOutput.cs b/test/Docker.DotNet.Tests/TestOutput.cs index 1417d7a46..61c96a783 100644 --- a/test/Docker.DotNet.Tests/TestOutput.cs +++ b/test/Docker.DotNet.Tests/TestOutput.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using Xunit.Abstractions; namespace Docker.DotNet.Tests @@ -15,8 +16,8 @@ public TestOutput(ITestOutputHelper outputHelper) public void WriteLine(string line) { Console.WriteLine(line); + Debug.WriteLine(line); _outputHelper.WriteLine(line); - System.Diagnostics.Debug.WriteLine(line); } } -} +} \ No newline at end of file