diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs
index 38a499a02f..e0e84dbda2 100644
--- a/tests/ImageSharp.Tests/FileTestBase.cs
+++ b/tests/ImageSharp.Tests/FileTestBase.cs
@@ -46,6 +46,9 @@ namespace ImageSharp.Tests
// TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only
};
+ // TODO: Find a better place for this
+ internal const string TestOutputRoot = "TestOutput/";
+
protected string CreateOutputDirectory(string path, params string[] pathParts)
{
var postFix = "";
@@ -54,7 +57,7 @@ namespace ImageSharp.Tests
postFix = "/" + string.Join("/", pathParts);
}
- path = "TestOutput/" + path + postFix;
+ path = TestOutputRoot + path + postFix;
if (!Directory.Exists(path))
{
diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs
index d4d4a0a89d..5325f7db56 100644
--- a/tests/ImageSharp.Tests/TestFile.cs
+++ b/tests/ImageSharp.Tests/TestFile.cs
@@ -4,6 +4,7 @@
//
namespace ImageSharp.Tests
{
+ using System;
using System.Collections.Concurrent;
using System.IO;
@@ -26,19 +27,28 @@ namespace ImageSharp.Tests
private readonly Image image;
private readonly string file;
- private TestFile(string file)
+ private TestFile(string file, bool decodeImage)
{
this.file = file;
this.Bytes = File.ReadAllBytes(file);
- this.image = new Image(this.Bytes);
+ if (decodeImage)
+ {
+ this.image = new Image(this.Bytes);
+ }
+
}
- public static TestFile Create(string file)
+ public static TestFile Create(string file) => CreateImpl(file, true);
+
+ // No need to decode the image when used by TestImageProvider!
+ internal static TestFile CreateWithoutImage(string file) => CreateImpl(file, false);
+
+ private static TestFile CreateImpl(string file, bool decodeImage)
{
return cache.GetOrAdd(file, (string fileName) =>
{
- return new TestFile(FormatsDirectory + fileName);
+ return new TestFile(FormatsDirectory + fileName, decodeImage);
});
}
@@ -72,6 +82,10 @@ namespace ImageSharp.Tests
public Image CreateImage()
{
+ if (this.image == null)
+ {
+ throw new InvalidOperationException("TestFile.CreateImage() is invalid because instance has been created with decodeImage = false!");
+ }
return new Image(this.image);
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/FlagsHelper.cs b/tests/ImageSharp.Tests/TestUtilities/FlagsHelper.cs
new file mode 100644
index 0000000000..858989b206
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/FlagsHelper.cs
@@ -0,0 +1,242 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Tests.TestUtilities
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ ///
+ /// Helper class for flag manipulation, based on
+ ///
+ /// http://www.codeproject.com/KB/dotnet/enum.aspx
+ ///
+ ///
+ /// Must be enum type (declared using enum keyword)
+ public class FlagsHelper
+ where T : struct, IConvertible
+ {
+ private static readonly EnumConverter Converter;
+
+ static FlagsHelper()
+ {
+ Type type = typeof(T);
+ string[] names = Enum.GetNames(type);
+ var values = (T[])Enum.GetValues(type);
+
+ Converter = new FlagsEnumConverter(names, values);
+ }
+
+ public static T[] GetSortedValues()
+ {
+ T[] vals = (T[])Enum.GetValues(typeof(T));
+ Array.Sort(vals);
+ return vals;
+ }
+
+ public static T Parse(string value, bool ignoreCase = false, bool parseNumeric = true)
+ {
+ return (T)Enum.ToObject(typeof(T), Converter.ParseInternal(value, ignoreCase, parseNumeric));
+ }
+
+ ///
+ /// Converts enum value to string
+ ///
+ /// Enum value converted to int
+ /// If is defined, the enum member name; otherwise the string representation of the .
+ /// If is applied, can return comma-separated list of values
+ public static string ToString(int value)
+ {
+ return Converter.ToStringInternal(value);
+ }
+
+ ///
+ /// Converts enum value to string
+ ///
+ /// Enum value
+ /// If is defined, the enum member name; otherwise the string representation of the .
+ /// If is applied, can return comma-separated list of values
+ public static string ToString(T value)
+ {
+ return Converter.ToStringInternal(value.ToInt32(null));
+ }
+
+ public static bool TryParse(string value, bool ignoreCase, bool parseNumeric, out T result)
+ {
+ int ir;
+ bool b = Converter.TryParseInternal(value, ignoreCase, parseNumeric, out ir);
+ result = (T)Enum.ToObject(typeof(T), ir);
+ return b;
+ }
+
+ public static bool TryParse(string value, bool ignoreCase, out T result)
+ {
+ int ir;
+ bool b = Converter.TryParseInternal(value, ignoreCase, true, out ir);
+ result = (T)Enum.ToObject(typeof(T), ir);
+ return b;
+ }
+
+ public static bool TryParse(string value, out T result)
+ {
+ int ir;
+ bool b = Converter.TryParseInternal(value, false, true, out ir);
+ result = (T)Enum.ToObject(typeof(T), ir);
+ return b;
+ }
+
+ class DictionaryEnumConverter : EnumConverter
+ {
+ protected readonly Dictionary Dic;
+
+ protected DictionaryEnumConverter(string[] names, T[] values)
+ {
+ this.Dic = new Dictionary(names.Length);
+ for (int j = 0; j < names.Length; j++) this.Dic.Add(Convert.ToInt32(values[j], null), names[j]);
+ }
+
+ public override int ParseInternal(string value, bool ignoreCase, bool parseNumber)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ if (value.Length == 0) throw new ArgumentException("Value is empty", nameof(value));
+ char f = value[0];
+ if (parseNumber && (char.IsDigit(f) || f == '+' || f == '-')) return int.Parse(value);
+ StringComparison stringComparison = ignoreCase
+ ? StringComparison.OrdinalIgnoreCase
+ : StringComparison.Ordinal;
+ foreach (KeyValuePair pair in this.Dic)
+ {
+ if (pair.Value.Equals(value, stringComparison)) return pair.Key;
+ }
+
+ throw new ArgumentException("Enum value wasn't found", nameof(value));
+ }
+
+ public override string ToStringInternal(int value)
+ {
+ string n;
+ return this.Dic.TryGetValue(value, out n) ? n : value.ToString();
+ }
+
+ public override bool TryParseInternal(string value, bool ignoreCase, bool parseNumber, out int result)
+ {
+ result = 0;
+ if (string.IsNullOrEmpty(value)) return false;
+ char f = value[0];
+ if (parseNumber && (char.IsDigit(f) || f == '+' || f == '-'))
+ {
+ int i;
+ if (int.TryParse(value, out i))
+ {
+ result = i;
+ return true;
+ }
+
+ return false;
+ }
+
+ StringComparison stringComparison = ignoreCase
+ ? StringComparison.OrdinalIgnoreCase
+ : StringComparison.Ordinal;
+ foreach (KeyValuePair pair in this.Dic)
+ {
+ if (pair.Value.Equals(value, stringComparison))
+ {
+ result = pair.Key;
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ abstract class EnumConverter
+ {
+ public abstract int ParseInternal(string value, bool ignoreCase, bool parseNumber);
+
+ public abstract string ToStringInternal(int value);
+
+ public abstract bool TryParseInternal(string value, bool ignoreCase, bool parseNumber, out int result);
+ }
+
+ class FlagsEnumConverter : DictionaryEnumConverter
+ {
+ private static readonly string[] Seps = new[] { "," };
+
+ private readonly uint[] values;
+
+ public FlagsEnumConverter(string[] names, T[] values)
+ : base(names, values)
+ {
+ this.values = new uint[values.Length];
+ for (int i = 0; i < values.Length; i++) this.values[i] = values[i].ToUInt32(null);
+ }
+
+ public override int ParseInternal(string value, bool ignoreCase, bool parseNumber)
+ {
+ string[] parts = value.Split(Seps, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length == 1) return base.ParseInternal(value, ignoreCase, parseNumber);
+ int val = 0;
+ for (int i = 0; i < parts.Length; i++)
+ {
+ string part = parts[i];
+ int t = base.ParseInternal(part.Trim(), ignoreCase, parseNumber);
+ val |= t;
+ }
+
+ return val;
+ }
+
+ public override string ToStringInternal(int value)
+ {
+ string n;
+ if (this.Dic.TryGetValue(value, out n)) return n;
+ var sb = new StringBuilder();
+ const string sep = ", ";
+ uint uval;
+ unchecked
+ {
+ uval = (uint)value;
+
+ for (int i = this.values.Length - 1; i >= 0; i--)
+ {
+ uint v = this.values[i];
+ if (v == 0) continue;
+ if ((v & uval) == v)
+ {
+ uval &= ~v;
+ sb.Insert(0, sep).Insert(0, this.Dic[(int)v]);
+ }
+ }
+ }
+
+ return uval == 0 && sb.Length > sep.Length ? sb.ToString(0, sb.Length - sep.Length) : value.ToString();
+ }
+
+ public override bool TryParseInternal(string value, bool ignoreCase, bool parseNumber, out int result)
+ {
+ string[] parts = value.Split(Seps, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length == 1) return base.TryParseInternal(value, ignoreCase, parseNumber, out result);
+ int val = 0;
+ for (int i = 0; i < parts.Length; i++)
+ {
+ string part = parts[i];
+ int t;
+ if (!base.TryParseInternal(part.Trim(), ignoreCase, parseNumber, out t))
+ {
+ result = 0;
+ return false;
+ }
+
+ val |= t;
+ }
+
+ result = val;
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/ImageDataAttributeBase.cs
new file mode 100644
index 0000000000..494e3bd171
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageDataAttributeBase.cs
@@ -0,0 +1,73 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+namespace ImageSharp.Tests.TestUtilities
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+
+ using Xunit.Sdk;
+
+ ///
+ /// Base class for Theory Data attributes which pass an instance of to the test cases.
+ ///
+ public abstract class ImageDataAttributeBase : DataAttribute
+ {
+ protected readonly object[] AdditionalParameters;
+
+ protected readonly PixelTypes PixelTypes;
+
+ protected ImageDataAttributeBase(PixelTypes pixelTypes, object[] additionalParameters)
+ {
+ this.PixelTypes = pixelTypes;
+ this.AdditionalParameters = additionalParameters;
+ }
+
+ public override IEnumerable