-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from Advanced-Systems/compression
Compression
- Loading branch information
Showing
8 changed files
with
329 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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,49 @@ | ||
using System; | ||
using System.IO.Compression; | ||
|
||
namespace AdvancedSystems.Core.Abstractions; | ||
|
||
/// <summary> | ||
/// Defines methods for compressing and decompressing data. | ||
/// </summary> | ||
public interface ICompressionService | ||
{ | ||
#region Methods | ||
|
||
/// <summary> | ||
/// Compresses the given byte array using the specified compression level. | ||
/// </summary> | ||
/// <param name="expandedBuffer"> | ||
/// The data to be compressed. | ||
/// </param> | ||
/// <param name="compressionLevel"> | ||
/// The level of compression to apply. | ||
/// </param> | ||
/// <returns> | ||
/// A byte array containing the compressed data. | ||
/// </returns> | ||
/// <exception cref="ArgumentException"> | ||
/// Thrown when <paramref name="expandedBuffer"/> is empty. | ||
/// </exception> | ||
byte[] Compress(ReadOnlyMemory<byte> expandedBuffer, CompressionLevel compressionLevel); | ||
|
||
/// <summary> | ||
/// Expands (decompresses) the given byte array that has been compressed. | ||
/// </summary> | ||
/// <param name="compressedBuffer"> | ||
/// The compressed data to be expanded | ||
/// </param> | ||
/// <returns> | ||
/// A byte array containing the decompressed data. | ||
/// </returns> | ||
/// <exception cref="ArgumentException"> | ||
/// Thrown when <paramref name="compressedBuffer"/> is empty. | ||
/// </exception> | ||
/// <remarks> | ||
/// This method assumes that the provided <paramref name="compressedBuffer"/> contains data that was compressed using | ||
/// a compatible compression algorithm (see also: <seealso cref="Compress(ReadOnlyMemory{byte}, CompressionLevel)"/>). | ||
/// </remarks> | ||
byte[] Expand(ReadOnlyMemory<byte> compressedBuffer); | ||
|
||
#endregion | ||
} |
This file contains 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,76 @@ | ||
using System; | ||
using System.IO.Compression; | ||
using System.Linq; | ||
|
||
using AdvancedSystems.Core.Common; | ||
|
||
using Xunit; | ||
|
||
namespace AdvancedSystems.Core.Tests.Common; | ||
|
||
public class ArchiveTests | ||
{ | ||
#region Tests | ||
|
||
[Fact] | ||
public void TestCompress() | ||
{ | ||
// Arrange | ||
byte[] buffer = Enumerable.Repeat<byte>(0xFF, 100).ToArray(); | ||
|
||
// Act | ||
byte[] compressedBuffer = Archive.Compress(buffer, CompressionLevel.Optimal); | ||
|
||
// Assert | ||
Assert.NotEmpty(compressedBuffer); | ||
Assert.True(buffer.Length > compressedBuffer.Length); | ||
} | ||
|
||
[Fact] | ||
public void TestCompress_ThrowsIfEmpty() | ||
{ | ||
// Arrange | ||
byte[] buffer = Enumerable.Empty<byte>().ToArray(); | ||
|
||
// Act | ||
void Compress() | ||
{ | ||
Archive.Compress(buffer, CompressionLevel.Optimal); | ||
} | ||
|
||
// Assert | ||
Assert.Throws<ArgumentException>(Compress); | ||
} | ||
|
||
[Fact] | ||
public void TestExpand() | ||
{ | ||
// Arrange | ||
byte[] buffer = Enumerable.Repeat<byte>(0xFF, 100).ToArray(); | ||
|
||
// Act | ||
byte[] compressedBuffer = Archive.Compress(buffer, CompressionLevel.Optimal); | ||
byte[] expandedBuffer = Archive.Expand(compressedBuffer); | ||
|
||
// Assert | ||
Assert.Equal(buffer, expandedBuffer); | ||
} | ||
|
||
[Fact] | ||
public void TestExpand_ThrowsIfEmpty() | ||
{ | ||
// Arrange | ||
byte[] buffer = Enumerable.Empty<byte>().ToArray(); | ||
|
||
// Act | ||
void Expand() | ||
{ | ||
Archive.Expand(buffer); | ||
} | ||
|
||
// Assert | ||
Assert.Throws<ArgumentException>(Expand); | ||
} | ||
|
||
#endregion | ||
} |
24 changes: 24 additions & 0 deletions
24
AdvancedSystems.Core.Tests/Fixtures/CompressionServiceFixture.cs
This file contains 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,24 @@ | ||
using AdvancedSystems.Core.Abstractions; | ||
using AdvancedSystems.Core.Services; | ||
|
||
using Microsoft.Extensions.Logging; | ||
|
||
using Moq; | ||
|
||
namespace AdvancedSystems.Core.Tests.Fixtures; | ||
|
||
public class CompressionServiceFixture | ||
{ | ||
public CompressionServiceFixture() | ||
{ | ||
this.CompressionService = new CompressionService(this.Logger.Object); | ||
} | ||
|
||
#region Properties | ||
|
||
public Mock<ILogger<CompressionService>> Logger { get; private set; } = new(); | ||
|
||
public ICompressionService CompressionService { get; private set; } | ||
|
||
#endregion | ||
} |
85 changes: 85 additions & 0 deletions
85
AdvancedSystems.Core.Tests/Services/CompressionServiceTests.cs
This file contains 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,85 @@ | ||
using System; | ||
using System.IO.Compression; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
|
||
using AdvancedSystems.Core.Abstractions; | ||
using AdvancedSystems.Core.DependencyInjection; | ||
using AdvancedSystems.Core.Tests.Fixtures; | ||
|
||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.TestHost; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Extensions.Logging; | ||
|
||
using Moq; | ||
|
||
using Xunit; | ||
|
||
namespace AdvancedSystems.Core.Tests.Services; | ||
|
||
public class CompressionServiceTests : IClassFixture<CompressionServiceFixture> | ||
{ | ||
private readonly CompressionServiceFixture _sut; | ||
|
||
public CompressionServiceTests(CompressionServiceFixture fixture) | ||
{ | ||
this._sut = fixture; | ||
} | ||
|
||
#region Tests | ||
|
||
[Fact] | ||
public void TestCompressionRoundtrip() | ||
{ | ||
// Arrange | ||
var buffer = Enumerable.Repeat<byte>(0xFF, 100).ToArray(); | ||
var compressionLevel = CompressionLevel.Optimal; | ||
|
||
// Act | ||
var compressedBuffer = this._sut.CompressionService.Compress(buffer, compressionLevel); | ||
var expandedBuffer = this._sut.CompressionService.Expand(compressedBuffer); | ||
|
||
// Assert | ||
Assert.NotEmpty(compressedBuffer); | ||
Assert.True(buffer.Length > compressedBuffer.Length); | ||
Assert.Equal(buffer, expandedBuffer); | ||
this._sut.Logger.Verify(x => x.Log( | ||
LogLevel.Trace, | ||
It.IsAny<EventId>(), | ||
It.Is<It.IsAnyType>((v, t) => v.ToString()!.Contains(Enum.GetName(compressionLevel)!)), | ||
It.IsAny<Exception>(), | ||
It.Is<Func<It.IsAnyType, Exception?, string>>((v, t) => true)) | ||
); | ||
} | ||
|
||
[Fact] | ||
public async Task TestAddCompressionService() | ||
{ | ||
// Arrange | ||
using var hostBuilder = await new HostBuilder() | ||
.ConfigureWebHost(builder => | ||
{ | ||
builder.UseTestServer(); | ||
builder.ConfigureServices(services => | ||
{ | ||
services.AddCompressionService(); | ||
}); | ||
builder.Configure(app => | ||
{ | ||
|
||
}); | ||
}) | ||
.StartAsync(); | ||
|
||
// Act | ||
var compressionService = hostBuilder.Services.GetService<ICompressionService>(); | ||
|
||
// Assert | ||
Assert.NotNull(compressionService); | ||
await hostBuilder.StopAsync(); | ||
} | ||
|
||
#endregion | ||
} |
This file contains 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
This file contains 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,51 @@ | ||
using System; | ||
using System.IO; | ||
using System.IO.Compression; | ||
|
||
using AdvancedSystems.Core.Abstractions; | ||
|
||
namespace AdvancedSystems.Core.Common; | ||
|
||
/// <inheritdoc cref="ICompressionService" /> | ||
public static class Archive | ||
{ | ||
/// <inheritdoc cref="ICompressionService.Compress(ReadOnlyMemory{byte}, CompressionLevel)" /> | ||
public static byte[] Compress(ReadOnlyMemory<byte> expandedBuffer, CompressionLevel compressionLevel) | ||
{ | ||
if (expandedBuffer.IsEmpty) throw new ArgumentException("Buffer to compress cannot be empty.", nameof(expandedBuffer)); | ||
using var compressedStream = new MemoryStream(); | ||
|
||
unsafe | ||
{ | ||
fixed (byte* pointer = &expandedBuffer.Span[0]) | ||
{ | ||
using var expandedStream = new UnmanagedMemoryStream(pointer, expandedBuffer.Length); | ||
|
||
using var gzipStream = new GZipStream(compressedStream, compressionLevel, leaveOpen: false); | ||
expandedStream.CopyTo(gzipStream); | ||
} | ||
} | ||
|
||
return compressedStream.ToArray(); | ||
} | ||
|
||
/// <inheritdoc cref="ICompressionService.Expand(ReadOnlyMemory{byte})" /> | ||
public static byte[] Expand(ReadOnlyMemory<byte> compressedBuffer) | ||
{ | ||
if (compressedBuffer.IsEmpty) throw new ArgumentException("Buffer to expand cannot be empty.", nameof(compressedBuffer)); | ||
using var expandedStream = new MemoryStream(); | ||
|
||
unsafe | ||
{ | ||
fixed (byte* pointer = &compressedBuffer.Span[0]) | ||
{ | ||
using var compressedStream = new UnmanagedMemoryStream(pointer, compressedBuffer.Length); | ||
|
||
using var gzipStream = new GZipStream(compressedStream, CompressionMode.Decompress, leaveOpen: false); | ||
gzipStream.CopyTo(expandedStream); | ||
} | ||
} | ||
|
||
return expandedStream.ToArray(); | ||
} | ||
} |
This file contains 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
This file contains 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,37 @@ | ||
using System; | ||
using System.IO.Compression; | ||
|
||
using AdvancedSystems.Core.Abstractions; | ||
using AdvancedSystems.Core.Common; | ||
|
||
using Microsoft.Extensions.Logging; | ||
|
||
namespace AdvancedSystems.Core.Services; | ||
|
||
/// <inheritdoc cref="ICompressionService" /> | ||
public sealed class CompressionService : ICompressionService | ||
{ | ||
private readonly ILogger<CompressionService> _logger; | ||
|
||
public CompressionService(ILogger<CompressionService> logger) | ||
{ | ||
this._logger = logger; | ||
} | ||
|
||
#region Methods | ||
|
||
/// <inheritdoc /> | ||
public byte[] Compress(ReadOnlyMemory<byte> expandedBuffer, CompressionLevel compressionLevel) | ||
{ | ||
this._logger.LogTrace("Compression Level = {CompressionLevel}", compressionLevel); | ||
return Archive.Compress(expandedBuffer, compressionLevel); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public byte[] Expand(ReadOnlyMemory<byte> compressedBuffer) | ||
{ | ||
return Archive.Expand(compressedBuffer); | ||
} | ||
|
||
#endregion | ||
} |