From a103cb8ec73b598f0eff923acffd2b7f029923dc Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Fri, 18 Aug 2017 20:10:02 +0200 Subject: [PATCH] TestImageProvider.FileProvider cache is now aware of decoder parameters --- .../ImageProviders/FileProvider.cs | 85 +++++++++- .../Tests/TestImageProviderTests.cs | 154 +++++++++++++++--- .../Tests/TestUtilityExtensionsTests.cs | 9 - 3 files changed, 213 insertions(+), 35 deletions(-) diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index db30bb65d..e2d052d0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Tests { using System; using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Reflection; using ImageSharp.Formats; using ImageSharp.PixelFormats; @@ -20,11 +22,86 @@ namespace ImageSharp.Tests { // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider // are shared between PixelTypes.Color & PixelTypes.Rgba32 - private class Key : Tuple + private class Key : IEquatable { - public Key(PixelTypes pixelType, string filePath, Type customDecoderType = null) - : base(pixelType, filePath, customDecoderType) + private Tuple commonValues; + + private Dictionary decoderParameters; + + public Key(PixelTypes pixelType, string filePath, IImageDecoder customDecoder) + { + Type customType = customDecoder?.GetType(); + this.commonValues = new Tuple(pixelType, filePath, customType); + this.decoderParameters = GetDecoderParameters(customDecoder); + } + + private static Dictionary GetDecoderParameters(IImageDecoder customDecoder) + { + Type type = customDecoder.GetType(); + + var data = new Dictionary(); + + while (type != null && type != typeof(object)) + { + PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (PropertyInfo p in properties) + { + string key = $"{type.FullName}.{p.Name}"; + object value = p.GetValue(customDecoder); + data[key] = value; + } + type = type.GetTypeInfo().BaseType; + } + return data; + } + + public bool Equals(Key other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + if (!this.commonValues.Equals(other.commonValues)) return false; + + if (this.decoderParameters.Count != other.decoderParameters.Count) + { + return false; + } + + foreach (KeyValuePair kv in this.decoderParameters) + { + object otherVal; + if (!other.decoderParameters.TryGetValue(kv.Key, out otherVal)) + { + return false; + } + if (!object.Equals(kv.Value, otherVal)) + { + return false; + } + } + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return this.Equals((Key)obj); + } + + public override int GetHashCode() + { + return this.commonValues.GetHashCode(); + } + + public static bool operator ==(Key left, Key right) + { + return Equals(left, right); + } + + public static bool operator !=(Key left, Key right) { + return !Equals(left, right); } } @@ -53,7 +130,7 @@ namespace ImageSharp.Tests { Guard.NotNull(decoder, nameof(decoder)); - Key key = new Key(this.PixelType, this.FilePath, decoder.GetType()); + Key key = new Key(this.PixelType, this.FilePath, decoder); Image cachedImage = cache.GetOrAdd( key, diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index f10ebeb30..97a791e36 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Tests { using System; + using System.Collections.Concurrent; + using System.Collections.Generic; using System.IO; using ImageSharp.Formats; @@ -61,19 +63,7 @@ namespace ImageSharp.Tests { Assert.Equal(expected, provider.PixelType); } - - [Theory] - [WithBlankImages(1, 1, PixelTypes.Rgba32)] - [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass( - TestImageProvider provider) - where TPixel : struct, IPixel - { - Image img = provider.GetImage(); - - Assert.IsType>(img); - } - + [Theory] [WithFile(TestImages.Bmp.Car, PixelTypes.All, 88)] [WithFile(TestImages.Bmp.F, PixelTypes.All, 88)] @@ -92,33 +82,153 @@ namespace ImageSharp.Tests private class TestDecoder : IImageDecoder { - public int InvocationCount { get; private set; } = 0; + public Image Decode(Configuration configuration, Stream stream) + where TPixel : struct, IPixel + { + invocationCounts[this.callerName]++; + return new Image(42, 42); + } + + // Couldn't make xUnit happy without this hackery: + + private static ConcurrentDictionary invocationCounts = new ConcurrentDictionary(); + + private string callerName = null; + + internal void InitCaller(string name) + { + this.callerName = name; + invocationCounts[name] = 0; + } + + internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + + private static readonly object Monitor = new object(); + + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) + { + action(); + } + } + } + + + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache(TestImageProvider provider) + where TPixel : struct, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + + TestDecoder.DoTestThreadSafe( + () => + { + string testName = nameof(this.GetImage_WithCustomParameterlessDecoder_ShouldUtilizeCache); + + var decoder = new TestDecoder(); + decoder.InitCaller(testName); + + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + + provider.GetImage(decoder); + Assert.Equal(1, TestDecoder.GetInvocationCount(testName)); + }); + } + + private class TestDecoderWithParameters : IImageDecoder + { + public string Param1 { get; set; } + + public int Param2 { get; set; } public Image Decode(Configuration configuration, Stream stream) where TPixel : struct, IPixel { - this.InvocationCount++; + invocationCounts[this.callerName]++; return new Image(42, 42); } + + private static ConcurrentDictionary invocationCounts = new ConcurrentDictionary(); + + private string callerName = null; + + internal void InitCaller(string name) + { + this.callerName = name; + invocationCounts[name] = 0; + } + + internal static int GetInvocationCount(string callerName) => invocationCounts[callerName]; + + private static readonly object Monitor = new object(); + + public static void DoTestThreadSafe(Action action) + { + lock (Monitor) + { + action(); + } + } } + [Theory] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] + public void GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual(TestImageProvider provider) + where TPixel : struct, IPixel + { + Assert.NotNull(provider.Utility.SourceFileOrDescription); + + TestDecoderWithParameters.DoTestThreadSafe( + () => + { + string testName = + nameof(this.GetImage_WithCustomParametricDecoder_ShouldUtilizeCache_WhenParametersAreEqual); + + var decoder1 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 666 }; + decoder1.InitCaller(testName); + + var decoder2 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 666 }; + decoder2.InitCaller(testName); + + provider.GetImage(decoder1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + + provider.GetImage(decoder2); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + }); + } [Theory] [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] - public void GetImage_WithCustomDecoder_ShouldUtilizeCache(TestImageProvider provider) + public void GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual(TestImageProvider provider) where TPixel : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - var decoder = new TestDecoder(); + TestDecoderWithParameters.DoTestThreadSafe( + () => + { + string testName = + nameof(this.GetImage_WithCustomParametricDecoder_ShouldNotUtilizeCache_WhenParametersAreNotEqual); + + var decoder1 = new TestDecoderWithParameters() { Param1 = "Lol", Param2 = 42 }; + decoder1.InitCaller(testName); - provider.GetImage(decoder); - Assert.Equal(1, decoder.InvocationCount); + var decoder2 = new TestDecoderWithParameters() { Param1 = "LoL", Param2 = 42 }; + decoder2.InitCaller(testName); - provider.GetImage(decoder); - Assert.Equal(1, decoder.InvocationCount); + provider.GetImage(decoder1); + Assert.Equal(1, TestDecoderWithParameters.GetInvocationCount(testName)); + + provider.GetImage(decoder2); + Assert.Equal(2, TestDecoderWithParameters.GetInvocationCount(testName)); + }); } + public static string[] AllBmpFiles => TestImages.Bmp.All; [Theory] @@ -224,4 +334,4 @@ namespace ImageSharp.Tests Assert.True(img.Width * img.Height > 0); } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 8651d246b..d4de6c627 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -133,15 +133,6 @@ namespace ImageSharp.Tests AssertContainsPixelType(PixelTypes.RgbaVector, expanded); } - [Fact] - public void Anyad() - { - PixelTypes pixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; - PixelTypes anyad = pixelTypes & PixelTypes.Bgr565; - - this.Output.WriteLine(anyad.ToString()); - } - [Fact] public void ToTypes_All() {