diff --git a/ReqIFSharp.Extensions.Tests/Services/ReqIFLoaderServiceTestFixture.cs b/ReqIFSharp.Extensions.Tests/Services/ReqIFLoaderServiceTestFixture.cs index 964c83a..423cbb5 100644 --- a/ReqIFSharp.Extensions.Tests/Services/ReqIFLoaderServiceTestFixture.cs +++ b/ReqIFSharp.Extensions.Tests/Services/ReqIFLoaderServiceTestFixture.cs @@ -21,11 +21,14 @@ namespace ReqIFSharp.Extensions.Tests.Services { using System; + using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; + using System.Reflection; using System.Threading; using System.Threading.Tasks; + using System.Xml.Schema; using Microsoft.Extensions.Logging; @@ -171,5 +174,110 @@ public async Task Verify_that_ExternalObject_image_can_be_Queried() Assert.That(image, Is.Not.Null.Or.Empty); } } + + [Test] + public async Task Verify_that_reloading_disposes_the_previous_source_stream() + { + var cts = new CancellationTokenSource(); + + var reqifPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "ProR_Traceability-Template-v1.0.reqif"); + + var supportedFileExtensionKind = reqifPath.ConvertPathToSupportedFileExtensionKind(); + + await using (var firstStream = new FileStream(reqifPath, FileMode.Open)) + { + await this.reqIfLoaderService.LoadAsync(firstStream, supportedFileExtensionKind, cts.Token); + } + + var previousSourceStream = GetSourceStream(this.reqIfLoaderService); + Assert.That(previousSourceStream, Is.Not.Null); + + await using (var secondStream = new FileStream(reqifPath, FileMode.Open)) + { + await this.reqIfLoaderService.LoadAsync(secondStream, supportedFileExtensionKind, cts.Token); + } + + Assert.That(() => _ = previousSourceStream.Position, Throws.InstanceOf()); + Assert.That(this.reqIfLoaderService.ReqIFData, Is.Not.Empty); + } + + [Test] + public async Task Verify_that_Reset_disposes_the_source_stream() + { + var cts = new CancellationTokenSource(); + + var reqifPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "ProR_Traceability-Template-v1.0.reqif"); + + var supportedFileExtensionKind = reqifPath.ConvertPathToSupportedFileExtensionKind(); + + await using (var fileStream = new FileStream(reqifPath, FileMode.Open)) + { + await this.reqIfLoaderService.LoadAsync(fileStream, supportedFileExtensionKind, cts.Token); + } + + var sourceStream = GetSourceStream(this.reqIfLoaderService); + Assert.That(sourceStream, Is.Not.Null); + + this.reqIfLoaderService.Reset(); + + Assert.That(() => _ = sourceStream.Position, Throws.InstanceOf()); + Assert.That(this.reqIfLoaderService.ReqIFData, Is.Null); + } + + [Test] + public async Task Verify_that_a_failing_deserializer_propagates_and_leaves_service_usable() + { + var cts = new CancellationTokenSource(); + + var reqifPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "TestData", "ProR_Traceability-Template-v1.0.reqif"); + + var supportedFileExtensionKind = reqifPath.ConvertPathToSupportedFileExtensionKind(); + + var failingLoaderService = new ReqIFLoaderService(new ThrowingDeSerializer()); + + await using (var fileStream = new FileStream(reqifPath, FileMode.Open)) + { + Assert.That( + async () => await failingLoaderService.LoadAsync(fileStream, supportedFileExtensionKind, cts.Token), + Throws.InstanceOf()); + } + + Assert.That(failingLoaderService.ReqIFData, Is.Null); + + await using (var fileStream = new FileStream(reqifPath, FileMode.Open)) + { + await this.reqIfLoaderService.LoadAsync(fileStream, supportedFileExtensionKind, cts.Token); + } + + Assert.That(this.reqIfLoaderService.ReqIFData, Is.Not.Empty); + } + + /// + /// Reads the private sourceStream field of the for white-box assertions. + /// + private static Stream GetSourceStream(ReqIFLoaderService service) + { + var field = typeof(ReqIFLoaderService).GetField("sourceStream", BindingFlags.NonPublic | BindingFlags.Instance); + return (Stream)field?.GetValue(service); + } + + /// + /// An that always throws, used to exercise the error path of + /// . + /// + private sealed class ThrowingDeSerializer : IReqIFDeSerializer + { + public IEnumerable Deserialize(string fileUri, bool validate = false, ValidationEventHandler validationEventHandler = null) + => throw new InvalidOperationException("deserialization failed"); + + public IEnumerable Deserialize(Stream stream, SupportedFileExtensionKind fileExtensionKind, bool validate = false, ValidationEventHandler validationEventHandler = null) + => throw new InvalidOperationException("deserialization failed"); + + public Task> DeserializeAsync(string fileUri, CancellationToken token, bool validate = false, ValidationEventHandler validationEventHandler = null) + => throw new InvalidOperationException("deserialization failed"); + + public Task> DeserializeAsync(Stream stream, SupportedFileExtensionKind fileExtensionKind, CancellationToken token, bool validate = false, ValidationEventHandler validationEventHandler = null) + => throw new InvalidOperationException("deserialization failed"); + } } } \ No newline at end of file diff --git a/ReqIFSharp.Extensions/Services/ReqIFLoaderService.cs b/ReqIFSharp.Extensions/Services/ReqIFLoaderService.cs index a7a2098..66388a0 100644 --- a/ReqIFSharp.Extensions/Services/ReqIFLoaderService.cs +++ b/ReqIFSharp.Extensions/Services/ReqIFLoaderService.cs @@ -154,19 +154,9 @@ private async Task LoadInternalAsync(Stream reqifStream, SupportedFileExtensionK { this.externalObjectDataCache.Clear(); + this.sourceStream?.Dispose(); this.sourceStream = new MemoryStream(); - var deserializationStream = new MemoryStream(); - - this.logger.LogDebug("copying the ReqIF stream to the deserialization stream for deserialization"); - - reqifStream.Seek(0, SeekOrigin.Begin); - await reqifStream.CopyToAsync(deserializationStream, 81920, token); - if (deserializationStream.Position != 0) - { - deserializationStream.Seek(0, SeekOrigin.Begin); - } - this.logger.LogDebug("copying the ReqIF stream to the source stream for safe keeping"); reqifStream.Seek(0, SeekOrigin.Begin); @@ -176,14 +166,26 @@ private async Task LoadInternalAsync(Stream reqifStream, SupportedFileExtensionK this.sourceStream.Seek(0, SeekOrigin.Begin); } - IEnumerable result = null; + IEnumerable result; var sw = Stopwatch.StartNew(); this.logger.LogDebug("starting deserialization"); - result = await this.reqIfDeSerializer.DeserializeAsync(deserializationStream, fileExtensionKind, token); - this.logger.LogDebug("deserialization finished in {Time} [ms]", sw.ElapsedMilliseconds); - deserializationStream.Dispose(); + using (var deserializationStream = new MemoryStream()) + { + this.logger.LogDebug("copying the ReqIF stream to the deserialization stream for deserialization"); + + reqifStream.Seek(0, SeekOrigin.Begin); + await reqifStream.CopyToAsync(deserializationStream, 81920, token); + if (deserializationStream.Position != 0) + { + deserializationStream.Seek(0, SeekOrigin.Begin); + } + + result = await this.reqIfDeSerializer.DeserializeAsync(deserializationStream, fileExtensionKind, token); + } + + this.logger.LogDebug("deserialization finished in {Time} [ms]", sw.ElapsedMilliseconds); this.ReqIFData = result; @@ -261,6 +263,7 @@ private async Task QueryDataInternalAsync(ExternalObject externalObject, /// public void Reset() { + this.sourceStream?.Dispose(); this.sourceStream = null; this.ReqIFData = null; this.externalObjectDataCache.Clear();