Merge pull request #2 from dnutiu/uwp-testing

Overload ICompressor and IMetadataRemover and accept a Stream
This commit is contained in:
Denis-Cosmin Nutiu 2022-04-03 18:18:17 +03:00 committed by GitHub
commit 1cec529c7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 166 additions and 46 deletions

View file

@ -7,14 +7,17 @@ using Xunit;
namespace ConsoleInterface.Tests; namespace ConsoleInterface.Tests;
public class TestSimpleOutputSink public class TestDirectoryOutputSink
{ {
private readonly string? _testsProjectDirectory; private readonly string? _testsProjectDirectory;
public TestSimpleOutputSink() public TestDirectoryOutputSink()
{ {
_testsProjectDirectory = Environment.GetEnvironmentVariable("IMAGE_CORE_TESTS"); _testsProjectDirectory = Environment.GetEnvironmentVariable("IMAGE_CORE_TESTS");
if (_testsProjectDirectory == null) throw new Exception("Environment variable IMAGE_CORE_TESTS is not set!"); if (_testsProjectDirectory == null)
{
throw new Exception("Environment variable IMAGE_CORE_TESTS is not set!");
}
} }
[Theory] [Theory]
@ -24,14 +27,14 @@ public class TestSimpleOutputSink
[InlineData("dir", "asd", @"dir\asd.jpg")] [InlineData("dir", "asd", @"dir\asd.jpg")]
public void TestGetOutputPath(string directory, string file, string expectedPath) public void TestGetOutputPath(string directory, string file, string expectedPath)
{ {
var sink = SimpleOutputSink.Create(directory); var sink = DirectoryOutputSink.Create(directory);
Assert.Equal(expectedPath, sink.GetOutputPath(file)); Assert.Equal(expectedPath, sink.GetOutputPath(file));
} }
[Fact] [Fact]
public void TestGetOutputPathNull() public void TestGetOutputPathNull()
{ {
var sink = SimpleOutputSink.Create("directory"); var sink = DirectoryOutputSink.Create("directory");
Assert.Throws<ArgumentException>(() => sink.GetOutputPath("")); Assert.Throws<ArgumentException>(() => sink.GetOutputPath(""));
} }
@ -39,7 +42,7 @@ public class TestSimpleOutputSink
public void TestSave() public void TestSave()
{ {
// Setup // Setup
var sink = SimpleOutputSink.Create("directory"); var sink = DirectoryOutputSink.Create("directory");
var metadataRemoverMock = new Mock<IMetadataRemover>(); var metadataRemoverMock = new Mock<IMetadataRemover>();
metadataRemoverMock.Setup(i => i.GetImagePath()).Returns("alo.wtf"); metadataRemoverMock.Setup(i => i.GetImagePath()).Returns("alo.wtf");
@ -47,7 +50,8 @@ public class TestSimpleOutputSink
sink.Save(metadataRemoverMock.Object); sink.Save(metadataRemoverMock.Object);
// Assert // Assert
metadataRemoverMock.Verify(i => i.CleanImage("directory\\alo.jpg")); metadataRemoverMock.Verify(i => i.CleanImage());
metadataRemoverMock.Verify(i => i.SaveImage("directory\\alo.jpg"));
} }
[Fact] [Fact]
@ -55,7 +59,7 @@ public class TestSimpleOutputSink
{ {
// Setup // Setup
Debug.Assert(_testsProjectDirectory != null, nameof(_testsProjectDirectory) + " != null"); Debug.Assert(_testsProjectDirectory != null, nameof(_testsProjectDirectory) + " != null");
var sink = SimpleOutputSink.Create(Path.Combine(_testsProjectDirectory, "test_pictures")); var sink = DirectoryOutputSink.Create(Path.Combine(_testsProjectDirectory, "test_pictures"));
var metadataRemoverMock = new Mock<IMetadataRemover>(); var metadataRemoverMock = new Mock<IMetadataRemover>();
var sourceFileName = Path.Combine(_testsProjectDirectory, "test_pictures\\IMG_0138.HEIC"); var sourceFileName = Path.Combine(_testsProjectDirectory, "test_pictures\\IMG_0138.HEIC");
metadataRemoverMock.Setup(i => i.GetImagePath()).Returns(sourceFileName); metadataRemoverMock.Setup(i => i.GetImagePath()).Returns(sourceFileName);
@ -64,6 +68,7 @@ public class TestSimpleOutputSink
sink.Save(metadataRemoverMock.Object); sink.Save(metadataRemoverMock.Object);
// Assert // Assert
metadataRemoverMock.Verify(i => i.CleanImage(It.IsAny<string>()), Times.Never); metadataRemoverMock.Verify(i => i.CleanImage(), Times.Never);
metadataRemoverMock.Verify(i => i.SaveImage(It.IsAny<string>()), Times.Never);
} }
} }

View file

@ -14,7 +14,10 @@ public class TestLocalFileBrowser
public TestLocalFileBrowser() public TestLocalFileBrowser()
{ {
_testsProjectDirectory = Environment.GetEnvironmentVariable("IMAGE_CORE_TESTS"); _testsProjectDirectory = Environment.GetEnvironmentVariable("IMAGE_CORE_TESTS");
if (_testsProjectDirectory == null) throw new Exception("Environment variable IMAGE_CORE_TESTS is not set!"); if (_testsProjectDirectory == null)
{
throw new Exception("Environment variable IMAGE_CORE_TESTS is not set!");
}
} }
[Fact] [Fact]
@ -39,6 +42,8 @@ public class TestLocalFileBrowser
Assert.NotEmpty(filePathsList); Assert.NotEmpty(filePathsList);
for (var i = 0; i < filePathsList.Count; i++) for (var i = 0; i < filePathsList.Count; i++)
{
Assert.Equal(expectedFileNames[i], Path.GetFileName(filePathsList[i])); Assert.Equal(expectedFileNames[i], Path.GetFileName(filePathsList[i]));
} }
} }
}

View file

@ -8,25 +8,30 @@ using Microsoft.Extensions.Logging.Abstractions;
namespace ConsoleInterface namespace ConsoleInterface
{ {
/// <summary> /// <summary>
/// SimpleOutputFormatter keeps the original file name of the image when formatting it. /// DirectoryOutputSink keeps the original file name of the image when formatting it.
/// SimpleOutputFormatter also saves all the file names into a new directory. /// DirectoryOutputSink also saves all the file names into a new directory.
/// path. /// path.
/// </summary> /// </summary>
public class SimpleOutputSink : IOutputSink public class DirectoryOutputSink : IOutputSink
{ {
public static ILogger Logger = NullLogger.Instance; public static ILogger Logger = NullLogger.Instance;
private readonly string _outputDirectory; private readonly string _outputDirectory;
/// <summary> /// <summary>
/// Creates an instance of SimpleOutputFormatter. /// Creates an instance of DirectoryOutputSink.
/// </summary> /// </summary>
/// <param name="outputDirectory">The output directory.</param> /// <param name="outputDirectory">The output directory.</param>
public SimpleOutputSink(string outputDirectory) public DirectoryOutputSink(string outputDirectory)
{ {
if (outputDirectory.Equals("")) if (outputDirectory.Equals(""))
{
outputDirectory = "."; outputDirectory = ".";
}
else else
{
FileSystemHelpers.CreateDestinationDirectory(outputDirectory); FileSystemHelpers.CreateDestinationDirectory(outputDirectory);
}
_outputDirectory = outputDirectory; _outputDirectory = outputDirectory;
} }
@ -41,7 +46,8 @@ namespace ConsoleInterface
} }
// Save the image under the same name in the new directory. // Save the image under the same name in the new directory.
metadataRemover.CleanImage(newFilePath); metadataRemover.CleanImage();
metadataRemover.SaveImage(newFilePath);
return true; return true;
} }
@ -54,7 +60,9 @@ namespace ConsoleInterface
{ {
Logger.LogDebug($"KeepFilenameFormatter - {_outputDirectory} - {initialFilePath}"); Logger.LogDebug($"KeepFilenameFormatter - {_outputDirectory} - {initialFilePath}");
if (string.IsNullOrEmpty(initialFilePath)) if (string.IsNullOrEmpty(initialFilePath))
{
throw new ArgumentException("The output file path cannot be null or empty!"); throw new ArgumentException("The output file path cannot be null or empty!");
}
var fileName = Path.GetFileName(initialFilePath).Split('.')[0]; var fileName = Path.GetFileName(initialFilePath).Split('.')[0];
var path = Path.Combine(_outputDirectory, $"{fileName}.jpg"); var path = Path.Combine(_outputDirectory, $"{fileName}.jpg");
@ -62,12 +70,12 @@ namespace ConsoleInterface
} }
/// <summary> /// <summary>
/// Creates an instance of OriginalFilenameFileOutputPathFormatter. /// Creates an instance of DirectoryOutputSink.
/// </summary> /// </summary>
/// <param name="outputDirectory">The output directory.</param> /// <param name="outputDirectory">The output directory.</param>
public static SimpleOutputSink Create(string outputDirectory) public static DirectoryOutputSink Create(string outputDirectory)
{ {
return new SimpleOutputSink(outputDirectory); return new DirectoryOutputSink(outputDirectory);
} }
} }
} }

View file

@ -14,7 +14,11 @@ namespace ConsoleInterface
/// <param name="directoryPath">The destination directory's path.</param> /// <param name="directoryPath">The destination directory's path.</param>
public static void CreateDestinationDirectory(string directoryPath) public static void CreateDestinationDirectory(string directoryPath)
{ {
if (Directory.Exists(directoryPath)) return; if (Directory.Exists(directoryPath))
{
return;
}
Logger.LogWarning("Output directory does not exist. Creating."); Logger.LogWarning("Output directory does not exist. Creating.");
Directory.CreateDirectory(directoryPath); Directory.CreateDirectory(directoryPath);
} }

View file

@ -29,7 +29,7 @@ namespace ConsoleInterface
private static void RunOptions(ProgramOptions options) private static void RunOptions(ProgramOptions options)
{ {
SetupLogging(options.LogLevel); SetupLogging(options.LogLevel);
var outputFormatter = SimpleOutputSink.Create(options.DestinationDirectory); var outputFormatter = DirectoryOutputSink.Create(options.DestinationDirectory);
var executor = TaskExecutor.Create(new TaskExecutorOptions var executor = TaskExecutor.Create(new TaskExecutorOptions
{ {
EnableCompression = options.CompressFiles is true, EnableCompression = options.CompressFiles is true,
@ -105,8 +105,8 @@ namespace ConsoleInterface
// File Retriever // File Retriever
LocalFileBrowser.Logger = _loggerFactory.CreateLogger(nameof(LocalFileBrowser)); LocalFileBrowser.Logger = _loggerFactory.CreateLogger(nameof(LocalFileBrowser));
// FileName formatter // FileName formatter
SimpleOutputSink.Logger = DirectoryOutputSink.Logger =
_loggerFactory.CreateLogger(nameof(SimpleOutputSink)); _loggerFactory.CreateLogger(nameof(DirectoryOutputSink));
FileSystemHelpers.Logger = _loggerFactory.CreateLogger(nameof(FileSystemHelpers)); FileSystemHelpers.Logger = _loggerFactory.CreateLogger(nameof(FileSystemHelpers));
Logger.LogTrace("SetupLogging - exit"); Logger.LogTrace("SetupLogging - exit");
} }

View file

@ -51,7 +51,11 @@ namespace ConsoleInterface
$"Cleaning {filePath}, compression {_options.EnableCompression}, outputFormatter {nameof(_options.OutputSink)}."); $"Cleaning {filePath}, compression {_options.EnableCompression}, outputFormatter {nameof(_options.OutputSink)}.");
ICompressor compressor = NullCompressor.Instance; ICompressor compressor = NullCompressor.Instance;
if (_options.EnableCompression) compressor = LosslessCompressor.Instance; if (_options.EnableCompression)
{
compressor = LosslessCompressor.Instance;
}
var imageMagick = new MagickImage(filePath); var imageMagick = new MagickImage(filePath);
IMetadataRemover metadataRemover = new ExifRemoverAndCompressor(imageMagick, compressor); IMetadataRemover metadataRemover = new ExifRemoverAndCompressor(imageMagick, compressor);
return outputSink.Save(metadataRemover); return outputSink.Save(metadataRemover);

View file

@ -5,7 +5,7 @@ using Xunit;
namespace ImageCore.Tests namespace ImageCore.Tests
{ {
public class TestMetadataRemover public class TestExifRemoverAndCompressor
{ {
[Fact] [Fact]
public void TestExifRemoverAndCompressorCleanImage() public void TestExifRemoverAndCompressorCleanImage()
@ -16,7 +16,8 @@ namespace ImageCore.Tests
var metadataRemover = new ExifRemoverAndCompressor(magicImageMock.Object, compressorMock.Object); var metadataRemover = new ExifRemoverAndCompressor(magicImageMock.Object, compressorMock.Object);
// Test // Test
metadataRemover.CleanImage("path"); metadataRemover.CleanImage();
metadataRemover.SaveImage("path");
// Assert // Assert
magicImageMock.Verify(i => i.RemoveProfile("exif")); magicImageMock.Verify(i => i.RemoveProfile("exif"));

View file

@ -34,22 +34,19 @@ namespace ImageCore.Tests
} }
[Fact] [Fact]
public void TestNullCompressor_Compress() public void TestLosslessCompressor_Compress_Stream()
{ {
ICompressor compressor = new NullCompressor(); ICompressor compressor = new LosslessCompressor();
var sourceFileName = Path.Combine(_testsProjectDirectory, "test_pictures/IMG_0138.HEIC"); var sourceFileName = Path.Combine(_testsProjectDirectory, "test_pictures/IMG_0138.HEIC");
var destinationFileName = Path.GetTempFileName(); var destinationFileName = Path.GetTempFileName();
File.Copy(sourceFileName, destinationFileName, true); File.Copy(sourceFileName, destinationFileName, true);
compressor.Compress(destinationFileName);
var originalFile = File.Open(sourceFileName, FileMode.Open); var destinationFileHandle = File.Open(destinationFileName, FileMode.Open);
var compressedFile = File.Open(destinationFileName, FileMode.Open); var lengthBeforeCompression = destinationFileHandle.Length;
Assert.True(compressedFile.Length == originalFile.Length); compressor.Compress(destinationFileHandle);
originalFile.Close(); Assert.True(destinationFileHandle.Length < lengthBeforeCompression);
compressedFile.Close();
File.Delete(destinationFileName);
} }
} }
} }

View file

@ -0,0 +1,37 @@
using System;
using System.IO;
using Image.Core;
using Xunit;
namespace ImageCore.Tests
{
public class TestNullCompressor
{
private readonly string _testsProjectDirectory;
public TestNullCompressor()
{
_testsProjectDirectory = Environment.GetEnvironmentVariable("IMAGE_CORE_TESTS");
}
[Fact]
public void TestNullCompressor_Compress()
{
ICompressor compressor = new NullCompressor();
var sourceFileName = Path.Combine(_testsProjectDirectory, "test_pictures/IMG_0138.HEIC");
var destinationFileName = Path.GetTempFileName();
File.Copy(sourceFileName, destinationFileName, true);
compressor.Compress(destinationFileName);
var originalFile = File.Open(sourceFileName, FileMode.Open);
var compressedFile = File.Open(destinationFileName, FileMode.Open);
Assert.True(compressedFile.Length == originalFile.Length);
originalFile.Close();
compressedFile.Close();
File.Delete(destinationFileName);
}
}
}

View file

@ -1,4 +1,5 @@
using ImageMagick; using System.IO;
using ImageMagick;
namespace Image.Core namespace Image.Core
{ {
@ -22,12 +23,19 @@ namespace Image.Core
} }
/// <summary> /// <summary>
/// Cleans the images and compresses it. /// Cleans the image.
/// </summary> /// </summary>
/// <param name="newFilePath">The file path to save the clean image.</param> public void CleanImage()
public void CleanImage(string newFilePath)
{ {
_magickImage.RemoveProfile("exif"); _magickImage.RemoveProfile("exif");
}
/// <summary>
/// Save the image under a new file path.
/// </summary>
/// <param name="newFilePath">The new path of the image.</param>
public void SaveImage(string newFilePath)
{
_magickImage.Write(newFilePath); _magickImage.Write(newFilePath);
_compressor.Compress(newFilePath); _compressor.Compress(newFilePath);
} }
@ -37,5 +45,15 @@ namespace Image.Core
{ {
return _magickImage.FileName; return _magickImage.FileName;
} }
/// <summary>
/// Saves the image.
/// </summary>
/// <param name="stream">The stream.</param>
public void SaveImage(Stream stream)
{
_magickImage.Write(stream);
_compressor.Compress(stream);
}
} }
} }

View file

@ -1,4 +1,6 @@
namespace Image.Core using System.IO;
namespace Image.Core
{ {
/// <summary> /// <summary>
/// ICompressor is an interface for implementing image compressors. /// ICompressor is an interface for implementing image compressors.
@ -10,5 +12,11 @@
/// </summary> /// </summary>
/// <param name="fileName">The file name of the image to be compressed.</param> /// <param name="fileName">The file name of the image to be compressed.</param>
void Compress(string fileName); void Compress(string fileName);
/// <summary>
/// The method compresses an image in place.
/// </summary>
/// <param name="stream">The stream of the image to be compressed.</param>
void Compress(Stream stream);
} }
} }

View file

@ -1,4 +1,6 @@
namespace Image.Core using System.IO;
namespace Image.Core
{ {
/// <summary> /// <summary>
/// Interface for implementing metadata removers. /// Interface for implementing metadata removers.
@ -6,10 +8,21 @@
public interface IMetadataRemover public interface IMetadataRemover
{ {
/// <summary> /// <summary>
/// Cleans an image and saves it under a new path. /// Cleans an image.
/// </summary>
void CleanImage();
/// <summary>
/// Saves an image under a new file path.
/// </summary> /// </summary>
/// <param name="newFilePath">The file path to save the clean image.</param> /// <param name="newFilePath">The file path to save the clean image.</param>
void CleanImage(string newFilePath); void SaveImage(string newFilePath);
/// <summary>
/// Saves the image.
/// </summary>
/// <param name="stream">The stream.</param>
void SaveImage(Stream stream);
/// <summary> /// <summary>
/// GetImagePath gets the current image path on the filesystem. /// GetImagePath gets the current image path on the filesystem.

View file

@ -1,4 +1,5 @@
using ImageMagick; using System.IO;
using ImageMagick;
namespace Image.Core namespace Image.Core
{ {
@ -22,5 +23,13 @@ namespace Image.Core
{ {
_imageOptimizer.LosslessCompress(fileName); _imageOptimizer.LosslessCompress(fileName);
} }
/// <summary>
/// <inheritdoc />
/// </summary>
public void Compress(Stream stream)
{
_imageOptimizer.LosslessCompress(stream);
}
} }
} }

View file

@ -1,4 +1,6 @@
namespace Image.Core using System.IO;
namespace Image.Core
{ {
/// <summary> /// <summary>
/// Does nothing. Using this Compressor will have no effect. /// Does nothing. Using this Compressor will have no effect.
@ -13,5 +15,9 @@
public void Compress(string fileName) public void Compress(string fileName)
{ {
} }
public void Compress(Stream stream)
{
}
} }
} }

View file

@ -6,5 +6,10 @@
&lt;/AssemblyExplorer&gt;</s:String> &lt;/AssemblyExplorer&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=761234b5_002D46ba_002D4312_002Dab60_002Dd15d5d83a61a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="TestGetFilenamesFromPath" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD; <s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=761234b5_002D46ba_002D4312_002Dab60_002Dd15d5d83a61a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="TestGetFilenamesFromPath" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;&#xD;
&lt;Or&gt;&#xD;
&lt;Project Location="C:\Users\nutiu\RiderProjects\ImgMetadataRemover\ImageCore.Tests" Presentation="&amp;lt;ImageCore.Tests&amp;gt;" /&gt;&#xD; &lt;Project Location="C:\Users\nutiu\RiderProjects\ImgMetadataRemover\ImageCore.Tests" Presentation="&amp;lt;ImageCore.Tests&amp;gt;" /&gt;&#xD;
&lt;TestAncestor&gt;&#xD;
&lt;TestId&gt;xUnit::B915AC83-B6E9-4E0A-BA88-915F629F57C8::net6.0::ConsoleInterface.Tests.TestSimpleOutputSink&lt;/TestId&gt;&#xD;
&lt;/TestAncestor&gt;&#xD;
&lt;/Or&gt;&#xD;
&lt;/SessionState&gt;</s:String></wpf:ResourceDictionary> &lt;/SessionState&gt;</s:String></wpf:ResourceDictionary>