diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 5135f7c..f3ea1fc 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -3,14 +3,17 @@ run-name: CIBuild_${{ github.event_name }}_${{ github.ref_name }}_${{ github.run
env:
PKG_MAJOR_VERSION: 1.2
- PROJECT_NAME: 'DNX.Extensions'
+ PROJECT_NAME: DNX.Extensions
DOTNET_VERSION: 8.0.x
NUGET_VERSION: 5.x
BUILD_CONFIG: Release
BUILD_PLATFORM: Any CPU
PACK_PARAMETERS: ''
+ COVERAGE_WARNING_THRESHOLD: 60
+ COVERAGE_ERROR_THRESHOLD: 80
NUGET_OUTPUT_FOLDER: nupkgs
- BRANCH_RELEASE_CANDIDATE: rc/**
+ BRANCH_PREFIX_RELEASE_CANDIDATE: rc/
+ BRANCH_PREFIX_PUBLISH_CANDIDATE: beta/
on:
push:
@@ -53,6 +56,21 @@ jobs:
echo "package_suffix=${package_suffix}" >> $GITHUB_ENV
+ - name: Determing GitHub Releasing
+ id: should_release
+ run: |
+ should_release=false
+
+ if [ "${{ github.ref }}" == 'refs/heads/main' ]
+ then
+ should_release=true
+ elif [[ "${{ github.ref }}" == refs/heads/${{ env.BRANCH_PREFIX_RELEASE_CANDIDATE }}* ]]
+ then
+ should_publish=true
+ fi
+
+ echo "should_release=${should_release}" >> $GITHUB_ENV
+
- name: Determine package publishing
id: should_publish
run: |
@@ -64,7 +82,7 @@ jobs:
elif [ "${{ github.ref }}" == "refs/heads/main" ]
then
should_publish=true
- elif [[ "${{ github.ref }}" == ${{ env.BRANCH_RELEASE_CANDIDATE }}* ]]
+ elif [[ "${{ github.ref }}" == refs/heads/${{ env.BRANCH_PREFIX_PUBLISH_CANDIDATE }}* ]]
then
should_publish=true
fi
@@ -83,6 +101,10 @@ jobs:
id: package_version
run: echo "package_version=${{ env.assembly_version }}${{ env.package_suffix }}" >> $GITHUB_ENV
+ - name: Show Configuration
+ id: show_configuration
+ run: env
+
outputs:
assembly_version: ${{ env.assembly_version }}
product_version: ${{ env.product_version }}
@@ -114,10 +136,22 @@ jobs:
run: dotnet build --no-restore --configuration ${{ env.BUILD_CONFIG }} /p:"Platform=${{ env.BUILD_PLATFORM }}" /p:"Version=${{ needs.setup.outputs.product_version }}" /p:"AssemblyVersion=${{ needs.setup.outputs.assembly_version }}"
- name: Test
- run: dotnet test --no-restore --no-build --configuration ${{ env.BUILD_CONFIG }} --verbosity normal --collect:"XPlat Code Coverage"
+ run: dotnet test --no-restore --no-build --configuration ${{ env.BUILD_CONFIG }} --verbosity normal --collect:"XPlat Code Coverage" --logger:trx;LogFileName=TestOutput.trx
+
+ - name: Test Results Summary
+ uses: bibipkins/dotnet-test-reporter@v1.4.0
+ if: success() || failure()
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ comment-title: 'Unit Test Results'
+ results-path: "**/*.trx"
+ coverage-type: cobertura
+ #coverage-path: "**/coverage.cobertura.xml"
+ #coverage-threshold: ${{ env.COVERAGE_WARNING_THRESHOLD }}
- name: Code Coverage Report
uses: irongut/CodeCoverageSummary@v1.3.0
+ if: success() || failure()
with:
filename: "**/coverage.cobertura.xml"
badge: true
@@ -127,7 +161,10 @@ jobs:
hide_complexity: false
indicators: true
output: both
- thresholds: '60 80'
+ thresholds: '${{ env.COVERAGE_ERROR_THRESHOLD }} ${{ env.COVERAGE_WARNING_THRESHOLD }}'
+
+ - name: Output Code Coverage
+ run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
- name: Pull Request - Add Coverage Comment
uses: marocchino/sticky-pull-request-comment@v2
@@ -157,7 +194,7 @@ jobs:
## Generate a Release and Tag in git
release:
name: Create GitHub Release
- if: github.ref == 'refs/heads/main' && success()
+ if: needs.setup.outputs.should_release == 'true' && success()
needs:
- setup
@@ -169,9 +206,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
+ - uses: actions/download-artifact@v4
+ with:
+ name: nuget_output
+ path: nuget
+
- name: Build Changelog
id: build_changelog
- uses: mikepenz/release-changelog-builder-action@v3.4.0
+ uses: mikepenz/release-changelog-builder-action@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -184,7 +226,7 @@ jobs:
artifacts: '**/*.nupkg'
- name: Tag git
- uses: pkgdeps/git-tag-action@v2.0.5
+ uses: pkgdeps/git-tag-action@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
github_repo: ${{ github.repository }}
@@ -196,7 +238,7 @@ jobs:
## Publish to NuGet
publish:
name: Publish to NuGet
- if: needs.setup.outputs.should_publish == 'true'
+ if: needs.setup.outputs.should_publish == 'true' && success()
needs:
- setup
@@ -207,7 +249,7 @@ jobs:
steps:
- name: Install NuGet
- uses: NuGet/setup-nuget@v1.1.1
+ uses: NuGet/setup-nuget@v2
with:
nuget-api-key: ${{ secrets.NUGET_API_KEY }}
nuget-version: ${{ env.NUGET_VERSION }}
diff --git a/src/DNX.Extensions/Arrays/ArrayExtensions.cs b/src/DNX.Extensions/Arrays/ArrayExtensions.cs
new file mode 100644
index 0000000..d1e7aa7
--- /dev/null
+++ b/src/DNX.Extensions/Arrays/ArrayExtensions.cs
@@ -0,0 +1,45 @@
+using System;
+
+#pragma warning disable 1591
+
+namespace DNX.Extensions.Arrays;
+
+///
+/// Array Extensions
+///
+public static class ArrayExtensions
+{
+ public static bool IsNullOrEmpty(this Array input)
+ {
+ return input == null || input.Length == 0;
+ }
+
+ public static T[] PadLeft(this T[] input, int length)
+ {
+ var paddedArray = new T[length];
+ var startIdx = length - input.Length;
+ if (length >= input.Length)
+ {
+ Array.Copy(input, 0, paddedArray, startIdx, input.Length);
+ }
+ else
+ {
+ Array.Copy(input, Math.Abs(startIdx), paddedArray, 0, length);
+ }
+
+ return paddedArray;
+ }
+
+ public static T[] ShiftLeft(this T[] input)
+ {
+ if (input == null)
+ {
+ return new T[0];
+ }
+
+ var shiftedArray = new T[input.Length];
+ Array.Copy(input, 1, shiftedArray, 0, input.Length - 1);
+
+ return shiftedArray;
+ }
+}
diff --git a/src/DNX.Extensions/Arrays/ByteArrayExtensions.cs b/src/DNX.Extensions/Arrays/ByteArrayExtensions.cs
new file mode 100644
index 0000000..0204916
--- /dev/null
+++ b/src/DNX.Extensions/Arrays/ByteArrayExtensions.cs
@@ -0,0 +1,32 @@
+using System.Text;
+
+namespace DNX.Extensions.Arrays;
+
+///
+/// Byte Array Extensions
+///
+public static class ByteArrayExtensions
+{
+ //private static string Base62CodingSpace = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+ ///
+ /// Converts A byte array into an ASCII string
+ ///
+ /// The byte[] to turn into the string
+ public static string GetAsciiString(this byte[] input)
+ {
+ return Encoding.ASCII.GetString(input);
+ }
+
+ ///
+ /// Converts A byte array into an hex string
+ ///
+ /// The byte[] to turn into the string
+ public static string ToHexString(this byte[] input)
+ {
+ var hex = new StringBuilder(input.Length * 2);
+ foreach (var b in input)
+ hex.AppendFormat("{0:x2}", b);
+ return hex.ToString();
+ }
+}
diff --git a/src/DNX.Extensions/Assemblies/AssemblyExtensions.cs b/src/DNX.Extensions/Assemblies/AssemblyExtensions.cs
new file mode 100644
index 0000000..17c3c06
--- /dev/null
+++ b/src/DNX.Extensions/Assemblies/AssemblyExtensions.cs
@@ -0,0 +1,48 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Resources;
+
+// ReSharper disable ConvertToUsingDeclaration
+
+namespace DNX.Extensions.Assemblies;
+
+///
+/// Assembly Extensions
+///
+public static class AssemblyExtensions
+{
+ ///
+ /// Gets the embedded resource text.
+ ///
+ /// The assembly.
+ /// Name of the relative resource.
+ /// The name space.
+ ///
+ ///
+ public static string GetEmbeddedResourceText(this Assembly assembly, string relativeResourceName, string nameSpace = null)
+ {
+ try
+ {
+ nameSpace = string.IsNullOrWhiteSpace(nameSpace)
+ ? Path.GetFileNameWithoutExtension(assembly.Location)
+ : nameSpace;
+
+ var resourceName = $"{nameSpace}.{relativeResourceName}";
+
+ using (var stream = assembly.GetManifestResourceStream(resourceName))
+ {
+ using (var reader = new StreamReader(stream))
+ {
+ var result = reader.ReadToEnd();
+
+ return result;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw new MissingManifestResourceException($"{relativeResourceName} not found", e);
+ }
+ }
+}
diff --git a/src/DNX.Extensions/Comparers/StringComparisonEqualityComparer.cs b/src/DNX.Extensions/Comparers/StringComparisonEqualityComparer.cs
new file mode 100644
index 0000000..194ad7a
--- /dev/null
+++ b/src/DNX.Extensions/Comparers/StringComparisonEqualityComparer.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+
+#pragma warning disable IDE0290 // Use Primary Constructor
+
+namespace DNX.Extensions.Comparers;
+
+///
+/// String Equality Comparer based on
+///
+///
+public class StringComparisonEqualityComparer : IEqualityComparer
+{
+ ///
+ /// The string comparison method
+ ///
+ public StringComparison StringComparisonMethod { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public StringComparisonEqualityComparer()
+ : this(StringComparison.CurrentCulture)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The string comparison.
+ public StringComparisonEqualityComparer(StringComparison stringComparisonMethod)
+ {
+ StringComparisonMethod = stringComparisonMethod;
+ }
+
+ ///
+ /// Determines whether the specified objects are equal.
+ ///
+ /// The first object of type T to compare.
+ /// The second object of type T to compare.
+ ///
+ /// true if the specified objects are equal; otherwise, false.
+ ///
+ ///
+ public bool Equals(string x, string y)
+ {
+ return string.Equals(x, y, StringComparisonMethod);
+ }
+
+ ///
+ /// Returns a hash code for this instance.
+ ///
+ /// The object.
+ ///
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ ///
+ public int GetHashCode(string obj)
+ {
+ if (Equals(obj?.ToLowerInvariant(), obj?.ToUpperInvariant()))
+ return obj?.ToLowerInvariant().GetHashCode() ?? default;
+
+ return obj.GetHashCode();
+ }
+}
diff --git a/src/DNX.Extensions/Conversion/ConvertExtensions.cs b/src/DNX.Extensions/Conversion/ConvertExtensions.cs
new file mode 100644
index 0000000..04f8112
--- /dev/null
+++ b/src/DNX.Extensions/Conversion/ConvertExtensions.cs
@@ -0,0 +1,103 @@
+using System;
+
+namespace DNX.Extensions.Conversion;
+
+///
+/// Extensions to simplify type conversion
+///
+public static class ConvertExtensions
+{
+ ///
+ /// Converts to string, or default.
+ ///
+ /// The object.
+ /// The default value.
+ /// System.String.
+ public static string ToStringOrDefault(this object obj, string defaultValue = "")
+ {
+ return obj?.ToString() ?? defaultValue;
+ }
+
+ ///
+ /// Converts to boolean.
+ ///
+ /// The text.
+ /// The default value.
+ /// true/false if can be converted, defaultValue otherwise.
+ public static bool ToBoolean(this string text, bool defaultValue = default)
+ {
+ return bool.TryParse(text, out var value)
+ ? value
+ : defaultValue;
+ }
+
+ ///
+ /// Converts to int32.
+ ///
+ /// The text.
+ /// The default value.
+ /// System.Int32.
+ public static int ToInt32(this string text, int defaultValue = default)
+ {
+ return int.TryParse(text, out var value)
+ ? value
+ : defaultValue;
+ }
+
+ ///
+ /// Converts to enum.
+ ///
+ ///
+ /// The text.
+ /// The default value.
+ /// T.
+ public static T ToEnum(this string text, T defaultValue = default)
+ where T : struct
+ {
+ return Enum.TryParse(text, true, out T value)
+ ? value
+ : defaultValue;
+ }
+
+ ///
+ /// Converts to guid.
+ ///
+ /// The text.
+ /// Guid.
+ public static Guid ToGuid(this string text)
+ {
+ return text.ToGuid(Guid.Empty);
+ }
+
+ ///
+ /// Converts to guid.
+ ///
+ /// The text.
+ /// The default value.
+ /// Guid.
+ public static Guid ToGuid(this string text, Guid defaultValue)
+ {
+ return Guid.TryParse(text, out var result)
+ ? result
+ : defaultValue;
+ }
+
+ ///
+ /// Converts to the specified type.
+ ///
+ ///
+ /// The object.
+ /// The default value.
+ /// T.
+ public static T To(this object obj, T defaultValue = default)
+ {
+ try
+ {
+ return (T)obj ?? defaultValue;
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/DNX.Extensions/Conversion/GuidExtensions.cs b/src/DNX.Extensions/Conversion/GuidExtensions.cs
new file mode 100644
index 0000000..283406d
--- /dev/null
+++ b/src/DNX.Extensions/Conversion/GuidExtensions.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace DNX.Extensions.Conversion;
+
+///
+/// Extensions for working with Guids
+///
+public static class GuidExtensions
+{
+ ///
+ /// Convert any text item to a guid.
+ ///
+ ///
+ /// The result is deterministic in that each text item will always generate the same result
+ /// NOTE: If the text item is actually a Guid, it will NOT be parsed directly to a Guid
+ ///
+ ///
+ ///
+ /// A
+ ///
+ public static Guid ToDeterministicGuid(this string input)
+ {
+ input ??= string.Empty;
+
+ //use MD5 hash to get a 16-byte hash of the string:
+ using var provider = new MD5CryptoServiceProvider();
+
+ var inputBytes = Encoding.Default.GetBytes(input);
+ var hashBytes = provider.ComputeHash(inputBytes);
+
+ //generate a guid from the hash:
+ var hashGuid = new Guid(hashBytes);
+
+ return hashGuid;
+ }
+}
diff --git a/src/DNX.Extensions/DateTimes/DateTimeExtensions.cs b/src/DNX.Extensions/DateTimes/DateTimeExtensions.cs
new file mode 100644
index 0000000..15370b8
--- /dev/null
+++ b/src/DNX.Extensions/DateTimes/DateTimeExtensions.cs
@@ -0,0 +1,163 @@
+using System;
+
+namespace DNX.Extensions.DateTimes;
+
+///
+/// Date Time Extensions
+///
+public static class DateTimeExtensions
+{
+ ///
+ /// Sets the year.
+ ///
+ /// The date time.
+ /// The year.
+ ///
+ public static DateTime SetYear(this DateTime dateTime, int year)
+ {
+ return dateTime.Year == year
+ ? dateTime
+ : dateTime.AddYears(year - dateTime.Year);
+ }
+
+ ///
+ /// Sets the month.
+ ///
+ /// The date time.
+ /// The month.
+ /// if set to true [maintain year].
+ /// if set to true [maintain day].
+ ///
+ public static DateTime SetMonth(this DateTime dateTime, int month, bool maintainYear = true, bool maintainDay = true)
+ {
+ if (dateTime.Month == month)
+ return dateTime;
+
+ var year = dateTime.Year;
+ var day = dateTime.Day;
+
+ var result = dateTime.AddMonths(month - dateTime.Month);
+
+ if (maintainDay)
+ result = result.SetDay(day, false, false);
+ if (maintainYear)
+ result = result.SetYear(year);
+
+ return result;
+ }
+
+ ///
+ /// Sets the day.
+ ///
+ /// The date time.
+ /// The day.
+ /// if set to true [maintain year].
+ /// if set to true [maintain month].
+ ///
+ public static DateTime SetDay(this DateTime dateTime, int day, bool maintainYear = true, bool maintainMonth = true)
+ {
+ if (dateTime.Day == day)
+ return dateTime;
+
+ var year = dateTime.Year;
+ var month = dateTime.Month;
+
+ var result = dateTime.AddDays(day - dateTime.Day);
+
+ if (maintainMonth)
+ result = result.SetMonth(month, false, false);
+ if (maintainYear)
+ result = result.SetYear(year);
+
+ return result;
+ }
+
+ ///
+ /// Resets the hours on a DateTime
+ ///
+ /// The date time.
+ ///
+ public static DateTime ResetHours(this DateTime dateTime)
+ {
+ return dateTime.Subtract(TimeSpan.FromHours(dateTime.Hour));
+ }
+
+ ///
+ /// Resets the minutes on a DateTime
+ ///
+ /// The date time.
+ ///
+ public static DateTime ResetMinutes(this DateTime dateTime)
+ {
+ return dateTime.Subtract(TimeSpan.FromMinutes(dateTime.Minute));
+ }
+
+ ///
+ /// Resets the seconds on a DateTime
+ ///
+ /// The date time.
+ ///
+ public static DateTime ResetSeconds(this DateTime dateTime)
+ {
+ return dateTime.Subtract(TimeSpan.FromSeconds(dateTime.Second));
+ }
+
+ ///
+ /// Resets the milliseconds on a DateTime
+ ///
+ /// The date time.
+ ///
+ public static DateTime ResetMilliseconds(this DateTime dateTime)
+ {
+ return dateTime.Subtract(TimeSpan.FromMilliseconds(dateTime.Millisecond));
+ }
+
+ ///
+ /// Sets the hours on a DateTime
+ ///
+ /// The date time.
+ /// The hours.
+ ///
+ public static DateTime SetHours(this DateTime dateTime, int hours)
+ {
+ return dateTime.ResetHours()
+ .AddHours(hours);
+ }
+
+ ///
+ /// Sets the minutes on a DateTime
+ ///
+ /// The date time.
+ /// The minutes.
+ ///
+ public static DateTime SetMinutes(this DateTime dateTime, int minutes)
+ {
+ return dateTime.ResetMinutes()
+ .AddMinutes(minutes);
+ }
+
+ ///
+ /// Sets the seconds on a DateTime
+ ///
+ /// The date time.
+ /// The seconds.
+ ///
+ public static DateTime SetSeconds(this DateTime dateTime, int seconds)
+ {
+ return dateTime.ResetSeconds()
+ .AddSeconds(seconds);
+ }
+
+ ///
+ /// Sets the milliseconds on a DateTime
+ ///
+ /// The date time.
+ /// The milliseconds.
+ /// Precision on a means this only works for values up to 999
+ ///
+ public static DateTime SetMilliseconds(this DateTime dateTime, double milliseconds)
+ {
+ return dateTime.ResetMilliseconds()
+ .AddMilliseconds(milliseconds);
+ }
+}
diff --git a/src/DNX.Extensions/Dictionaries/DictionaryExtensions.cs b/src/DNX.Extensions/Dictionaries/DictionaryExtensions.cs
new file mode 100644
index 0000000..c57c6cb
--- /dev/null
+++ b/src/DNX.Extensions/Dictionaries/DictionaryExtensions.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace DNX.Extensions.Dictionaries;
+
+///
+/// Dictionary Extensions
+///
+public static class DictionaryExtensions
+{
+ ///
+ /// Gets the specified key value
+ ///
+ /// The type of the k.
+ /// The type of the v.
+ /// The dictionary.
+ /// The key.
+ /// The default value.
+ ///
+ public static TV Get(this IDictionary dictionary, TK key, TV defaultValue = default)
+ {
+ return dictionary != null && key != null && dictionary.TryGetValue(key, out var value)
+ ? value
+ : defaultValue;
+ }
+
+ ///
+ /// Converts a string to Dictionary<string, string>
+ ///
+ /// The text.
+ /// The element separator.
+ /// The value separator.
+ ///
+ public static IDictionary ToStringDictionary(this string text, string elementSeparator = "|", string valueSeparator = "=")
+ {
+ var dictionary = (text ?? string.Empty)
+ .Split(elementSeparator.ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
+ .ToDictionary(
+ x => x.Split(valueSeparator.ToCharArray()).FirstOrDefault(),
+ x => string.Join(valueSeparator, x.Split(valueSeparator.ToCharArray()).Skip(1))
+ );
+
+ return dictionary;
+ }
+
+ ///
+ /// Converts a string to Dictionary<string, object>
+ ///
+ /// The text.
+ /// The element separator.
+ /// The value separator.
+ ///
+ public static IDictionary ToStringObjectDictionary(this string text, string elementSeparator = "|", string valueSeparator = "=")
+ {
+ var dictionary = text
+ .ToStringDictionary(elementSeparator, valueSeparator)
+ .ToDictionary(
+ x => x.Key,
+ x => (object)x.Value
+ );
+
+ return dictionary;
+ }
+}
diff --git a/src/DNX.Extensions/Enumerations/EnumerableExtensions.cs b/src/DNX.Extensions/Enumerations/EnumerableExtensions.cs
deleted file mode 100644
index 04be678..0000000
--- a/src/DNX.Extensions/Enumerations/EnumerableExtensions.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace DNX.Extensions.Enumerations;
-
-public static class EnumerableExtensions
-{
- ///
- /// Determines whether the specified enumerable has any elements and is not null
- ///
- ///
- /// The enumerable.
- /// true if the specified enumerable has any elements; otherwise, false.
- /// Also available as an extension method
- public static bool HasAny(this IEnumerable enumerable)
- {
- return enumerable != null && enumerable.Any();
- }
-
- ///
- /// Determines whether the specified enumerable has any elements that match the predicate and is not null
- ///
- ///
- /// The enumerable.
- /// The predicate.
- /// true if the specified predicate has any elements that match the predicate; otherwise, false.
- /// Also available as an extension method
- public static bool HasAny(this IEnumerable enumerable, Func predicate)
- {
- return enumerable != null && enumerable.Any(predicate);
- }
-}
diff --git a/src/DNX.Extensions/Enums/EnumExtensions.cs b/src/DNX.Extensions/Enums/EnumExtensions.cs
new file mode 100644
index 0000000..149107d
--- /dev/null
+++ b/src/DNX.Extensions/Enums/EnumExtensions.cs
@@ -0,0 +1,52 @@
+using System;
+using System.ComponentModel;
+
+namespace DNX.Extensions.Enums;
+
+///
+/// Enum Extensions
+///
+public static class EnumExtensions
+{
+ ///
+ /// Gets the attribute.
+ ///
+ ///
+ /// The en.
+ ///
+ public static T GetAttribute(this Enum en)
+ {
+ var type = en.GetType();
+
+ var memInfo = type.GetMember(en.ToString());
+
+ if (memInfo.Length > 0)
+ {
+ var attrs = memInfo[0].GetCustomAttributes(typeof(T), false);
+
+ if (attrs.Length > 0)
+ {
+ return (T)attrs[0];
+ }
+ }
+
+ return default;
+ }
+
+ ///
+ /// Retrieve the description on the enum, e.g.
+ /// [Description("Bright Pink")]
+ /// BrightPink = 2,
+ /// Then when you pass in the enum, it will retrieve the description
+ ///
+ /// The Enumeration
+ /// A string representing the friendly name
+ public static string GetDescription(this Enum en)
+ {
+ var attr = en.GetAttribute();
+
+ return attr == null
+ ? en.ToString()
+ : attr.Description;
+ }
+}
diff --git a/src/DNX.Extensions/Execution/RunSafely.cs b/src/DNX.Extensions/Execution/RunSafely.cs
new file mode 100644
index 0000000..114b979
--- /dev/null
+++ b/src/DNX.Extensions/Execution/RunSafely.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Threading.Tasks;
+
+namespace DNX.Extensions.Execution;
+
+///
+/// Execute code without having to implement try...catch
+///
+public class RunSafely
+{
+ ///
+ /// Executes the specified action.
+ ///
+ /// The action.
+ /// The exception handler.
+ public static void Execute(Action action, Action exceptionHandler = null)
+ {
+ try
+ {
+ action.Invoke();
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+ }
+ }
+
+ ///
+ /// Executes the specified function.
+ ///
+ ///
+ /// The function.
+ /// The exception handler.
+ /// T.
+ public static T Execute(Func func, Action exceptionHandler = null)
+ {
+ return Execute(func, default, exceptionHandler);
+ }
+
+ ///
+ /// Executes the specified function.
+ ///
+ ///
+ /// The function.
+ /// The default value.
+ /// The exception handler.
+ /// T.
+ public static T Execute(Func func, T defaultValue, Action exceptionHandler = null)
+ {
+ try
+ {
+ return func.Invoke();
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+
+ return defaultValue;
+ }
+ }
+
+ ///
+ /// execute as an asynchronous operation.
+ ///
+ /// The task.
+ /// The exception handler.
+ public static async Task ExecuteAsync(Task task, Action exceptionHandler = null)
+ {
+ try
+ {
+ await task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+ }
+ }
+
+ ///
+ /// execute as an asynchronous operation.
+ ///
+ ///
+ /// The task.
+ /// The exception handler.
+ /// Task<T>.
+ public static async Task ExecuteAsync(Task task, Action exceptionHandler = null)
+ {
+ return await ExecuteAsync(task, default, exceptionHandler);
+ }
+
+ ///
+ /// execute as an asynchronous operation.
+ ///
+ ///
+ /// The task.
+ /// The default value.
+ /// The exception handler.
+ /// Task<T>.
+ public static async Task ExecuteAsync(Task task, T defaultValue, Action exceptionHandler = null)
+ {
+ try
+ {
+ return await task.ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Execute(() => exceptionHandler?.Invoke(ex));
+
+ return defaultValue;
+ }
+ }
+}
diff --git a/src/DNX.Extensions/IO/DirectoryInfoExtensions.cs b/src/DNX.Extensions/IO/DirectoryInfoExtensions.cs
new file mode 100644
index 0000000..ce2b1e5
--- /dev/null
+++ b/src/DNX.Extensions/IO/DirectoryInfoExtensions.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using DNX.Extensions.Strings;
+
+namespace DNX.Extensions.IO;
+
+///
+/// DirectoryInfo Extensions.
+///
+public static class DirectoryInfoExtensions
+{
+ ///
+ /// Finds files based on pattern
+ ///
+ /// The directory information.
+ /// The pattern.
+ /// if set to true [recurse directories].
+ /// IEnumerable<FileInfo>.
+ public static IEnumerable FindFiles(this DirectoryInfo directoryInfo, string pattern, bool recurseDirectories = true)
+ {
+ var searchOption = recurseDirectories
+ ? SearchOption.AllDirectories
+ : SearchOption.TopDirectoryOnly;
+
+ var files = directoryInfo.Exists
+ ? directoryInfo.EnumerateFiles(pattern, searchOption)
+ : [];
+
+ return files;
+ }
+
+ ///
+ /// Finds files based on pattern
+ ///
+ /// The directory information.
+ /// The patterns.
+ /// if set to true [recurse directories].
+ /// IEnumerable<FileInfo>.
+ public static IEnumerable FindFiles(this DirectoryInfo directoryInfo, string[] patterns, bool recurseDirectories = true)
+ {
+ var fileInfos = patterns
+ .SelectMany(p => directoryInfo.FindFiles(p, recurseDirectories));
+
+ return fileInfos;
+ }
+
+ ///
+ /// Finds directories based on pattern
+ ///
+ /// The directory information.
+ /// The pattern.
+ /// if set to true [recurse directories].
+ /// IEnumerable<DirectoryInfo>.
+ public static IEnumerable FindDirectories(this DirectoryInfo directoryInfo, string pattern, bool recurseDirectories = true)
+ {
+ var searchOption = recurseDirectories
+ ? SearchOption.AllDirectories
+ : SearchOption.TopDirectoryOnly;
+
+ var directoryInfos = directoryInfo.Exists
+ ? directoryInfo.EnumerateDirectories(pattern, searchOption)
+ : [];
+
+ return directoryInfos;
+ }
+
+ ///
+ /// Finds directories based on pattern
+ ///
+ /// The directory information.
+ /// The patterns.
+ /// if set to true [recurse directories].
+ /// IEnumerable<DirectoryInfo>.
+ public static IEnumerable FindDirectories(this DirectoryInfo directoryInfo, string[] patterns, bool recurseDirectories = true)
+ {
+ var directoryInfos = patterns
+ .SelectMany(p => directoryInfo.FindDirectories(p, recurseDirectories));
+
+ return directoryInfos;
+ }
+
+ ///
+ /// Gets the relative file path.
+ ///
+ /// The directory information.
+ /// The owning directory information.
+ /// System.String.
+ public static string GetRelativePath(this DirectoryInfo directoryInfo, DirectoryInfo owningDirectoryInfo)
+ {
+ var relativePath = owningDirectoryInfo == null || directoryInfo == null
+ ? null
+ : GetRelativePath(directoryInfo.FullName, owningDirectoryInfo.FullName)
+ .RemoveStartsWith($".{Path.DirectorySeparatorChar}");
+
+ if (relativePath == ".")
+ {
+ relativePath = string.Empty;
+ }
+
+ return relativePath;
+ }
+ ///
+ /// Returns a relative path string from a full path based on a base path
+ /// provided.
+ ///
+ /// The path to convert. Can be either a file or a directory
+ /// The base path on which relative processing is based. Should be a directory.
+ ///
+ /// String of the relative path.
+ ///
+ /// Examples of returned values:
+ /// test.txt, ..\test.txt, ..\..\..\test.txt, ., .., subdir\test.txt
+ ///
+ ///
+ /// From : http://stackoverflow.com/questions/275689/how-to-get-relative-path-from-absolute-path
+ ///
+ private static string GetRelativePath(string fullPath, string basePath)
+ {
+#if NETSTANDARD2_1_OR_GREATER
+ return Path.GetRelativePath(basePath, fullPath);
+#else
+ // Require trailing path separator for path
+ fullPath = fullPath.EnsureEndsWith(Path.DirectorySeparatorChar.ToString());
+ basePath = basePath.EnsureEndsWith(Path.DirectorySeparatorChar.ToString());
+
+ var baseUri = new Uri(basePath);
+ var fullUri = new Uri(fullPath);
+
+ var relativeUri = baseUri.MakeRelativeUri(fullUri);
+
+ // Uri's use forward slashes so convert back to OS slashes
+ return relativeUri.ToString()
+ .Replace("/", Path.DirectorySeparatorChar.ToString())
+ .RemoveEndsWith(Path.DirectorySeparatorChar.ToString());
+#endif
+ }
+}
diff --git a/src/DNX.Extensions/IO/FileInfoExtensions.cs b/src/DNX.Extensions/IO/FileInfoExtensions.cs
new file mode 100644
index 0000000..0eb34ce
--- /dev/null
+++ b/src/DNX.Extensions/IO/FileInfoExtensions.cs
@@ -0,0 +1,69 @@
+using System;
+using System.IO;
+
+namespace DNX.Extensions.IO;
+
+///
+/// FileInfo Extensions.
+///
+public static class FileInfoExtensions
+{
+ ///
+ /// Gets the name of the relative file.
+ ///
+ /// The file information.
+ /// The directory information.
+ /// System.String.
+ public static string GetRelativeFileName(this FileInfo fileInfo, DirectoryInfo directoryInfo)
+ {
+ return Path.Combine(fileInfo.GetRelativeFilePath(directoryInfo), fileInfo.Name);
+ }
+
+ ///
+ /// Gets the relative file path.
+ ///
+ /// The file information.
+ /// The directory information.
+ /// System.String.
+ public static string GetRelativeFilePath(this FileInfo fileInfo, DirectoryInfo directoryInfo)
+ {
+ return fileInfo.Directory.GetRelativePath(directoryInfo);
+ }
+
+ ///
+ /// Gets the friendly size of the file.
+ ///
+ /// The file information.
+ /// System.String.
+ public static string GetFriendlyFileSize(FileInfo fileInfo)
+ {
+ return GetFriendlyFileSize(fileInfo?.Length ?? 0);
+ }
+
+ ///
+ /// Gets the friendly size of the file.
+ ///
+ /// Size of the file.
+ /// System.String.
+ /// >
+ ///
+ /// Based on: https://stackoverflow.com/questions/281640/how-do-i-get-a-human-readable-file-size-in-bytes-abbreviation-using-net
+ ///
+ public static string GetFriendlyFileSize(long fileSize)
+ {
+ string[] suffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB
+
+ var num = 0d;
+ var place = 0;
+
+ if (fileSize > 0)
+ {
+ var bytes = Math.Abs(fileSize);
+
+ place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
+ num = Math.Round(bytes / Math.Pow(1024, place), 1);
+ }
+
+ return $"{Math.Sign(fileSize) * num}{suffixes[place]}";
+ }
+}
diff --git a/src/DNX.Extensions/Linq/EnumerableExtensions.cs b/src/DNX.Extensions/Linq/EnumerableExtensions.cs
new file mode 100644
index 0000000..ba40738
--- /dev/null
+++ b/src/DNX.Extensions/Linq/EnumerableExtensions.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+// ReSharper disable PossibleMultipleEnumeration
+
+namespace DNX.Extensions.Linq;
+
+///
+/// Enumerable Extensions
+///
+public static class EnumerableExtensions
+{
+ private static readonly Random Randomizer = new();
+
+ ///
+ /// Determines whether the specified enumerable has any elements and is not null
+ ///
+ ///
+ /// The enumerable.
+ /// true if the specified enumerable has any elements; otherwise, false.
+ /// Also available as an extension method
+ public static bool HasAny(this IEnumerable enumerable)
+ {
+ return enumerable != null && enumerable.Any();
+ }
+
+ ///
+ /// Determines whether the specified enumerable has any elements that match the predicate and is not null
+ ///
+ ///
+ /// The enumerable.
+ /// The predicate.
+ /// true if the specified predicate has any elements that match the predicate; otherwise, false.
+ /// Also available as an extension method
+ public static bool HasAny(this IEnumerable enumerable, Func predicate)
+ {
+ return enumerable != null && enumerable.Any(predicate);
+ }
+
+ ///
+ /// Determine if enumerable contains any of the specified candidates
+ ///
+ ///
+ /// The value.
+ /// The candidates.
+ ///
+ /// true if input is one of the specified candidates; otherwise, false.
+ ///
+ public static bool IsOneOf(this T value, params T[] candidates)
+ => value.IsOneOf(candidates?.ToList());
+
+ ///
+ /// Determine if enumerable contains any of the specified candidates, using a Comparer
+ ///
+ ///
+ /// The value.
+ /// The comparer.
+ /// The candidates.
+ ///
+ /// true if input is one of the specified candidates; otherwise, false.
+ ///
+ public static bool IsOneOf(this T value, IEqualityComparer comparer, params T[] candidates)
+ => value.IsOneOf(candidates?.ToList(), comparer);
+
+ ///
+ /// Determines whether input is one of the specified candidates.
+ ///
+ ///
+ /// The input.
+ /// The candidates.
+ ///
+ /// true if input is one of the specified candidates; otherwise, false.
+ ///
+ public static bool IsOneOf(this T input, IEnumerable candidates)
+ {
+ return candidates != null && candidates.Any() && candidates.Contains(input);
+ }
+
+ ///
+ /// Determines whether input is one of the specified candidates, according to Comparison Method
+ ///
+ ///
+ /// The input.
+ /// The candidates.
+ /// The comparer.
+ ///
+ /// true if input is one of the specified candidates; otherwise, false.
+ ///
+ public static bool IsOneOf(this T input, IEnumerable candidates, IEqualityComparer comparer)
+ {
+ return candidates != null && candidates.Any() && candidates.Contains(input, comparer);
+ }
+
+ ///
+ /// Gets the random item.
+ ///
+ ///
+ /// The items.
+ /// The randomizer to use (optional)
+ ///
+ public static T GetRandomItem(this IEnumerable items, Random randomizer = null)
+ {
+ // ReSharper disable PossibleMultipleEnumeration
+ if (!items.HasAny())
+ return default;
+
+ var list = items.ToArray();
+ // ReSharper restore PossibleMultipleEnumeration
+
+ var index = (randomizer ?? Randomizer).Next(list.Length);
+
+ return list[index];
+ }
+
+ ///
+ /// Gets the item at an index position, or default
+ ///
+ ///
+ /// The items.
+ /// The index.
+ ///
+ public static T GetAt(this IList items, int index)
+ {
+ return items != null && index >= 0 && index < items.Count
+ ? items[index]
+ : default;
+ }
+}
diff --git a/src/DNX.Extensions/Properties/launchSettings.json b/src/DNX.Extensions/Properties/launchSettings.json
new file mode 100644
index 0000000..50794bf
--- /dev/null
+++ b/src/DNX.Extensions/Properties/launchSettings.json
@@ -0,0 +1,7 @@
+{
+ "profiles": {
+ "DNX.Extensions": {
+ "commandName": "Project"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DNX.Extensions/Reflection/ReflectionExtensions.cs b/src/DNX.Extensions/Reflection/ReflectionExtensions.cs
new file mode 100644
index 0000000..5efaa5f
--- /dev/null
+++ b/src/DNX.Extensions/Reflection/ReflectionExtensions.cs
@@ -0,0 +1,48 @@
+using System.Reflection;
+
+namespace DNX.Extensions.Reflection;
+
+///
+/// Extensions for simplifying Reflection tasks
+///
+public static class ReflectionExtensions
+{
+ ///
+ /// Gets the property value of an instance by name
+ ///
+ ///
+ /// The instance.
+ /// Name of the property.
+ /// The flags.
+ /// The default value.
+ /// System.Object.
+ public static object GetPropertyValueByName(this T instance, string propertyName, BindingFlags flags, object defaultValue = default)
+ {
+ var pi = typeof(T).GetProperty(propertyName, flags);
+ if (pi == null)
+ {
+ return defaultValue;
+ }
+
+ var allowNonPublic = flags.HasFlag(BindingFlags.NonPublic) || !flags.HasFlag(BindingFlags.Public);
+ var getter = pi.GetGetMethod(allowNonPublic);
+
+ var value = getter?.Invoke(instance, null)
+ ?? defaultValue;
+
+ return value;
+ }
+
+ ///
+ /// Gets a private property value by name
+ ///
+ ///
+ /// The instance.
+ /// Name of the property.
+ /// The default value.
+ /// System.Object.
+ public static object GetPrivatePropertyValue(this T instance, string propertyName, object defaultValue = default)
+ {
+ return instance.GetPropertyValueByName(propertyName, BindingFlags.Instance | BindingFlags.NonPublic, defaultValue);
+ }
+}
diff --git a/src/DNX.Extensions/Strings/ArgumentParserExtensions.cs b/src/DNX.Extensions/Strings/ArgumentParserExtensions.cs
new file mode 100644
index 0000000..bb5d903
--- /dev/null
+++ b/src/DNX.Extensions/Strings/ArgumentParserExtensions.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace DNX.Extensions.Strings;
+
+///
+/// Extensions for textual argument parsing
+///
+public static class ArgumentParserExtensions
+{
+ ///
+ /// Parses a raw command line string into an array of parts
+ ///
+ /// The text.
+ /// IList<System.String>.
+ ///
+ /// See: https://stackoverflow.com/questions/14655023/split-a-string-that-has-white-spaces-unless-they-are-enclosed-within-quotes
+ ///
+ public static IList ParseArguments(this string text)
+ {
+ var parts = Regex.Matches(text, @"[\""].+?[\""]|[^ ]+")
+ .Cast()
+ .Select(m => m.Value.Trim("\"".ToCharArray()))
+ .ToList();
+
+ return parts;
+ }
+}
diff --git a/src/DNX.Extensions/Strings/StringExtensions.cs b/src/DNX.Extensions/Strings/StringExtensions.cs
index 38e4145..5ce6710 100644
--- a/src/DNX.Extensions/Strings/StringExtensions.cs
+++ b/src/DNX.Extensions/Strings/StringExtensions.cs
@@ -4,7 +4,7 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
-using DNX.Extensions.Enumerations;
+using DNX.Extensions.Linq;
// ReSharper disable InconsistentNaming
diff --git a/tests/DNX.Extensions.Tests/Arrays/ArrayExtensionsTests.cs b/tests/DNX.Extensions.Tests/Arrays/ArrayExtensionsTests.cs
new file mode 100644
index 0000000..caf1e79
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Arrays/ArrayExtensionsTests.cs
@@ -0,0 +1,86 @@
+using DNX.Extensions.Arrays;
+using FluentAssertions;
+using Xunit;
+using Xunit.Abstractions;
+
+#pragma warning disable IDE0290
+
+namespace DNX.Extensions.Tests.Arrays
+{
+ public class ArrayExtensionsTests(ITestOutputHelper outputHelper)
+ {
+ [Theory]
+ [InlineData("2,3,4", false)]
+ [InlineData("", true)]
+ [InlineData(null, true)]
+ public void Test_IsNullOrEmpty(string byteArray, bool isEmpty)
+ {
+ var bytes = byteArray?.Split(",", StringSplitOptions.RemoveEmptyEntries)
+ .Select(x => Convert.ToByte(x))
+ .ToArray();
+
+ bytes.IsNullOrEmpty().Should().Be(isEmpty);
+ }
+
+ [Fact]
+ public void Test_PadLeft_when_greater_than_existing_array_length()
+ {
+ var values = "1,2,3,4,5,6".Split(",").ToArray();
+
+ var result = values.PadLeft(8);
+
+ values.Length.Should().Be(6);
+ result.Length.Should().Be(8);
+
+ for (var i = 1; i <= result.Length; ++i)
+ {
+ outputHelper.WriteLine($"Index: {i}");
+ result[^i].Should().Be(i > values.Length ? default : values[^i]);
+ }
+ }
+
+ [Fact]
+ public void Test_PadLeft_when_less_than_existing_array_length()
+ {
+ var values = "1,2,3,4,5,6".Split(",").ToArray();
+
+ var result = values.PadLeft(4);
+
+ values.Length.Should().Be(6);
+ result.Length.Should().Be(4);
+
+ for (var i = 1; i <= result.Length; ++i)
+ {
+ outputHelper.WriteLine($"Index: {i}");
+ result[^i].Should().Be(values[^i]);
+ }
+ }
+
+ [Fact]
+ public void Test_ShiftLeft_populated_array()
+ {
+ var values = "1,2,3,4,5,6".Split(",").ToArray();
+
+ var result = values.ShiftLeft();
+
+ result.Length.Should().Be(values.Length);
+
+ for (var i = 0; i < result.Length; ++i)
+ {
+ outputHelper.WriteLine($"Index: {i}");
+ result[i].Should().Be(i >= values.Length - 1 ? default : values[i + 1]);
+ }
+ }
+
+ [Fact]
+ public void Test_ShiftLeft_null_array()
+ {
+ string[] values = null;
+
+ var result = values.ShiftLeft();
+
+ result.Should().NotBeNull();
+ result.Length.Should().Be(0);
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Arrays/ByteArrayExtensionsTests.cs b/tests/DNX.Extensions.Tests/Arrays/ByteArrayExtensionsTests.cs
new file mode 100644
index 0000000..985bcf5
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Arrays/ByteArrayExtensionsTests.cs
@@ -0,0 +1,40 @@
+using DNX.Extensions.Arrays;
+using FluentAssertions;
+using Xunit;
+
+namespace DNX.Extensions.Tests.Arrays
+{
+ public class ByteArrayExtensionsTests
+ {
+ [Theory]
+ [InlineData("65,66,67,68,69,70", "ABCDEF")]
+ [InlineData("97,98,99,100,101,102", "abcdef")]
+ [InlineData("", "")]
+ [InlineData(null, "")]
+ public void Test_GetAsciiString(string byteText, string expectedResult)
+ {
+ var bytes = byteText == null
+ ? []
+ : byteText.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToByte(x)).ToArray();
+
+ var result = bytes.GetAsciiString();
+
+ result.Should().Be(expectedResult);
+ }
+
+ [Theory]
+ [InlineData("1,2,3,4,5,6", "010203040506")]
+ [InlineData("", "")]
+ [InlineData(null, "")]
+ public void Test_ToHexString(string byteText, string expectedResult)
+ {
+ var bytes = byteText == null
+ ? []
+ : byteText.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(x => Convert.ToByte(x)).ToArray();
+
+ var result = bytes.ToHexString();
+
+ result.Should().Be(expectedResult);
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Assemblies/AssemblyExtensionsTests.cs b/tests/DNX.Extensions.Tests/Assemblies/AssemblyExtensionsTests.cs
new file mode 100644
index 0000000..49111ab
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Assemblies/AssemblyExtensionsTests.cs
@@ -0,0 +1,61 @@
+using System.Reflection;
+using System.Resources;
+using DNX.Extensions.Assemblies;
+using FluentAssertions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace DNX.Extensions.Tests.Assemblies
+{
+ public class AssemblyExtensionsTests(ITestOutputHelper testOutputHelper)
+ {
+ [Fact]
+ public void GetEmbeddedResourceText_can_read_resource_successfully()
+ {
+ // Arrange
+ var name = "TestData.SampleData.json";
+
+ // Act
+ var result = Assembly.GetExecutingAssembly().GetEmbeddedResourceText(name);
+
+ testOutputHelper.WriteLine("Result: {0}", result);
+
+ // Assert
+ result.Should().NotBeNull();
+ }
+
+ [Fact]
+ public void GetEmbeddedResourceText_throws_on_unknown_resource_name()
+ {
+ // Arrange
+ var name = $"{Guid.NewGuid()}.json";
+
+ // Act
+ var ex = Assert.Throws(
+ () => Assembly.GetExecutingAssembly().GetEmbeddedResourceText(name)
+ );
+
+ testOutputHelper.WriteLine("Exception Message: {0}", ex?.Message);
+
+ // Assert
+ ex.Should().NotBeNull();
+ ex.Message.Should().Contain(name);
+ }
+
+ [Fact]
+ public void GetEmbeddedResourceText_can_read_resource_with_specific_namespace_successfully()
+ {
+ // Arrange
+ var name = "SampleData.json";
+ var nameSpace = $"DNX.Extensions.Tests.TestData";
+
+ // Act
+ var result = Assembly.GetExecutingAssembly().GetEmbeddedResourceText(name, nameSpace);
+
+ testOutputHelper.WriteLine("Result: {0}", result);
+
+ // Assert
+ result.Should().NotBeNull();
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Comparers/StringComparisonEqualityComparerTests.cs b/tests/DNX.Extensions.Tests/Comparers/StringComparisonEqualityComparerTests.cs
new file mode 100644
index 0000000..f1161ba
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Comparers/StringComparisonEqualityComparerTests.cs
@@ -0,0 +1,105 @@
+using DNX.Extensions.Comparers;
+using FluentAssertions;
+using Xunit;
+
+namespace DNX.Extensions.Tests.Comparers
+{
+ public class StringComparisonEqualityComparerTests
+ {
+ private StringComparison _comparisonMethod = StringComparison.CurrentCultureIgnoreCase;
+ private StringComparisonEqualityComparer Sut => new(_comparisonMethod);
+
+ [Fact]
+ public void DefaultConstructor_works_as_expected()
+ {
+ var sut = new StringComparisonEqualityComparer();
+
+ sut.StringComparisonMethod.Should().Be(StringComparison.CurrentCulture);
+ }
+
+ [Theory]
+ [MemberData(nameof(StringComparisonValues_Data))]
+ public void Constructor_for_StringComparison_works_as_expected(StringComparison stringComparison)
+ {
+ var sut = new StringComparisonEqualityComparer(stringComparison);
+
+ sut.StringComparisonMethod.Should().Be(stringComparison);
+ }
+
+ [Theory]
+ [MemberData(nameof(Equals_Data))]
+ public void Equals_compares_as_expected(string x, string y, StringComparison stringComparison, bool expectedResult)
+ {
+ // Arrange
+ _comparisonMethod = stringComparison;
+
+ // Act
+ var result = Sut.Equals(x, y);
+
+ // Assert
+ result.Should().Be(expectedResult);
+ }
+
+ [Theory]
+ [MemberData(nameof(Equals_Data))]
+ public void GetHashCode_compares_as_expected(string x, string y, StringComparison stringComparison, bool expectedResult)
+ {
+ // Arrange
+ _comparisonMethod = stringComparison;
+
+ // Act
+ var resultX = Sut.GetHashCode(x);
+ var resultY = Sut.GetHashCode(y);
+
+ // Assert
+ (resultX == resultY).Should().Be(expectedResult);
+ }
+
+ #region TestData
+
+ public static TheoryData StringComparisonValues_Data()
+ {
+ return new TheoryData(
+ Enum.GetValues(typeof(StringComparison))
+ .Cast()
+ );
+ }
+
+ public static TheoryData Equals_Data()
+ {
+ return new TheoryData
+ {
+ { null, null, StringComparison.CurrentCulture, true },
+ { "", "", StringComparison.CurrentCulture, true },
+ { "ClearBank", "", StringComparison.CurrentCulture, false },
+ { "ClearBank", "", StringComparison.CurrentCulture, false },
+ { "", "ClearBank", StringComparison.CurrentCulture, false },
+ { "ClearBank", null, StringComparison.CurrentCulture, false },
+ { null, "ClearBank", StringComparison.CurrentCulture, false },
+
+ { "Clear", "Bank", StringComparison.CurrentCulture, false },
+ { "Clear", "Bank", StringComparison.CurrentCultureIgnoreCase, false },
+ { "Clear", "Bank", StringComparison.InvariantCulture, false },
+ { "Clear", "Bank", StringComparison.InvariantCultureIgnoreCase, false },
+ { "Clear", "Bank", StringComparison.Ordinal, false },
+ { "Clear", "Bank", StringComparison.OrdinalIgnoreCase, false },
+
+ { "ClearBank", "ClearBank", StringComparison.CurrentCulture, true },
+ { "ClearBank", "ClearBank", StringComparison.CurrentCultureIgnoreCase, true },
+ { "ClearBank", "ClearBank", StringComparison.InvariantCulture, true },
+ { "ClearBank", "ClearBank", StringComparison.InvariantCultureIgnoreCase, true },
+ { "ClearBank", "ClearBank", StringComparison.Ordinal, true },
+ { "ClearBank", "ClearBank", StringComparison.OrdinalIgnoreCase, true },
+
+ { "ClearBank", "CLEARBANK", StringComparison.CurrentCulture, false },
+ { "ClearBank", "CLEARBANK", StringComparison.CurrentCultureIgnoreCase, true },
+ { "ClearBank", "CLEARBANK", StringComparison.InvariantCulture, false },
+ { "ClearBank", "CLEARBANK", StringComparison.InvariantCultureIgnoreCase, true },
+ { "ClearBank", "CLEARBANK", StringComparison.Ordinal, false },
+ { "ClearBank", "CLEARBANK", StringComparison.OrdinalIgnoreCase, true },
+ };
+ }
+
+ #endregion
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Configuration/EnvironmentConfig.cs b/tests/DNX.Extensions.Tests/Configuration/EnvironmentConfig.cs
new file mode 100644
index 0000000..ebd8144
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Configuration/EnvironmentConfig.cs
@@ -0,0 +1,9 @@
+using DNX.Extensions.Linq;
+
+namespace DNX.Extensions.Tests.Configuration;
+public class EnvironmentConfig
+{
+ public static bool IsLinuxStyleFileSystem => Environment.OSVersion.Platform.IsOneOf(PlatformID.Unix, PlatformID.MacOSX);
+
+ public static bool IsWindowsStyleFileSystem => Environment.OSVersion.Platform.ToString().StartsWith("Win");
+}
diff --git a/tests/DNX.Extensions.Tests/Conversion/ConvertExtensionsTest.cs b/tests/DNX.Extensions.Tests/Conversion/ConvertExtensionsTest.cs
new file mode 100644
index 0000000..aa5600c
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Conversion/ConvertExtensionsTest.cs
@@ -0,0 +1,229 @@
+using DNX.Extensions.Conversion;
+using FluentAssertions;
+using Xunit;
+
+namespace DNX.Extensions.Tests.Conversion
+{
+ public class ConvertExtensionsTests
+ {
+ public enum Numbers
+ {
+ Zero = 0,
+ One,
+ Two,
+ Three,
+ Four,
+ Five
+ }
+
+ public class TestClass1 { }
+ public class TestClass2 : TestClass1 { }
+ public class TestClass3 { }
+
+ public class ToStringOrDefault
+ {
+ [Theory]
+ [InlineData(1, "1")]
+ [InlineData(12.34, "12.34")]
+ [InlineData(true, "True")]
+ [InlineData((string)null, "")]
+ public void ToStringOrDefault_without_override_can_convert_successfully(object instance, string expected)
+ {
+ var result = instance.ToStringOrDefault();
+
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData(1, "bob", "1")]
+ [InlineData(12.34, "bob", "12.34")]
+ [InlineData(true, "bob", "True")]
+ [InlineData((string)null, "bob", "bob")]
+ public void ToStringOrDefault_with_override_can_convert_successfully(object instance, string defaultValue, string expected)
+ {
+ var result = instance.ToStringOrDefault(defaultValue);
+
+ result.Should().Be(expected);
+ }
+ }
+
+ public class ToBoolean
+ {
+ [Theory]
+ [InlineData("true", true)]
+ [InlineData("TRUE", true)]
+ [InlineData("TrUe", true)]
+ [InlineData("false", false)]
+ [InlineData("FALSE", false)]
+ [InlineData("FaLsE", false)]
+ [InlineData("NotABoolean", false)]
+ public void ToBoolean_without_override_can_convert_successfully(string text, bool expected)
+ {
+ var result = text.ToBoolean();
+
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("true", false, true)]
+ [InlineData("TRUE", false, true)]
+ [InlineData("TrUe", false, true)]
+ [InlineData("false", true, false)]
+ [InlineData("FALSE", true, false)]
+ [InlineData("FaLsE", true, false)]
+ [InlineData("NotABoolean", false, false)]
+ [InlineData("NotABoolean", true, true)]
+ public void ToBoolean_with_override_can_convert_successfully(string text, bool defaultValue, bool expected)
+ {
+ var result = text.ToBoolean(defaultValue);
+
+ result.Should().Be(expected);
+ }
+ }
+
+ public class ToInt32
+ {
+ [Theory]
+ [InlineData("160", 160)]
+ [InlineData("0", 0)]
+ [InlineData("-1", -1)]
+ [InlineData("2147483647", 2147483647)]
+ [InlineData("2147483648", 0)]
+ [InlineData("NotAnInt32", 0)]
+ public void ToInt32_without_override_can_convert_successfully(string text, int expected)
+ {
+ var result = text.ToInt32();
+
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("160", 42, 160)]
+ [InlineData("0", 57, 0)]
+ [InlineData("-1", 5, -1)]
+ [InlineData("2147483647", 12345, 2147483647)]
+ [InlineData("2147483648", 12345, 12345)]
+ [InlineData("NotAnInt32", 0, 0)]
+ [InlineData("NotAnInt32", 222, 222)]
+ public void ToInt32_with_override_can_convert_successfully(string text, int defaultValue, int expected)
+ {
+ var result = text.ToInt32(defaultValue);
+
+ result.Should().Be(expected);
+ }
+ }
+
+ public class ToEnum
+ {
+ [Theory]
+ [InlineData("One", Numbers.One)]
+ [InlineData("FOUR", Numbers.Four)]
+ [InlineData("THREE", Numbers.Three)]
+ [InlineData("BOB", Numbers.Zero)]
+ public void ToEnum_without_override_can_convert_Numbers_enum_successfully(string text, Numbers expected)
+ {
+ var result = text.ToEnum();
+
+ result.Should().Be(expected);
+ ;
+ }
+
+ [Theory]
+ [InlineData("One", Numbers.Five, Numbers.One)]
+ [InlineData("FOUR", Numbers.Five, Numbers.Four)]
+ [InlineData("THREE", Numbers.Five, Numbers.Three)]
+ [InlineData("BOB", Numbers.Five, Numbers.Five)]
+ public void ToEnum_with_override_can_convert_Numbers_enum_successfully(string text, Numbers defaultValue, Numbers expected)
+ {
+ var result = text.ToEnum(defaultValue);
+
+ result.Should().Be(expected);
+ ;
+ }
+ }
+
+ public class ToGuid
+ {
+ [Theory]
+ [InlineData("034B1998-E8C7-4DC0-B0EF-E4D606166756", "034B1998-E8C7-4DC0-B0EF-E4D606166756")]
+ [InlineData("034B1998-E8C7-4DC0-B0EF-E4D606166756", "034b1998-e8c7-4dc0-b0ef-e4d606166756")]
+ [InlineData("bob", "00000000-0000-0000-0000-000000000000")]
+ public void ToGuid_without_default_can_convert_successfully(string text, string expected)
+ {
+ var result = text.ToGuid();
+
+ result.ToString().Should().BeEquivalentTo(expected);
+ }
+
+ [Theory]
+ [InlineData("034B1998-E8C7-4DC0-B0EF-E4D606166756", "00000000-0000-0000-0000-000000000000", "034B1998-E8C7-4DC0-B0EF-E4D606166756")]
+ [InlineData("034B1998-E8C7-4DC0-B0EF-E4D606166756", "00000000-0000-0000-0000-000000000000", "034b1998-e8c7-4dc0-b0ef-e4d606166756")]
+ [InlineData("034B1998-E8C7-4DC0-B0EF-E4D606166756", "E223D5C2-61C5-4FD9-AABE-F8761B4EDCA6", "034b1998-e8c7-4dc0-b0ef-e4d606166756")]
+ [InlineData("bob", "E223D5C2-61C5-4FD9-AABE-F8761B4EDCA6", "E223D5C2-61C5-4FD9-AABE-F8761B4EDCA6")]
+ public void ToGuid_with_default_can_convert_successfully(string text, string defaultValue, string expected)
+ {
+ var result = text.ToGuid(Guid.Parse(defaultValue));
+
+ result.ToString().Should().BeEquivalentTo(expected);
+ }
+ }
+
+ public class To
+ {
+ [Fact]
+ public void To_without_default_for_null_class_instance_can_convert_object_successfully()
+ {
+ var instance = (TestClass2)null;
+ var result = instance.To();
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void To_without_default_for_related_class_can_convert_object_successfully()
+ {
+ var instance = new TestClass2();
+ var result = instance.To();
+ result.Should().NotBeNull();
+ result.Should().Be(instance);
+ }
+
+ [Fact]
+ public void To_without_default_for_unrelated_class_can_convert_object_successfully()
+ {
+ var instance = new TestClass3();
+ var result = instance.To();
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void To_with_default_for_null_class_instance_can_convert_object_successfully()
+ {
+ var instance = (TestClass2)null;
+ var defaultValue = new TestClass1();
+ var result = instance.To(defaultValue);
+ result.Should().NotBeNull();
+ result.Should().Be(defaultValue);
+ }
+
+ [Fact]
+ public void To_with_default_for_related_class_can_convert_object_successfully()
+ {
+ var defaultValue = new TestClass1();
+ var instance = new TestClass2();
+ var result = instance.To(defaultValue);
+ result.Should().NotBeNull();
+ result.Should().Be(instance);
+ }
+
+ [Fact]
+ public void To_with_default_for_unrelated_class_can_convert_object_successfully()
+ {
+ var defaultValue = new TestClass1();
+ var instance = new TestClass3();
+ var result = instance.To(defaultValue);
+ result.Should().NotBeNull();
+ result.Should().Be(defaultValue);
+ }
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Conversion/GuidExtensionsTests.cs b/tests/DNX.Extensions.Tests/Conversion/GuidExtensionsTests.cs
new file mode 100644
index 0000000..877a6db
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Conversion/GuidExtensionsTests.cs
@@ -0,0 +1,48 @@
+using DNX.Extensions.Conversion;
+using FluentAssertions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace DNX.Extensions.Tests.Conversion
+{
+ public class GuidExtensionsTests(ITestOutputHelper outputHelper)
+ {
+ [Theory]
+ [InlineData("ABC")]
+ [InlineData("abc")]
+ [InlineData("")]
+ [InlineData(null)]
+ [InlineData("497111F7-1511-49A8-8DB2-31B5E40953CB")]
+ public void ToDeterministicGuid_can_create_a_guid_from_any_string(string text)
+ {
+ // Act
+ var result = text.ToDeterministicGuid();
+ outputHelper.WriteLine($"Text: {text} = {result}");
+
+ // Assert
+ result.Should().NotBe(Guid.Empty);
+ result.ToString().Should().NotBe(text);
+ }
+
+ [Theory]
+ [InlineData("ABC", "d2bd2f90-dfb1-4f0c-70b4-a5d23525e932")]
+ [InlineData("abc", "98500190-d23c-b04f-d696-3f7d28e17f72")]
+ [InlineData("", "d98c1dd4-008f-04b2-e980-0998ecf8427e")]
+ [InlineData(null, "d98c1dd4-008f-04b2-e980-0998ecf8427e")]
+ [InlineData("497111F7-1511-49A8-8DB2-31B5E40953CB", "811f4c34-f777-5718-e90f-5b03aee2b92f")]
+ [InlineData("Strings can be any length, and can be much longer than a standard Guid length", "151991b0-80dc-5902-d525-3a2f3c901477")]
+ public void ToDeterministicGuid_will_always_generate_a_predictable_result(string text, string expected)
+ {
+ // Act
+ var result = text.ToDeterministicGuid();
+ var result2 = text.ToDeterministicGuid();
+ outputHelper.WriteLine($"Text: {text} = {result}");
+
+ // Assert
+ result.Should().NotBe(Guid.Empty);
+ result.ToString().Should().NotBe(text);
+ result.Should().Be(result2);
+ result.ToString().Should().Be(expected);
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/DNX.Extensions.Tests.csproj b/tests/DNX.Extensions.Tests/DNX.Extensions.Tests.csproj
index ef324c8..f5b5536 100644
--- a/tests/DNX.Extensions.Tests/DNX.Extensions.Tests.csproj
+++ b/tests/DNX.Extensions.Tests/DNX.Extensions.Tests.csproj
@@ -8,6 +8,20 @@
disable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
all
diff --git a/tests/DNX.Extensions.Tests/DateTimes/DateTimeExtensionsTests.cs b/tests/DNX.Extensions.Tests/DateTimes/DateTimeExtensionsTests.cs
new file mode 100644
index 0000000..19710df
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/DateTimes/DateTimeExtensionsTests.cs
@@ -0,0 +1,156 @@
+using DNX.Extensions.DateTimes;
+using FluentAssertions;
+using Xunit;
+
+namespace DNX.Extensions.Tests.DateTimes
+{
+ public class DateTimeExtensionsTests
+ {
+ private static readonly Random Randomizer = new();
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void SetYear_can_operate_as_expected(DateTime dateTime)
+ {
+ var year = Randomizer.Next(1, 9999);
+ var month = dateTime.Month;
+ var day = dateTime.Day;
+
+ var result = dateTime.SetYear(year);
+
+ result.Year.Should().Be(year);
+ result.Month.Should().Be(month);
+ result.Day.Should().Be(day);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void SetMonth_can_operate_as_expected(DateTime dateTime)
+ {
+ var year = dateTime.Year;
+ var month = Randomizer.Next(1, 12);
+ var day = dateTime.Day;
+
+ var result = dateTime.SetMonth(month);
+
+ result.Year.Should().Be(year);
+ result.Month.Should().Be(month);
+ result.Day.Should().Be(day);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void SetDay_can_operate_as_expected(DateTime dateTime)
+ {
+ var year = dateTime.Year;
+ var month = dateTime.Month;
+ var day = Randomizer.Next(1, 28);
+
+ var result = dateTime.SetDay(day);
+
+ result.Year.Should().Be(year);
+ result.Month.Should().Be(month);
+ result.Day.Should().Be(day);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void ResetHours_can_operate_as_expected(DateTime dateTime)
+ {
+ var result = dateTime.ResetHours();
+
+ result.Hour.Should().Be(0);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void ResetMinutes_can_operate_as_expected(DateTime dateTime)
+ {
+ var result = dateTime.ResetMinutes();
+
+ result.Minute.Should().Be(0);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void ResetSeconds_can_operate_as_expected(DateTime dateTime)
+ {
+ var result = dateTime.ResetSeconds();
+
+ result.Second.Should().Be(0);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void ResetMilliseconds_can_operate_as_expected(DateTime dateTime)
+ {
+ var result = dateTime.ResetMilliseconds();
+
+ result.Millisecond.Should().Be(0);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void SetHours_can_operate_as_expected(DateTime dateTime)
+ {
+ var value = Randomizer.Next(1, 24);
+
+ var result = dateTime.SetHours(value);
+
+ result.Hour.Should().Be(value);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void SetMinutes_can_operate_as_expected(DateTime dateTime)
+ {
+ var value = Randomizer.Next(1, 60);
+
+ var result = dateTime.SetMinutes(value);
+
+ result.Minute.Should().Be(value);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void SetSeconds_can_operate_as_expected(DateTime dateTime)
+ {
+ var value = Randomizer.Next(1, 60);
+
+ var result = dateTime.SetSeconds(value);
+
+ result.Second.Should().Be(value);
+ }
+
+ [Theory]
+ [MemberData(nameof(ResetDateTime_Data))]
+ public void SetMilliseconds_can_operate_as_expected(DateTime dateTime)
+ {
+ var value = Randomizer.Next(1, 999);
+
+ var result = dateTime.SetMilliseconds(value);
+
+ result.Millisecond.Should().Be(value);
+ }
+
+ public static TheoryData ResetDateTime_Data()
+ {
+ var data = new TheoryData
+ {
+ { DateTime.UtcNow },
+ { DateTime.UnixEpoch },
+ { DateTime.Parse("2021-11-05 20:53:44.12345") },
+ { DateTime.Parse("2021-11-05 20:53:44") },
+ { DateTime.Parse("2021-11-05 20:53") },
+ { DateTime.Parse("2021-11-05") },
+ };
+
+ if (DateTime.Now.Hour != DateTime.UtcNow.Hour)
+ {
+ data.Add(DateTime.Now);
+ }
+
+ return data;
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Dictionaries/DictionaryExtensionsTests.cs b/tests/DNX.Extensions.Tests/Dictionaries/DictionaryExtensionsTests.cs
new file mode 100644
index 0000000..9c02f5a
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Dictionaries/DictionaryExtensionsTests.cs
@@ -0,0 +1,105 @@
+using DNX.Extensions.Dictionaries;
+using FluentAssertions;
+using Xunit;
+
+namespace DNX.Extensions.Tests.Dictionaries
+{
+ public class DictionaryExtensionsTests
+ {
+ [Theory]
+ [InlineData("Sunday", 0)]
+ [InlineData("Monday", 1)]
+ [InlineData("Thursday", 4)]
+ [InlineData("Saturday", 6)]
+ [InlineData("NotADay", 0)]
+ public void Can_get_dictionary_value_safely_or_default(string key, int expectedValue)
+ {
+ // Arrange
+ var dict = Enum.GetNames(typeof(DayOfWeek))
+ .ToDictionary(
+ x => x,
+ x => (int)(Enum.Parse(x))
+ );
+
+ // Act
+ var value = dict.Get(key);
+
+ // Assert
+ value.Should().Be(expectedValue);
+ }
+
+ [Theory]
+ [InlineData("Sunday", 999, 0)]
+ [InlineData("Monday", 999, 1)]
+ [InlineData("NotADay", 999, 999)]
+ [InlineData("NotADay", 0, 0)]
+ public void Can_get_dictionary_value_safely_overriding_default(string key, int defaultValue, int expectedValue)
+ {
+ // Arrange
+ var dict = Enum.GetNames(typeof(DayOfWeek))
+ .ToDictionary(
+ x => x,
+ x => (int)(Enum.Parse(x))
+ );
+
+ // Act
+ var value = dict.Get(key, defaultValue);
+
+ // Assert
+ value.Should().Be(expectedValue);
+ }
+
+ [Fact]
+ public void Can_split_well_formed_string_into_string_dictionary()
+ {
+ // Arrange
+ var text = "Liverpool=1|Southend United=7";
+
+ // Act
+ var result = text.ToStringDictionary();
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().BeOfType(typeof(Dictionary));
+ result.Keys.Count.Should().Be(2);
+ result.ContainsKey("Liverpool").Should().Be(true);
+ result["Liverpool"].Should().Be("1");
+ result.ContainsKey("Southend United").Should().Be(true);
+ result["Southend United"].Should().Be("7");
+ }
+
+ [Fact]
+ public void Null_string_is_handled_into_string_dictionary()
+ {
+ // Arrange
+ const string text = null;
+
+ // Act
+ var result = text.ToStringDictionary();
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().BeOfType(typeof(Dictionary));
+ result.Keys.Count.Should().Be(0);
+ }
+
+ [Fact]
+ public void Can_split_well_formed_string_into_string_object_dictionary()
+ {
+ // Arrange
+ var text = "Liverpool=1|Southend United=7";
+
+ // Act
+ var result = text.ToStringObjectDictionary();
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Should().BeOfType(typeof(Dictionary));
+ result.Keys.Count.Should().Be(2);
+ result.ContainsKey("Liverpool").Should().Be(true);
+ result["Liverpool"].Should().Be("1");
+ result.ContainsKey("Southend United").Should().Be(true);
+ result["Southend United"].Should().Be("7");
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Enumerations/EnumerableExtensionsTests.cs b/tests/DNX.Extensions.Tests/Enumerations/EnumerableExtensionsTests.cs
deleted file mode 100644
index 876fd1e..0000000
--- a/tests/DNX.Extensions.Tests/Enumerations/EnumerableExtensionsTests.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using DNX.Extensions.Enumerations;
-using FluentAssertions;
-using Xunit;
-
-namespace DNX.Extensions.Tests.Enumerations;
-
-public class EnumerableExtensionsTests
-{
- [Theory]
- [InlineData("", false)]
- [InlineData(null, false)]
- [InlineData("a,b,c,d,e,f,g,h,i,j", true)]
- public void Test_HasAny(string commaDelimitedArray, bool expectedResult)
- {
- var enumerable = commaDelimitedArray?
- .Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
-
- // Act
- var result = enumerable.HasAny();
-
- // Assert
- result.Should().Be(expectedResult);
- }
-
- [Theory]
- [InlineData("", "1", false)]
- [InlineData(null, "1", false)]
- [InlineData("a1,b2,c1,d2,e1,f2,g1,h2,i1,j2", "1", true)]
- [InlineData("a1,b2,c1,d2,e1,f2,g1,h2,i1,j2", "2", true)]
- [InlineData("a1,b2,c1,d2,e1,f2,g1,h2,i1,j2", "0", false)]
- public void Test_HasAny_predicate(string commaDelimitedArray, string suffix, bool expectedResult)
- {
- var enumerable = commaDelimitedArray?
- .Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
-
- // Act
- var result = enumerable.HasAny(s => s.EndsWith(suffix));
-
- // Assert
- result.Should().Be(expectedResult);
- }
-}
diff --git a/tests/DNX.Extensions.Tests/Enums/EnumExtensionsTests.cs b/tests/DNX.Extensions.Tests/Enums/EnumExtensionsTests.cs
new file mode 100644
index 0000000..a64622a
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Enums/EnumExtensionsTests.cs
@@ -0,0 +1,42 @@
+using System.ComponentModel;
+using DNX.Extensions.Enums;
+using FluentAssertions;
+using Xunit;
+
+namespace DNX.Extensions.Tests.Enums
+{
+ public enum MyType
+ {
+ One = 1,
+
+ [Description("Number 2")]
+ Two = 2,
+
+ [Description]
+ Three = 3,
+
+ [Description(null)]
+ Four = 4,
+
+ [Description("")]
+ Five = 5
+ }
+
+ public class EnumExtensionsTests
+ {
+ [Theory]
+ [InlineData(MyType.One, "One")]
+ [InlineData(MyType.Two, "Number 2")]
+ [InlineData(MyType.Three, "")]
+ [InlineData(MyType.Four, null)]
+ [InlineData(MyType.Five, "")]
+ public void GetDescription_can_retrieve_value_correctly(MyType myType, string expectedResult)
+ {
+ // Act
+ var result = myType.GetDescription();
+
+ // Assert
+ result.Should().Be(expectedResult, $"{myType} has description: {result}");
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Execution/RunSafelyTests.cs b/tests/DNX.Extensions.Tests/Execution/RunSafelyTests.cs
new file mode 100644
index 0000000..bbfa8db
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Execution/RunSafelyTests.cs
@@ -0,0 +1,318 @@
+using DNX.Extensions.Execution;
+using FluentAssertions;
+using Xunit;
+
+// ReSharper disable InconsistentNaming
+
+namespace DNX.Extensions.Tests.Execution
+{
+ public class RunSafelyTests
+ {
+ public class Execute_Tests
+ {
+ [Fact]
+ public void Can_run_simple_action_that_succeeds()
+ {
+ // Arrange
+ var guid = Guid.NewGuid();
+ var value = Guid.Empty;
+
+ // Act
+ RunSafely.Execute(() => value = guid);
+
+ // Assert
+ value.Should().Be(guid);
+ }
+
+ [Fact]
+ public void Can_handle_simple_action_that_fails()
+ {
+ // Act
+ RunSafely.Execute(() => throw new Exception(nameof(Can_handle_simple_action_that_fails)));
+ }
+
+ [Fact]
+ public void Can_handle_simple_action_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var value = int.MaxValue;
+ var guid = Guid.NewGuid();
+ var message = "";
+
+ // Act
+ RunSafely.Execute(() => throw new Exception(guid.ToString()), ex => message = ex.Message);
+
+ // Assert
+ value.Should().Be(int.MaxValue);
+ message.Should().NotBeNull();
+ message.Should().NotBeNull(guid.ToString());
+ }
+
+ [Fact]
+ public void Can_handle_simple_func_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+
+ // Act
+ RunSafely.Execute(() => value = dividend / divisor);
+
+ // Assert
+ value.Should().Be(0);
+ }
+
+ [Fact]
+ public void Can_handle_simple_func_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+ var message = "";
+
+ // Act
+ RunSafely.Execute(() => value = dividend / divisor, ex => message = ex.Message);
+
+ // Assert
+ value.Should().Be(0);
+ message.Should().NotBeNullOrEmpty();
+ message.Should().Contain("divide by zero");
+ }
+ }
+
+ public class ExecuteT_Tests
+ {
+ [Fact]
+ public void Can_run_simple_func_that_succeeds()
+ {
+ // Arrange
+ var guid = Guid.NewGuid();
+
+ // Act
+ var value = RunSafely.Execute(() => guid);
+
+ // Assert
+ value.Should().Be(guid);
+ }
+
+ [Fact]
+ public void Can_handle_simple_func_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor);
+
+ // Assert
+ value.Should().Be(default);
+ }
+
+ [Fact]
+ public void Can_handle_simple_func_with_default_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor, defaultResult);
+
+ // Assert
+ value.Should().Be(defaultResult);
+ }
+
+ [Fact]
+ public void Can_handle_simple_func_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var message = "";
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor, ex => message = ex.Message);
+
+ // Assert
+ value.Should().Be(default);
+ message.Should().NotBeNullOrEmpty();
+ message.Should().Contain("divide by zero");
+ }
+
+ [Fact]
+ public void Can_handle_simple_func_with_default_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+ var message = "";
+
+ // Act
+ var value = RunSafely.Execute(() => dividend / divisor, defaultResult, ex => message = ex.Message);
+
+ // Assert
+ value.Should().Be(defaultResult);
+ message.Should().NotBeNullOrEmpty();
+ message.Should().Contain("divide by zero");
+ }
+ }
+
+ public class ExecuteAsync_Tests
+ {
+ [Fact]
+ public async Task Can_run_simple_task_that_succeeds()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 20;
+ var value = 0;
+
+ var task = new Task(() => value = dividend / divisor);
+ task.Start();
+
+ // Act
+ await RunSafely.ExecuteAsync(task);
+
+ // Assert
+ value.Should().Be(dividend / divisor);
+ }
+
+ [Fact]
+ public async Task Can_handle_simple_action_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+
+ var task = new Task(() => value = dividend / divisor);
+ task.Start();
+
+ // Act
+ await RunSafely.ExecuteAsync(task);
+
+ // Assert
+ value.Should().Be(0);
+ }
+
+ [Fact]
+ public async Task Can_handle_simple_action_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+ var message = "";
+
+ var task = new Task(() => value = dividend / divisor);
+ task.Start();
+
+ // Act
+ await RunSafely.ExecuteAsync(task, ex => message = ex.Message);
+
+ // Assert
+ value.Should().Be(0);
+ message.Should().NotBeNullOrEmpty();
+ message.Should().Contain("divide by zero");
+ }
+ }
+
+ public class ExecuteAsyncT_Tests
+ {
+ private static async Task DivideAsync(int dividend, int divisor)
+ {
+ var quotient = await Task.Run(() => dividend / divisor);
+
+ return quotient;
+ }
+
+ [Fact]
+ public async Task Can_run_simple_func_that_succeeds()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 50;
+
+ // Act
+ var value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor));
+
+ // Assert
+ value.Should().Be(dividend / divisor);
+ }
+
+ [Fact]
+ public async Task Can_run_simple_func_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor));
+
+ // Assert
+ value.Should().Be(default);
+ }
+
+ [Fact]
+ public async Task Can_run_simple_func_with_default_that_fails()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+ var value = 0;
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor), defaultResult);
+
+ // Assert
+ value.Should().Be(defaultResult);
+ }
+
+ [Fact]
+ public async Task Can_handle_simple_func_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var value = 0;
+ var message = "";
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor), ex => message = ex.Message);
+
+ // Assert
+ value.Should().Be(default);
+ message.Should().NotBeNull();
+ message.Should().NotBeNull("divide by zero");
+ }
+
+ [Fact]
+ public async Task Can_handle_simple_func_with_default_that_fails_and_extract_exception()
+ {
+ // Arrange
+ var dividend = 1000;
+ var divisor = 0;
+ var defaultResult = 500;
+ var value = 0;
+ var message = "";
+
+ // Act
+ value = await RunSafely.ExecuteAsync(DivideAsync(dividend, divisor), defaultResult,
+ ex => message = ex.Message);
+
+ // Assert
+ value.Should().Be(defaultResult);
+ message.Should().NotBeNull();
+ message.Should().Contain("divide by zero");
+ }
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/IO/DirectoryInfoExtensionsTests.cs b/tests/DNX.Extensions.Tests/IO/DirectoryInfoExtensionsTests.cs
new file mode 100644
index 0000000..f511b91
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/IO/DirectoryInfoExtensionsTests.cs
@@ -0,0 +1,269 @@
+using DNX.Extensions.IO;
+using DNX.Extensions.Strings;
+using FluentAssertions;
+using Xunit;
+using Xunit.Abstractions;
+
+// ReSharper disable StringLiteralTypo
+
+namespace DNX.Extensions.Tests.IO
+{
+ public class DirectoryInfoExtensionsTests : IDisposable
+ {
+ private readonly DirectoryInfo _directoryInfo;
+ private readonly ITestOutputHelper _outputHelper;
+
+ public DirectoryInfoExtensionsTests(ITestOutputHelper outputHelper)
+ {
+ _outputHelper = outputHelper;
+
+ var directoryPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+ _directoryInfo = new DirectoryInfo(directoryPath);
+ _directoryInfo.Create();
+
+ SetupStandardFileStructure(_directoryInfo);
+ }
+
+ public void Dispose()
+ {
+ _directoryInfo.Delete(true);
+ GC.SuppressFinalize(this);
+ }
+
+ internal static void CreateFile(DirectoryInfo directoryInfo, string fileName)
+ {
+ var filePath = Path.Combine(directoryInfo.FullName, fileName);
+
+ File.WriteAllText(filePath, string.Empty);
+ }
+
+ internal static void SetupStandardFileStructure(DirectoryInfo directoryInfo)
+ {
+ var dir1 = directoryInfo.CreateSubdirectory("dir1");
+ var dir2 = directoryInfo.CreateSubdirectory("dir2");
+ var dir3 = dir1.CreateSubdirectory("dur3");
+ var dir4 = dir2.CreateSubdirectory("dur4");
+
+ CreateFile(directoryInfo, "file.txt");
+ CreateFile(directoryInfo, "file.json");
+ CreateFile(dir1, "file1.txt");
+ CreateFile(dir2, "file2.txt");
+ CreateFile(dir1, "file1.json");
+ CreateFile(dir2, "file2.json");
+ CreateFile(dir3, "file1.tf");
+ CreateFile(dir4, "file2.tf");
+ }
+
+ [Fact]
+ public void FindFiles_for_directory_that_does_not_exist_finds_expected_files()
+ {
+ // Arrange
+ const string pattern = "*.txt";
+ var directoryInfo = new DirectoryInfo(Path.Join(Path.GetTempPath(), Guid.NewGuid().ToString()));
+
+ // Act
+ var result = directoryInfo.FindFiles(pattern, false);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(0);
+ }
+
+ [Fact]
+ public void FindFiles_for_single_pattern_without_recursion_finds_expected_files()
+ {
+ // Arrange
+ const string pattern = "*.txt";
+
+ // Act
+ var result = _directoryInfo.FindFiles(pattern, false);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(1);
+ result.Count(x => x.Name == "file.txt").Should().Be(1);
+ }
+
+ [Fact]
+ public void FindFiles_for_single_pattern_with_recursion_finds_expected_files()
+ {
+ // Arrange
+ const string pattern = "*.txt";
+
+ // Act
+ var result = _directoryInfo.FindFiles(pattern, true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(3);
+ result.Count(x => x.Name == "file.txt").Should().Be(1);
+ result.Count(x => x.Name == "file1.txt").Should().Be(1);
+ result.Count(x => x.Name == "file2.txt").Should().Be(1);
+ }
+
+ [Fact]
+ public void FindFiles_for_multiple_patterns_without_recursion_finds_expected_files()
+ {
+ // Arrange
+ var patterns = new[] { "*.txt", "*.json" };
+ SetupStandardFileStructure(_directoryInfo);
+
+ // Act
+ var result = _directoryInfo.FindFiles(patterns, false);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(2);
+ result.Count(x => x.Name == "file.txt").Should().Be(1);
+ result.Count(x => x.Name == "file.json").Should().Be(1);
+ }
+
+ [Fact]
+ public void FindFiles_for_multiple_patterns_with_recursion_finds_expected_files()
+ {
+ // Arrange
+ var patterns = new[] { "*.txt", "*.json" };
+ SetupStandardFileStructure(_directoryInfo);
+
+ // Act
+ var result = _directoryInfo.FindFiles(patterns, true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(6);
+ result.Count(x => x.Name == "file.txt").Should().Be(1);
+ result.Count(x => x.Name == "file1.txt").Should().Be(1);
+ result.Count(x => x.Name == "file2.txt").Should().Be(1);
+ result.Count(x => x.Name == "file.json").Should().Be(1);
+ result.Count(x => x.Name == "file1.json").Should().Be(1);
+ result.Count(x => x.Name == "file2.json").Should().Be(1);
+ }
+
+ [Fact]
+ public void FindDirectories_for_directory_that_does_not_exist_finds_expected_files()
+ {
+ // Arrange
+ const string pattern = "dir*";
+ var directoryInfo = new DirectoryInfo(Path.Join(Path.GetTempPath(), Guid.NewGuid().ToString()));
+
+ // Act
+ var result = directoryInfo.FindDirectories(pattern, false);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(0);
+ }
+
+ [Fact]
+ public void FindDirectories_for_single_pattern_without_recursion_finds_expected_files()
+ {
+ // Arrange
+ const string pattern = "dir*";
+
+ // Act
+ var result = _directoryInfo.FindDirectories(pattern, false);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(2);
+ result.Count(x => x.Name == "dir1").Should().Be(1);
+ result.Count(x => x.Name == "dir2").Should().Be(1);
+ }
+
+ [Fact]
+ public void FindDirectories_for_single_pattern_with_recursion_finds_expected_files()
+ {
+ // Arrange
+ const string pattern = "d?r*";
+
+ // Act
+ var result = _directoryInfo.FindDirectories(pattern, true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(4);
+ result.Count(x => x.Name == "dir1").Should().Be(1);
+ result.Count(x => x.Name == "dir2").Should().Be(1);
+ result.Count(x => x.Name == "dur3").Should().Be(1);
+ result.Count(x => x.Name == "dur4").Should().Be(1);
+ }
+
+ [Fact]
+ public void FindDirectories_for_multiple_patterns_without_recursion_finds_expected_files()
+ {
+ // Arrange
+ var patterns = new[] { "dir*", "dur*" };
+
+ // Act
+ var result = _directoryInfo.FindDirectories(patterns, false);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(2);
+ result.Count(x => x.Name == "dir1").Should().Be(1);
+ result.Count(x => x.Name == "dir2").Should().Be(1);
+ }
+
+ [Fact]
+ public void FindDirectories_for_multiple_patterns_with_recursion_finds_expected_files()
+ {
+ // Arrange
+ var patterns = new[] { "dir*", "dur*" };
+
+ // Act
+ var result = _directoryInfo.FindDirectories(patterns, true);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count().Should().Be(4);
+ result.Count(x => x.Name == "dir1").Should().Be(1);
+ result.Count(x => x.Name == "dir2").Should().Be(1);
+ result.Count(x => x.Name == "dur3").Should().Be(1);
+ result.Count(x => x.Name == "dur4").Should().Be(1);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetRelativePath_Data))]
+ public void GetRelativePath_can_extract_relative_path_correctly(string dirName, string relativeToDirName, string expected)
+ {
+ _outputHelper.WriteLine($"{Environment.OSVersion.Platform} - Checking DirName: {dirName} -> {relativeToDirName} = {expected}");
+
+ var dirInfo = dirName == null ? null : new DirectoryInfo(dirName);
+ var relativeToDirInfo = relativeToDirName == null ? null : new DirectoryInfo(relativeToDirName);
+
+ var result = dirInfo.GetRelativePath(relativeToDirInfo);
+
+ result.Should().Be(expected, $"{nameof(dirName)}: {dirName} - {nameof(relativeToDirInfo)}: {relativeToDirInfo}");
+ }
+
+ public static TheoryData GetRelativePath_Data()
+ {
+ var guid1 = Guid.NewGuid().ToString();
+ var guid2 = Guid.NewGuid().ToString();
+ var guid3 = Guid.NewGuid().ToString();
+
+ var data = new TheoryData()
+ {
+ { Path.Combine(Path.GetTempPath(), guid1), null, null },
+ { null, Path.Combine(Path.GetTempPath(), guid1), null },
+ { Path.Combine(Path.GetTempPath(), guid1), Path.Combine(Path.GetTempPath(), "abcdefg"), Path.Join("..", guid1) },
+ { Path.Combine(Path.GetTempPath(), guid2), Path.Combine(Path.GetTempPath(), guid3), Path.Join("..", guid2) },
+ { Path.Combine(Path.GetTempPath(), "abcdefg"), Path.Combine(Path.GetTempPath(), "abcdefg"), "" },
+ { Path.Combine(Path.GetTempPath(), "abcdefg", "dir3"), Path.Combine(Path.GetTempPath(), "abcdefg"), "dir3" },
+ { Path.Combine(Path.GetTempPath(), "abcdefg", "dir3"), Path.Combine(Path.GetTempPath(), "abcdefg", "dir3"), "" },
+ { Path.Combine(Path.GetTempPath(), "folder1"), Path.Combine(Path.GetTempPath(), "folder2"), Path.Combine("..", "folder1") },
+ };
+
+ if (Configuration.EnvironmentConfig.IsWindowsStyleFileSystem)
+ {
+ data.Add(Path.Combine(Path.GetTempPath(), "folder1"), Path.Combine("D:", "folder2"), Path.Combine(Path.GetTempPath(), "folder1"));
+ }
+ if (Configuration.EnvironmentConfig.IsLinuxStyleFileSystem)
+ {
+ data.Add(Path.Combine(Path.GetTempPath(), "folder1"), Path.Combine("/etc", "folder2"), Path.Combine(Path.GetTempPath(), "folder1").EnsureStartsWith(Path.Combine("..", "..")));
+ }
+
+ return data;
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/IO/FileInfoExtensionsTests.cs b/tests/DNX.Extensions.Tests/IO/FileInfoExtensionsTests.cs
new file mode 100644
index 0000000..c0a2aa0
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/IO/FileInfoExtensionsTests.cs
@@ -0,0 +1,157 @@
+using DNX.Extensions.IO;
+using DNX.Extensions.Strings;
+using FluentAssertions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace DNX.Extensions.Tests.IO
+{
+ public class FileInfoExtensionsTests(ITestOutputHelper outputHelper)
+ {
+ private static string DriveRoot1
+ {
+ get
+ {
+ return Configuration.EnvironmentConfig.IsLinuxStyleFileSystem
+ ? "/root1"
+ : "C:";
+ }
+ }
+
+ private static string DriveRoot2
+ {
+ get
+ {
+ return Configuration.EnvironmentConfig.IsLinuxStyleFileSystem
+ ? "/root2"
+ : "D:";
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetRelativeFileName_Data))]
+ public void GetRelativeFileName_can_extract_relative_filename_correctly(string fileName, string dirName, string expected)
+ {
+ outputHelper.WriteLine($"{Environment.OSVersion.Platform} - Checking FileName: {fileName} -> {dirName} = {expected}");
+
+ var fileInfo = new FileInfo(fileName);
+ var dirInfo = new DirectoryInfo(dirName);
+
+ var result = fileInfo.GetRelativeFileName(dirInfo);
+
+ result.Should().Be(expected, $"{nameof(dirName)}: {dirName} - {nameof(fileName)}: {fileName}");
+ }
+
+ [Theory]
+ [MemberData(nameof(GetRelativeFilePath_Data))]
+ public void GetRelativeFilePath_can_extract_relative_path_correctly(string fileName, string dirName, string expected)
+ {
+ outputHelper.WriteLine($"{Environment.OSVersion.Platform} - Checking FileName: {fileName} -> {dirName} = {expected}");
+
+ var fileInfo = new FileInfo(fileName);
+ var dirInfo = new DirectoryInfo(dirName);
+
+ var result = fileInfo.GetRelativeFilePath(dirInfo);
+
+ result.Should().Be(expected, $"{nameof(dirName)}: {dirName} - {nameof(fileName)}: {fileName}");
+ }
+
+ [Theory]
+ [MemberData(nameof(FileSizeData))]
+ public void GetFriendlyFileSize_given_a_fileSize_should_return_expected_text(long fileSize, string expected)
+ {
+ var result = FileInfoExtensions.GetFriendlyFileSize(fileSize);
+
+ result.Should().Be(expected);
+ }
+
+ [Fact]
+ public void GetFriendlyFileSize_given_an_invalid_FileInfo_should_return_expected_text()
+ {
+ // Arrange
+ var fileInfo = (FileInfo)null;
+
+ // Act
+ var result = FileInfoExtensions.GetFriendlyFileSize(fileInfo);
+
+ // Assert
+ result.Should().Be("0B");
+ }
+
+ [Theory]
+ [MemberData(nameof(FileSizeData))]
+ public void GetFriendlyFileSize_given_a_valid_FileInfo_should_return_expected_text(long fileSize, string expected)
+ {
+ var fileName = Path.GetTempFileName();
+ var fileInfo = new FileInfo(fileName);
+
+ var data = new byte[fileSize];
+
+ File.WriteAllBytes(fileInfo.FullName, data);
+
+ // Act
+ var result = FileInfoExtensions.GetFriendlyFileSize(fileInfo);
+
+ // Assert
+ result.Should().Be(expected);
+
+ // Cleanup
+ fileInfo.Delete();
+ }
+
+ public static TheoryData FileSizeData()
+ {
+ return new TheoryData
+ {
+ { 0, "0B" },
+ { 1000, "1000B" },
+ { 1023, "1023B" },
+ { 1024, "1KB" },
+ { 1536, "1.5KB" },
+ { 1792, "1.8KB" },
+ { 2048, "2KB" },
+ { 10240, "10KB" },
+ { 102400, "100KB" },
+ { 1024000, "1000KB" },
+ { 1048500, "1023.9KB" },
+ { 1048575, "1024KB" },
+ { 1048576, "1MB" },
+ { 2097152, "2MB" },
+ { 10485760, "10MB" },
+ };
+ }
+
+ public static TheoryData GetRelativeFileName_Data()
+ {
+ return new TheoryData
+ {
+ { Path.Combine(DriveRoot1, "Temp", "abcdefg", "file.txt"), Path.Combine(DriveRoot1, "Temp", "abcdefg"), "file.txt" },
+ { Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3", "file1.tf"), Path.Combine(DriveRoot1, "Temp", "abcdefg"), Path.Combine("dir3", "file1.tf") },
+ { Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3", "file1.tf"), Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3"), "file1.tf" },
+ { Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3", "file1.tf"), Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3"), "file1.tf" },
+ { Path.Combine(DriveRoot1, "Temp", "folder1", "file.txt"), Path.Combine(DriveRoot2, "folder2"),
+ Configuration.EnvironmentConfig.IsLinuxStyleFileSystem
+ ? Path.Combine(DriveRoot1, "Temp", "folder1", "file.txt").EnsureStartsWith(Path.Combine("..", ".."))
+ : Path.Combine(DriveRoot1, "Temp", "folder1", "file.txt")
+ },
+ { Path.Combine(DriveRoot1, "Temp", "folder1", "file.txt"), Path.Combine(DriveRoot1, "Temp", "folder2"), Path.Combine("..", "folder1", "file.txt") },
+ };
+ }
+
+ public static TheoryData GetRelativeFilePath_Data()
+ {
+ return new TheoryData
+ {
+ { Path.Combine(DriveRoot1, "Temp", "abcdefg", "file.txt"), Path.Combine(DriveRoot1, "Temp", "abcdefg"), "" },
+ { Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3", "file1.tf"), Path.Combine(DriveRoot1, "Temp", "abcdefg"), "dir3" },
+ { Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3", "file1.tf"), Path.Combine(DriveRoot1, "Temp", "abcdefg", "dir3"), "" },
+ { Path.Combine(DriveRoot1, "Temp", "folder1", "file.txt"), Path.Combine(DriveRoot1, "Temp", "folder2"), Path.Combine("..", "folder1") },
+ { Path.Combine(DriveRoot1, "Temp", "folder1", "file.txt"), Path.Combine(DriveRoot2, "folder2"),
+ Configuration.EnvironmentConfig.IsLinuxStyleFileSystem
+ ? Path.Combine(DriveRoot1, "Temp", "folder1").EnsureStartsWith(Path.Combine("..", ".."))
+ : Path.Combine(DriveRoot1, "Temp", "folder1")
+ },
+ };
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Linq/EnumerableExtensionsTests.cs b/tests/DNX.Extensions.Tests/Linq/EnumerableExtensionsTests.cs
new file mode 100644
index 0000000..2a172df
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Linq/EnumerableExtensionsTests.cs
@@ -0,0 +1,437 @@
+using System.Collections;
+using System.Collections.Generic;
+using DNX.Extensions.Linq;
+using FluentAssertions;
+using Xunit;
+using Xunit.Abstractions;
+
+// ReSharper disable InconsistentNaming
+
+namespace DNX.Extensions.Tests.Linq
+{
+ internal enum OneToFive
+ {
+ One,
+ Two,
+ Three,
+ Four,
+ Five
+ }
+
+ public class EnumerableExtensionsTests
+ {
+ public class HasAny
+ {
+ [Theory]
+ [InlineData("1,2,3,4,5")]
+ [InlineData("Dave,Bob,Steve")]
+ public void Populated_Enumerable_returns_successfully_as_True(string itemList)
+ {
+ // Arrange
+ var enumerable = itemList.Split(",");
+
+ // Act
+ var result = enumerable.HasAny();
+
+ // Assert
+ result.Should().BeTrue();
+ }
+
+ [Fact]
+ public void Empty_Enumerable_returns_successfully_as_False()
+ {
+ // Arrange
+ var enumerable = new List();
+
+ // Act
+ var result = enumerable.HasAny();
+
+ // Assert
+ result.Should().BeFalse();
+ }
+
+ [Fact]
+ public void Null_Enumerable_returns_successfully_as_False()
+ {
+ // Arrange
+ List enumerable = null;
+
+ // Act
+ var result = enumerable.HasAny();
+
+ // Assert
+ result.Should().BeFalse();
+ }
+ }
+
+ public class HasAny_Predicate
+ {
+ [Theory]
+ [InlineData("", "1", false)]
+ [InlineData(null, "1", false)]
+ [InlineData("a1,b2,c1,d2,e1,f2,g1,h2,i1,j2", "1", true)]
+ [InlineData("a1,b2,c1,d2,e1,f2,g1,h2,i1,j2", "2", true)]
+ [InlineData("a1,b2,c1,d2,e1,f2,g1,h2,i1,j2", "0", false)]
+ public void Test_HasAny_predicate(string commaDelimitedArray, string suffix, bool expectedResult)
+ {
+ var enumerable = commaDelimitedArray?
+ .Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
+ ;
+
+ var result = enumerable.HasAny(s => s.EndsWith(suffix));
+
+ result.Should().Be(expectedResult);
+ }
+ }
+
+ public class IsOneOf_Tests
+ {
+ [Theory]
+ [MemberData(nameof(IsOneOf_string_Data))]
+ public void IsOneOf_with_string_data_can_operate_as_expected(string[] list, string candidate, bool expectedResult)
+ {
+ var result = candidate.IsOneOf(list);
+
+ result.Should().Be(expectedResult);
+ }
+
+ [Theory]
+ [MemberData(nameof(IsOneOf_string_comparer_Data))]
+ public void IsOneOf_with_string_data_and_comparer_can_operate_as_expected(string[] list, string candidate, IEqualityComparer comparer, bool expectedResult)
+ {
+ var result = candidate.IsOneOf(list, comparer);
+
+ result.Should().Be(expectedResult);
+ }
+
+ [Fact]
+ public void IsOneOf_with_params_can_operate_as_expected()
+ {
+ ((string)null).IsOneOf().Should().Be(false);
+ ((string)null).IsOneOf((string[])null).Should().Be(false);
+ "Hello".IsOneOf((string[])null).Should().BeFalse();
+ "Hello".IsOneOf().Should().Be(false);
+ "3".IsOneOf("1", "2", "3", "4", "5").Should().Be(true);
+ "6".IsOneOf("1", "2", "3", "4", "5").Should().Be(false);
+ "One".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Two".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Three".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Four".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Five".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "five".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(false);
+ "FIVE".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(false);
+ "Six".IsOneOf("One", "Two", "Three", "Four", "Five").Should().Be(false);
+ 1.IsOneOf(1, 2, 3, 4, 5).Should().Be(true);
+ 3.IsOneOf(1, 2, 3, 4, 5).Should().Be(true);
+ 5.IsOneOf(1, 2, 3, 4, 5).Should().Be(true);
+ 6.IsOneOf(1, 2, 3, 4, 5).Should().Be(false);
+ OneToFive.One.IsOneOf(OneToFive.One, OneToFive.Three, OneToFive.Five).Should().Be(true);
+ OneToFive.Three.IsOneOf(OneToFive.One, OneToFive.Three, OneToFive.Five).Should().Be(true);
+ OneToFive.Five.IsOneOf(OneToFive.One, OneToFive.Three, OneToFive.Five).Should().Be(true);
+ OneToFive.Two.IsOneOf(OneToFive.One, OneToFive.Three, OneToFive.Five).Should().Be(false);
+ ((OneToFive)100).IsOneOf(OneToFive.One, OneToFive.Three, OneToFive.Five).Should().Be(false);
+ }
+
+ [Fact]
+ public void IsOneOf_with_params_and_Comparer_can_operate_as_expected()
+ {
+ var comparer = StringComparer.FromComparison(StringComparison.CurrentCulture);
+ ((string)null).IsOneOf(comparer).Should().Be(false);
+ ((string)null).IsOneOf(comparer, null).Should().Be(false);
+ "Hello".IsOneOf((string[])null).Should().BeFalse();
+ "Hello".IsOneOf().Should().Be(false);
+ "Hello".IsOneOf(comparer).Should().Be(false);
+ "3".IsOneOf(comparer, "1", "2", "3", "4", "5").Should().Be(true);
+ "6".IsOneOf(comparer, "1", "2", "3", "4", "5").Should().Be(false);
+ "One".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Two".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Three".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Four".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Five".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "five".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(false);
+ "FIVE".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(false);
+ "Six".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(false);
+
+ comparer = StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase);
+ "Hello".IsOneOf(comparer).Should().Be(false);
+ "3".IsOneOf(comparer, "1", "2", "3", "4", "5").Should().Be(true);
+ "6".IsOneOf(comparer, "1", "2", "3", "4", "5").Should().Be(false);
+ "One".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Two".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Three".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Four".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Five".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "five".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "FIVE".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(true);
+ "Six".IsOneOf(comparer, "One", "Two", "Three", "Four", "Five").Should().Be(false);
+ }
+
+ #region Test Data
+
+ public static TheoryData IsOneOf_string_Data()
+ {
+ return new TheoryData
+ {
+ { null, "3", false },
+ { Enumerable.Empty().ToArray(), "3", false },
+ { "1,2,3,4,5".Split(','), "3", true },
+ { "1,2,3,4,5".Split(','), "6", false },
+ { "One,Two,Three,Four,Five".Split(','), "One", true },
+ { "One,Two,Three,Four,Five".Split(','), "Two", true },
+ { "One,Two,Three,Four,Five".Split(','), "Three", true },
+ { "One,Two,Three,Four,Five".Split(','), "Four", true },
+ { "One,Two,Three,Four,Five".Split(','), "Five", true },
+ { "One,Two,Three,Four,Five".Split(','), "five", false },
+ { "One,Two,Three,Four,Five".Split(','), "Six", false },
+ };
+ }
+
+ public static TheoryData, bool> IsOneOf_string_comparer_Data()
+ {
+ return new TheoryData, bool>
+ {
+ { "1,2,3,4,5".Split(','), "3", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "1,2,3,4,5".Split(','), "6", StringComparer.FromComparison(StringComparison.CurrentCulture), false },
+ { "1,2,3,4,5".Split(','), "3", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "1,2,3,4,5".Split(','), "6", StringComparer.FromComparison(StringComparison.CurrentCulture), false },
+
+ { "One,Two,Three,Four,Five".Split(','), "One", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Two", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Three", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Four", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Five", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "five", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Six", StringComparer.FromComparison(StringComparison.CurrentCultureIgnoreCase), false },
+ { "One,Two,Three,Four,Five".Split(','), "One", StringComparer.FromComparison(StringComparison.CurrentCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Two", StringComparer.FromComparison(StringComparison.CurrentCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Three", StringComparer.FromComparison(StringComparison.CurrentCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Four", StringComparer.FromComparison(StringComparison.CurrentCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Five", StringComparer.FromComparison(StringComparison.CurrentCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "five", StringComparer.FromComparison(StringComparison.CurrentCulture), false },
+ { "One,Two,Three,Four,Five".Split(','), "Six", StringComparer.FromComparison(StringComparison.CurrentCulture), false },
+
+ { "One,Two,Three,Four,Five".Split(','), "One", StringComparer.FromComparison(StringComparison.OrdinalIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Two", StringComparer.FromComparison(StringComparison.OrdinalIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Three", StringComparer.FromComparison(StringComparison.OrdinalIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Four", StringComparer.FromComparison(StringComparison.OrdinalIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Five", StringComparer.FromComparison(StringComparison.OrdinalIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "five", StringComparer.FromComparison(StringComparison.OrdinalIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Six", StringComparer.FromComparison(StringComparison.OrdinalIgnoreCase), false },
+ { "One,Two,Three,Four,Five".Split(','), "One", StringComparer.FromComparison(StringComparison.Ordinal), true },
+ { "One,Two,Three,Four,Five".Split(','), "Two", StringComparer.FromComparison(StringComparison.Ordinal), true },
+ { "One,Two,Three,Four,Five".Split(','), "Three", StringComparer.FromComparison(StringComparison.Ordinal), true },
+ { "One,Two,Three,Four,Five".Split(','), "Four", StringComparer.FromComparison(StringComparison.Ordinal), true },
+ { "One,Two,Three,Four,Five".Split(','), "Five", StringComparer.FromComparison(StringComparison.Ordinal), true },
+ { "One,Two,Three,Four,Five".Split(','), "five", StringComparer.FromComparison(StringComparison.Ordinal), false },
+ { "One,Two,Three,Four,Five".Split(','), "Six", StringComparer.FromComparison(StringComparison.Ordinal), false },
+
+ { "One,Two,Three,Four,Five".Split(','), "One", StringComparer.FromComparison(StringComparison.InvariantCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Two", StringComparer.FromComparison(StringComparison.InvariantCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Three", StringComparer.FromComparison(StringComparison.InvariantCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Four", StringComparer.FromComparison(StringComparison.InvariantCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Five", StringComparer.FromComparison(StringComparison.InvariantCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "five", StringComparer.FromComparison(StringComparison.InvariantCultureIgnoreCase), true },
+ { "One,Two,Three,Four,Five".Split(','), "Six", StringComparer.FromComparison(StringComparison.InvariantCultureIgnoreCase), false },
+ { "One,Two,Three,Four,Five".Split(','), "One", StringComparer.FromComparison(StringComparison.InvariantCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Two", StringComparer.FromComparison(StringComparison.InvariantCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Three", StringComparer.FromComparison(StringComparison.InvariantCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Four", StringComparer.FromComparison(StringComparison.InvariantCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "Five", StringComparer.FromComparison(StringComparison.InvariantCulture), true },
+ { "One,Two,Three,Four,Five".Split(','), "five", StringComparer.FromComparison(StringComparison.InvariantCulture), false },
+ { "One,Two,Three,Four,Five".Split(','), "Six", StringComparer.FromComparison(StringComparison.InvariantCulture), false },
+ };
+ }
+
+ #endregion
+ }
+
+ public class GetRandomItem(ITestOutputHelper testOutputHelper)
+ {
+ [Theory]
+ [InlineData("1,2,3,4,5", 10000)]
+ [InlineData("A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z", 10000)]
+ [InlineData("1,2", 100)]
+ [InlineData("1", 100)]
+ public void Populated_Enumerable_repeatedly_returns_an_item_successfully(string itemList, int count)
+ {
+ // Arrange
+ var enumerable = itemList.Split(",", StringSplitOptions.RemoveEmptyEntries);
+
+ var hitCounts = enumerable
+ .ToDictionary(x => x, x => 0);
+
+ Enumerable.Range(1, count)
+ .ToList()
+ .ForEach(x =>
+ {
+ // Act
+ var result = enumerable.GetRandomItem();
+
+ hitCounts[result]++;
+
+ // Assert
+ result.Should().NotBeNullOrEmpty();
+ });
+
+ // Assert
+ foreach (var kvp in hitCounts)
+ {
+ testOutputHelper.WriteLine("HitCount [{0}]: {1}", kvp.Key, kvp.Value);
+ kvp.Value.Should().BeGreaterThan(0);
+ }
+ }
+
+ [Fact]
+ public void Empty_string_Enumerable_returns_default()
+ {
+ // Arrange
+ var enumerable = Enumerable.Empty();
+
+ // Act
+ var result = enumerable.GetRandomItem();
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void Null_string_Enumerable_returns_default()
+ {
+ // Arrange
+ IEnumerable enumerable = null;
+
+ // Act
+ var result = enumerable.GetRandomItem();
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void Empty_int_Enumerable_returns_default()
+ {
+ // Arrange
+ var enumerable = Enumerable.Empty();
+
+ // Act
+ var result = enumerable.GetRandomItem();
+
+ // Assert
+ result.Should().Be(default);
+ }
+
+ [Fact]
+ public void Null_int_Enumerable_returns_default()
+ {
+ // Arrange
+ IEnumerable enumerable = null;
+
+ // Act
+ var result = enumerable.GetRandomItem();
+
+ // Assert
+ result.Should().Be(default);
+ }
+ }
+
+ public class GetAt
+ {
+ [Theory]
+ [InlineData("1,2,3,4,5", 0, "1")]
+ [InlineData("1,2,3,4,5", 2, "3")]
+ [InlineData("1,2,3,4,5", 4, "5")]
+ [InlineData("1,2,3,4,5", -1, null)]
+ [InlineData("1,2,3,4,5", 5, null)]
+ [InlineData("1,2,3,4,5", 6, null)]
+ [InlineData("1,2,3,4,5", 100, null)]
+ [InlineData("1,2,3,4,5", -100, null)]
+ public void Populated_List_repeatedly_returns_an_item_successfully(string itemList, int index, string expected)
+ {
+ // Arrange
+ var items = itemList.Split(",", StringSplitOptions.RemoveEmptyEntries)
+ .ToList();
+
+ // Act
+ var result = items.GetAt(index);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [InlineData("1,2,3,4,5", 0, "1")]
+ [InlineData("1,2,3,4,5", 2, "3")]
+ [InlineData("1,2,3,4,5", 4, "5")]
+ [InlineData("1,2,3,4,5", -1, null)]
+ [InlineData("1,2,3,4,5", 5, null)]
+ [InlineData("1,2,3,4,5", 6, null)]
+ [InlineData("1,2,3,4,5", 100, null)]
+ [InlineData("1,2,3,4,5", -100, null)]
+ public void Populated_Array_repeatedly_returns_an_item_successfully(string itemList, int index, string expected)
+ {
+ // Arrange
+ var items = itemList.Split(",", StringSplitOptions.RemoveEmptyEntries)
+ .ToArray();
+
+ // Act
+ var result = items.GetAt(index);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Fact]
+ public void Empty_IList_returns_default()
+ {
+ // Arrange
+ var items = Enumerable.Empty()
+ .ToList();
+
+ // Act
+ var result = items.GetAt(0);
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void Empty_Array_returns_default()
+ {
+ // Arrange
+ var items = Array.Empty();
+
+ // Act
+ var result = items.GetAt(0);
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void Null_IList_returns_default()
+ {
+ // Arrange
+ List items = null;
+
+ // Act
+ var result = items.GetAt(0);
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void Null_Array_returns_default()
+ {
+ // Arrange
+ string[] items = null;
+
+ // Act
+ var result = items.GetAt(0);
+
+ // Assert
+ result.Should().BeNull();
+ }
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Reflection/ReflectionExtensionsTests.cs b/tests/DNX.Extensions.Tests/Reflection/ReflectionExtensionsTests.cs
new file mode 100644
index 0000000..f728082
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Reflection/ReflectionExtensionsTests.cs
@@ -0,0 +1,136 @@
+using System.Reflection;
+using DNX.Extensions.Reflection;
+using FluentAssertions;
+using Xunit;
+
+#pragma warning disable CA1822 // Members can be static
+#pragma warning disable IDE0051 // Unused private member
+
+// ReSharper disable UnusedMember.Local
+
+namespace DNX.Extensions.Tests.Reflection;
+
+public class TestClass
+{
+ internal string MachineName => Environment.MachineName;
+ internal string UserName => Environment.UserName;
+ private string UserDomainName => Environment.UserDomainName;
+
+ public string PublicSetOnly { private get; set; }
+
+ private static string CurrentDirectory => Environment.CurrentDirectory;
+}
+
+public class ReflectionExtensionsTests
+{
+ public static TheoryData GetPrivatePropertyValue_Private_Data()
+ {
+ return new TheoryData
+ {
+ { "UserDomainName", Environment.UserDomainName },
+ };
+ }
+
+ public static TheoryData GetPrivatePropertyValue_Public_Data()
+ {
+ return new TheoryData
+ {
+ { nameof(TestClass.MachineName), Environment.MachineName },
+ { nameof(TestClass.UserName), Environment.UserName },
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(GetPrivatePropertyValue_Private_Data))]
+ public void GetPrivatePropertyValue_can_read_private_values_successfully(string propertyName, string expected)
+ {
+ // Arrange
+ var instance = new TestClass();
+
+ // Act
+ var result = instance.GetPrivatePropertyValue(propertyName);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetPrivatePropertyValue_Public_Data))]
+ public void GetPrivatePropertyValue_can_read_non_private_values_successfully(string propertyName, string expected)
+ {
+ // Arrange
+ var instance = new TestClass();
+
+ // Act
+ var result = instance.GetPrivatePropertyValue(propertyName);
+
+ // Assert
+ result.Should().Be(expected);
+ }
+
+ [Fact]
+ public void GetPropertyValueByName_can_read_private_static_values_successfully()
+ {
+ // Arrange
+ var instance = new TestClass();
+
+ // Act
+ var result = instance.GetPropertyValueByName("CurrentDirectory", BindingFlags.Static | BindingFlags.NonPublic);
+
+ // Assert
+ result.Should().Be(Environment.CurrentDirectory);
+ }
+
+ [Fact]
+ public void GetPropertyValueByName_for_unknown_property_name_returns_null()
+ {
+ // Arrange
+ var instance = new TestClass();
+
+ // Act
+ var result = instance.GetPropertyValueByName(Guid.NewGuid().ToString(), BindingFlags.Static | BindingFlags.NonPublic);
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void GetPropertyValueByName_for_property_name_without_getter_returns_null()
+ {
+ // Arrange
+ var instance = new TestClass();
+
+ // Act
+ var result = instance.GetPropertyValueByName(nameof(TestClass.PublicSetOnly), BindingFlags.Instance | BindingFlags.Public);
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void GetPrivatePropertyValue_for_unknown_property_name_returns_null()
+ {
+ // Arrange
+ var instance = new TestClass();
+
+ // Act
+ var result = instance.GetPrivatePropertyValue(Guid.NewGuid().ToString());
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public void GetPrivatePropertyValue_for_unknown_property_name_returns_default_value()
+ {
+ // Arrange
+ var instance = new TestClass();
+ var defaultValue = Guid.NewGuid().ToString();
+
+ // Act
+ var result = instance.GetPrivatePropertyValue(Guid.NewGuid().ToString(), defaultValue);
+
+ // Assert
+ result.Should().Be(defaultValue);
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/Strings/ArgumentParserExtensionsTests.cs b/tests/DNX.Extensions.Tests/Strings/ArgumentParserExtensionsTests.cs
new file mode 100644
index 0000000..80da711
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/Strings/ArgumentParserExtensionsTests.cs
@@ -0,0 +1,36 @@
+using DNX.Extensions.Strings;
+using FluentAssertions;
+using Xunit;
+
+namespace DNX.Extensions.Tests.Strings;
+
+public class ArgumentParserExtensionsTests
+{
+ [Theory]
+ [InlineData("command value1 value2 --option optionValue", 5, "command|value1|value2|--option|optionValue")]
+ [InlineData("command value1 \"value2\" --option 'optionValue'", 5, "command|value1|value2|--option|'optionValue'")]
+ [InlineData("command \" value1 has multiple spaces \" \"value2 contains spaces\" --option 'optionValue'", 5, "command| value1 has multiple spaces |value2 contains spaces|--option|'optionValue'")]
+ [InlineData("--swiftcon \"Server=.\\SQLEXPRESS;Database=Swift;Trusted_Connection=True;ConnectRetryCount=6;ConnectRetryInterval=10;Connection Timeout=30;\" --swiftsoaphaaddress \"https://127.0.0.1:48200/soapha/\" --swiftencryptedlau mylauwhichshouldbeencrypted --institutionadclientid \"ab988c21-f419-4488-b3d6-a7ffeea63e68\" --institutionadclientsecret \"No8pQsZjBSIGbGMM6KCHf24qPvZ+YnvJKt0cTeQar0g=\" --institutionadtenantname \"cbiuktestinstitution.onmicrosoft.com\" --institutionprincipalcon \"Server=.\\SQLEXPRESS;Database=BankingInstitutionAuthentication;Trusted_Connection=True;ConnectRetryCount=6;ConnectRetryInterval=10;Connection Timeout=30;\" --institutionprincipalids \"AE261E74-4BDF-470C-9FFD-0227804DD8B9\" \"10246AE7-ED49-41C4-AF25-023521FF3622\"", 17, "--swiftcon|Server=.\\SQLEXPRESS;Database=Swift;Trusted_Connection=True;ConnectRetryCount=6;ConnectRetryInterval=10;Connection Timeout=30;|--swiftsoaphaaddress|https://127.0.0.1:48200/soapha/|--swiftencryptedlau|mylauwhichshouldbeencrypted|--institutionadclientid|ab988c21-f419-4488-b3d6-a7ffeea63e68|--institutionadclientsecret|No8pQsZjBSIGbGMM6KCHf24qPvZ+YnvJKt0cTeQar0g=|--institutionadtenantname|cbiuktestinstitution.onmicrosoft.com|--institutionprincipalcon|Server=.\\SQLEXPRESS;Database=BankingInstitutionAuthentication;Trusted_Connection=True;ConnectRetryCount=6;ConnectRetryInterval=10;Connection Timeout=30;|--institutionprincipalids|AE261E74-4BDF-470C-9FFD-0227804DD8B9|10246AE7-ED49-41C4-AF25-023521FF3622")]
+ [InlineData("Endpoint=sb://#{service_bus_url-prefix}#.servicebus.windows.net/;SharedAccessKeyName=#{servicebus_sas_applications_name}#;SharedAccessKey=#{servicebus_sas_applications_key}#", 1, "Endpoint=sb://#{service_bus_url-prefix}#.servicebus.windows.net/;SharedAccessKeyName=#{servicebus_sas_applications_name}#;SharedAccessKey=#{servicebus_sas_applications_key}#")]
+ [InlineData("\"Server=.;Database=JPMEmulator;Integrated Security=True;MultipleActiveResultSets=True\"", 1, "Server=.;Database=JPMEmulator;Integrated Security=True;MultipleActiveResultSets=True")]
+ public void When_called_with_a_valid_simple_string_of_values(string text, int parameterCount, string resultsByPipe)
+ {
+ // Act
+ var result = text.ParseArguments();
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count.Should().Be(parameterCount);
+
+ var parameters = resultsByPipe.Split("|".ToCharArray());
+ parameters.Length.Should().Be(parameterCount);
+
+ var parameterPosition = 0;
+ foreach (var parameter in parameters)
+ {
+ result[parameterPosition].Should().Be(parameter);
+
+ ++parameterPosition;
+ }
+ }
+}
diff --git a/tests/DNX.Extensions.Tests/TestData/SampleData.json b/tests/DNX.Extensions.Tests/TestData/SampleData.json
new file mode 100644
index 0000000..c592d02
--- /dev/null
+++ b/tests/DNX.Extensions.Tests/TestData/SampleData.json
@@ -0,0 +1,4 @@
+{
+ "Id": 12345,
+ "Name": "Dave Dangerous"
+}