From 0c5075781cc0c515364facf722c1abc01f7ed6c1 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Sat, 3 May 2025 21:27:00 +0200
Subject: [PATCH 1/6] Init
---
.../Commands.Tests.Console/Program.cs | 2 +-
src/Commands/Commands.csproj | 5 +
.../Abstractions/IExecutionProvider.cs | 2 +-
.../Abstractions/ITest.cs} | 2 +-
.../Abstractions/ITestCollection.cs | 22 +++
.../Components/Abstractions/ITestProvider.cs | 17 +++
.../Abstractions/TestResultType.cs | 0
.../Attributes}/TestAttribute.cs | 2 +-
src/Commands/Testing/Components/Test.cs | 30 ++++
.../Testing/Components/TestCollection.cs | 98 +++++++++++++
src/Commands/Testing/Components/TestGroup.cs | 109 ++++++++++++++
.../Testing/Components/TestProperties.cs | 51 +++++++
.../Testing/Execution/TestCollection.cs | 138 ------------------
.../Execution/TestCollectionProperties.cs | 94 ------------
.../Testing/{ => Results}/TestResult.cs | 0
src/Commands/Testing/TestProvider.cs | 37 -----
.../Testing/TestProviderProperties.cs | 69 ---------
src/Commands/Testing/TestUtilities.cs | 3 +-
18 files changed, 338 insertions(+), 343 deletions(-)
rename src/Commands/Testing/{Abstractions/ITestProvider.cs => Components/Abstractions/ITest.cs} (95%)
create mode 100644 src/Commands/Testing/Components/Abstractions/ITestCollection.cs
create mode 100644 src/Commands/Testing/Components/Abstractions/ITestProvider.cs
rename src/Commands/Testing/{ => Components}/Abstractions/TestResultType.cs (100%)
rename src/Commands/Testing/{ => Components/Attributes}/TestAttribute.cs (86%)
create mode 100644 src/Commands/Testing/Components/Test.cs
create mode 100644 src/Commands/Testing/Components/TestCollection.cs
create mode 100644 src/Commands/Testing/Components/TestGroup.cs
create mode 100644 src/Commands/Testing/Components/TestProperties.cs
delete mode 100644 src/Commands/Testing/Execution/TestCollection.cs
delete mode 100644 src/Commands/Testing/Execution/TestCollectionProperties.cs
rename src/Commands/Testing/{ => Results}/TestResult.cs (100%)
delete mode 100644 src/Commands/Testing/TestProvider.cs
delete mode 100644 src/Commands/Testing/TestProviderProperties.cs
diff --git a/src/Commands.Tests/Commands.Tests.Console/Program.cs b/src/Commands.Tests/Commands.Tests.Console/Program.cs
index 0c79554f..16698421 100644
--- a/src/Commands.Tests/Commands.Tests.Console/Program.cs
+++ b/src/Commands.Tests/Commands.Tests.Console/Program.cs
@@ -13,7 +13,7 @@
}, "help"))
.ToCollection();
-var tests = TestCollection.From([.. components.GetCommands()])
+var tests = TestCollection.From(components.GetCommands())
.ToCollection();
var results = await tests.Execute((str) => new TestContext(str));
diff --git a/src/Commands/Commands.csproj b/src/Commands/Commands.csproj
index e2ef75bf..e74312eb 100644
--- a/src/Commands/Commands.csproj
+++ b/src/Commands/Commands.csproj
@@ -52,5 +52,10 @@
\
+
+
+
+
+
diff --git a/src/Commands/Core/Components/Abstractions/IExecutionProvider.cs b/src/Commands/Core/Components/Abstractions/IExecutionProvider.cs
index d802ee99..0d1ba7e8 100644
--- a/src/Commands/Core/Components/Abstractions/IExecutionProvider.cs
+++ b/src/Commands/Core/Components/Abstractions/IExecutionProvider.cs
@@ -1,7 +1,7 @@
namespace Commands;
///
-/// Defines mechanisms for executing commands based on a set of arguments.
+/// Defines a mechanism for executing commands based on a set of arguments.
///
public interface IExecutionProvider : IComponentCollection
{
diff --git a/src/Commands/Testing/Abstractions/ITestProvider.cs b/src/Commands/Testing/Components/Abstractions/ITest.cs
similarity index 95%
rename from src/Commands/Testing/Abstractions/ITestProvider.cs
rename to src/Commands/Testing/Components/Abstractions/ITest.cs
index 80f51059..d7e19b16 100644
--- a/src/Commands/Testing/Abstractions/ITestProvider.cs
+++ b/src/Commands/Testing/Components/Abstractions/ITest.cs
@@ -3,7 +3,7 @@
///
/// Represents a test provider that can be used to test a command.
///
-public interface ITestProvider
+public interface ITest
{
///
/// Gets or sets the result that the test should return. If the test does not return this result, it will be considered a failure.
diff --git a/src/Commands/Testing/Components/Abstractions/ITestCollection.cs b/src/Commands/Testing/Components/Abstractions/ITestCollection.cs
new file mode 100644
index 00000000..1952f791
--- /dev/null
+++ b/src/Commands/Testing/Components/Abstractions/ITestCollection.cs
@@ -0,0 +1,22 @@
+namespace Commands.Testing;
+
+///
+///
+///
+///
+public interface ITestCollection : ICollection, IEnumerable
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ //public int AddRange(IEnumerable items);
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ //public int RemoveRange(IEnumerable items);
+}
diff --git a/src/Commands/Testing/Components/Abstractions/ITestProvider.cs b/src/Commands/Testing/Components/Abstractions/ITestProvider.cs
new file mode 100644
index 00000000..314d87fb
--- /dev/null
+++ b/src/Commands/Testing/Components/Abstractions/ITestProvider.cs
@@ -0,0 +1,17 @@
+namespace Commands.Testing;
+
+///
+///
+///
+public interface ITestProvider : ITestCollection
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ValueTask> Execute(Func callerCreation, CommandOptions? options = null)
+ where TContext : class, ICallerContext;
+}
diff --git a/src/Commands/Testing/Abstractions/TestResultType.cs b/src/Commands/Testing/Components/Abstractions/TestResultType.cs
similarity index 100%
rename from src/Commands/Testing/Abstractions/TestResultType.cs
rename to src/Commands/Testing/Components/Abstractions/TestResultType.cs
diff --git a/src/Commands/Testing/TestAttribute.cs b/src/Commands/Testing/Components/Attributes/TestAttribute.cs
similarity index 86%
rename from src/Commands/Testing/TestAttribute.cs
rename to src/Commands/Testing/Components/Attributes/TestAttribute.cs
index bd454377..d2d04785 100644
--- a/src/Commands/Testing/TestAttribute.cs
+++ b/src/Commands/Testing/Components/Attributes/TestAttribute.cs
@@ -4,7 +4,7 @@
/// An attribute that is used to define a test for a command.
///
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
-public sealed class TestAttribute : Attribute, ITestProvider
+public sealed class TestAttribute : Attribute, ITest
{
///
public TestResultType ShouldEvaluateTo { get; set; } = TestResultType.Success;
diff --git a/src/Commands/Testing/Components/Test.cs b/src/Commands/Testing/Components/Test.cs
new file mode 100644
index 00000000..65ad62f5
--- /dev/null
+++ b/src/Commands/Testing/Components/Test.cs
@@ -0,0 +1,30 @@
+namespace Commands.Testing;
+
+///
+public sealed class Test : ITest
+{
+ ///
+ public TestResultType ShouldEvaluateTo { get; }
+
+ ///
+ public string Arguments { get; }
+
+ internal Test(string arguments, TestResultType shouldEvaluateTo)
+ {
+ Arguments = arguments;
+ ShouldEvaluateTo = shouldEvaluateTo;
+ }
+
+ #region Initializers
+
+ ///
+ /// Defines a collection of properties to configure and convert into a new instance of .
+ ///
+ /// The arguments to test with.
+ /// The result to test for.
+ /// A fluent-pattern property object that can be converted into an instance when configured.
+ public static TestProperties From(string? arguments = null, TestResultType testResult = TestResultType.Success)
+ => new TestProperties().AddArguments(arguments).AddResult(testResult);
+
+ #endregion
+}
\ No newline at end of file
diff --git a/src/Commands/Testing/Components/TestCollection.cs b/src/Commands/Testing/Components/TestCollection.cs
new file mode 100644
index 00000000..343e350c
--- /dev/null
+++ b/src/Commands/Testing/Components/TestCollection.cs
@@ -0,0 +1,98 @@
+namespace Commands.Testing;
+
+///
+///
+///
+public sealed class TestCollection : ITestProvider
+{
+ private TestGroup[] _tests;
+
+ ///
+ /// Gets the number of groups contained in this collection.
+ ///
+ public int Count
+ => _tests.Length;
+
+ ///
+ ///
+ ///
+ ///
+ public TestCollection(params TestGroup[] tests)
+ {
+ _tests = tests;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async ValueTask> Execute(Func callerCreation, CommandOptions? options = null)
+ where TContext : class, ICallerContext
+ {
+ options ??= new CommandOptions();
+
+ var results = new IEnumerable[_tests.Length];
+
+ for (var i = 0; i < _tests.Length; i++)
+ results[i] = await _tests[i].Run(callerCreation, options).ConfigureAwait(false);
+
+ return results.SelectMany(x => x);
+ }
+
+ ///
+ public void Add(TestGroup item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ Array.Resize(ref _tests, _tests.Length);
+
+ _tests[_tests.Length] = item;
+ }
+
+ ///
+ public void Clear()
+ => _tests = [];
+
+ ///
+ public bool Contains(TestGroup item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ return _tests.Contains(item);
+ }
+
+ ///
+ public void CopyTo(TestGroup[] array, int arrayIndex)
+ => _tests.CopyTo(array, arrayIndex);
+
+ ///
+ public bool Remove(TestGroup item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ var indexOf = Array.IndexOf(_tests, item);
+
+ if (indexOf == -1)
+ return false;
+
+ for (var i = indexOf; i < _tests.Length - 1; i++)
+ _tests[i] = _tests[i + 1];
+
+ Array.Resize(ref _tests, _tests.Length - 1);
+
+ return true;
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ => ((IEnumerable)_tests).GetEnumerator();
+
+ bool ICollection.IsReadOnly
+ => false;
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+}
diff --git a/src/Commands/Testing/Components/TestGroup.cs b/src/Commands/Testing/Components/TestGroup.cs
new file mode 100644
index 00000000..bc8974d9
--- /dev/null
+++ b/src/Commands/Testing/Components/TestGroup.cs
@@ -0,0 +1,109 @@
+namespace Commands.Testing;
+
+///
+/// Represents a group of implementations to be tested against the that this group targets.
+///
+public sealed class TestGroup : ITestCollection
+{
+ private ITest[] _tests;
+
+ ///
+ /// Gets the command which the tests contained in this should be tested against.
+ ///
+ public Command Command { get; }
+
+ ///
+ /// Gets the number of implementations contained in this group.
+ ///
+ public int Count
+ => _tests.Length;
+
+ ///
+ /// Creates a new targetting the provided command, including the provided tests.
+ ///
+ ///
+ ///
+ public TestGroup(Command command, params ITest[] tests)
+ {
+ Assert.NotNull(command, nameof(command));
+ Assert.NotNull(tests, nameof(tests));
+
+ Command = command;
+
+ _tests = tests;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async ValueTask> Run(Func callerCreation, CommandOptions options)
+ where TContext : class, ICallerContext
+ {
+ Assert.NotNull(callerCreation, nameof(callerCreation));
+ Assert.NotNull(options, nameof(options));
+
+ var results = new TestResult[_tests.Length];
+
+ for (var i = 0; i < _tests.Length; i++)
+ results[i] = await Command.TestAgainst(callerCreation, _tests[i], options).ConfigureAwait(false);
+
+ return results;
+ }
+
+ ///
+ public void Add(ITest item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ Array.Resize(ref _tests, _tests.Length);
+
+ _tests[_tests.Length] = item;
+ }
+
+ ///
+ public void Clear()
+ => _tests = [];
+
+ ///
+ public bool Contains(ITest item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ return _tests.Contains(item);
+ }
+
+ ///
+ public void CopyTo(ITest[] array, int arrayIndex)
+ => _tests.CopyTo(array, arrayIndex);
+
+ ///
+ public bool Remove(ITest item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ var indexOf = Array.IndexOf(_tests, item);
+
+ if (indexOf == -1)
+ return false;
+
+ for (var i = indexOf; i < _tests.Length - 1; i++)
+ _tests[i] = _tests[i + 1];
+
+ Array.Resize(ref _tests, _tests.Length - 1);
+
+ return true;
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ => ((IEnumerable)_tests).GetEnumerator();
+
+ bool ICollection.IsReadOnly
+ => false;
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+}
diff --git a/src/Commands/Testing/Components/TestProperties.cs b/src/Commands/Testing/Components/TestProperties.cs
new file mode 100644
index 00000000..92448f57
--- /dev/null
+++ b/src/Commands/Testing/Components/TestProperties.cs
@@ -0,0 +1,51 @@
+namespace Commands.Testing;
+
+///
+/// A set of properties for a test provider.
+///
+public sealed class TestProperties
+{
+ private string? _arguments;
+ private TestResultType _result;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ public TestProperties()
+ {
+ _result = TestResultType.Success;
+ }
+
+ ///
+ /// Sets the arguments this provider should test with.
+ ///
+ /// The arguments to set.
+ /// The same for call-chaining.
+ public TestProperties AddArguments(string? arguments)
+ {
+ _arguments = arguments;
+
+ return this;
+ }
+
+ ///
+ /// Sets the result this provider should return.
+ ///
+ /// The result to set.
+ /// The same for call-chaining.
+ public TestProperties AddResult(TestResultType result)
+ {
+ _result = result;
+
+ return this;
+ }
+
+ ///
+ /// Converts the properties to a new instance of .
+ ///
+ /// A new instance of .
+ public Test ToTest()
+ {
+ return new Test(_arguments ?? string.Empty, _result);
+ }
+}
diff --git a/src/Commands/Testing/Execution/TestCollection.cs b/src/Commands/Testing/Execution/TestCollection.cs
deleted file mode 100644
index 27fb0b31..00000000
--- a/src/Commands/Testing/Execution/TestCollection.cs
+++ /dev/null
@@ -1,138 +0,0 @@
-
-namespace Commands.Testing;
-
-///
-/// A test collection that can be ran and evaluated per command instance. This class cannot be inherited.
-///
-public sealed class TestCollection : IDictionary
-{
- private readonly Dictionary _tests;
-
- ///
- /// Gets the number of tests present for this runner.
- ///
- public int Count
- => _tests.Sum(x => x.Value.Length);
-
- ///
- public ICollection Keys
- => _tests.Keys;
-
- ///
- public ICollection Values
- => _tests.Values;
-
- ///
- public bool IsReadOnly { get; } = false;
-
- ///
- public ITestProvider[] this[Command key]
- {
- get => _tests[key];
- set => _tests[key] = value;
- }
-
- ///
- /// Creates a new instance of with no tests.
- ///
- public TestCollection()
- {
- _tests = [];
- }
-
- ///
- /// Creates a new instance of with the specified tests.
- ///
- /// The tests, grouped by command that should
- public TestCollection(Dictionary tests)
- {
- _tests = tests;
- }
-
- ///
- /// Starts all contained tests sequentially and returns the total result.
- ///
- /// An action for creating new context when a command is executed. The inbound string represents the tested command name and arguments.
- /// The options to use when running the tests.
- /// An awaitable that represents testing operation.
- public async Task Execute(Func creationAction, CommandOptions? options = null)
- {
- options ??= new CommandOptions();
-
- var arr = new TestResult[Count];
- var i = 0;
-
- foreach (var test in _tests)
- {
- options.CancellationToken.ThrowIfCancellationRequested();
-
- foreach (var provider in test.Value)
- {
- arr[i] = await test.Key.Test(creationAction, provider, options);
- i++;
- }
- }
-
- return arr;
- }
-
- ///
- public bool ContainsKey(Command key)
- => _tests.ContainsKey(key);
-
- ///
- public bool TryGetValue(Command key,
-#if NET8_0_OR_GREATER
- [MaybeNullWhen(false)]
-#endif
- out ITestProvider[] value)
- => _tests.TryGetValue(key, out value);
-
- ///
- public void Add(Command key, ITestProvider[] value)
- => _tests.Add(key, value);
-
- ///
- public bool Remove(Command key)
- => _tests.Remove(key);
-
- ///
- public void Clear()
- => _tests.Clear();
-
- ///
- public bool Remove(KeyValuePair item)
- => _tests.Remove(item.Key);
-
- ///
- public IEnumerator> GetEnumerator()
- => _tests.GetEnumerator();
-
- #region Initializers
-
- ///
- /// Defines a collection of properties to configure and convert into a new instance of , with the specified commands.
- ///
- /// A collection of commands to evaluate. Commands marked with will have test providers automatically defined for them.
- /// A fluent-pattern property object that can be converted into an instance when configured.
- public static TestCollectionProperties From(params Command[] commands)
- => new TestCollectionProperties().AddCommands(commands);
-
- #endregion
-
- #region IDictionary<>
-
- IEnumerator IEnumerable.GetEnumerator()
- => GetEnumerator();
-
- void ICollection>.Add(KeyValuePair item)
- => _tests.Add(item.Key, item.Value);
-
- bool ICollection>.Contains(KeyValuePair item)
- => _tests.ContainsKey(item.Key) && _tests[item.Key].SequenceEqual(item.Value);
-
- void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
- => ((ICollection>)_tests).CopyTo(array, arrayIndex);
-
- #endregion
-}
\ No newline at end of file
diff --git a/src/Commands/Testing/Execution/TestCollectionProperties.cs b/src/Commands/Testing/Execution/TestCollectionProperties.cs
deleted file mode 100644
index b0a9dd0f..00000000
--- a/src/Commands/Testing/Execution/TestCollectionProperties.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-namespace Commands.Testing;
-
-///
-/// A set of properties for a test runner.
-///
-public sealed class TestCollectionProperties
-{
- private readonly List _tests;
- private readonly List _commands;
-
- ///
- /// Creates a new instance of .
- ///
- public TestCollectionProperties()
- {
- _tests = [];
- _commands = [];
- }
-
- ///
- /// Adds a test provider to the test runner.
- ///
- /// The provider to add.
- /// The same for call-chaining.
- public TestCollectionProperties AddTest(TestProviderProperties provider)
- {
- Assert.NotNull(provider, nameof(provider));
-
- _tests.Add(provider);
-
- return this;
- }
-
- ///
- /// Adds multiple test providers to the test runner.
- ///
- /// The providers to add.
- /// The same for call-chaining.
- public TestCollectionProperties AddTests(params TestProviderProperties[] providers)
- {
- foreach (var provider in providers)
- AddTest(provider);
-
- return this;
- }
-
- ///
- /// Adds a command to the test runner.
- ///
- /// The command to add.
- /// The same for call-chaining.
- public TestCollectionProperties AddCommand(Command command)
- {
- Assert.NotNull(command, nameof(command));
-
- _commands.Add(command);
-
- return this;
- }
-
- ///
- /// Adds multiple commands to the test runner.
- ///
- /// The commands to add.
- /// The same for call-chaining.
- public TestCollectionProperties AddCommands(params Command[] commands)
- {
- foreach (var command in commands)
- AddCommand(command);
-
- return this;
- }
-
- ///
- /// Converts the properties to a new instance of .
- ///
- /// A new instance of .
- public TestCollection ToCollection()
- {
- var tests = _commands.ToDictionary(x => x, x => x.Attributes.OfType().ToArray());
-
- var runtimeDefined = _tests.Select(x => x.ToProvider()).GroupBy(x => x.Command);
-
- foreach (var group in runtimeDefined)
- {
- if (tests.TryGetValue(group.Key, out var value))
- tests[group.Key] = [.. value, .. group];
- else
- tests[group.Key] = [.. group];
- }
-
- return new TestCollection(tests);
- }
-}
\ No newline at end of file
diff --git a/src/Commands/Testing/TestResult.cs b/src/Commands/Testing/Results/TestResult.cs
similarity index 100%
rename from src/Commands/Testing/TestResult.cs
rename to src/Commands/Testing/Results/TestResult.cs
diff --git a/src/Commands/Testing/TestProvider.cs b/src/Commands/Testing/TestProvider.cs
deleted file mode 100644
index 05e19f83..00000000
--- a/src/Commands/Testing/TestProvider.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-namespace Commands.Testing;
-
-///
-public class TestProvider : ITestProvider
-{
- ///
- public TestResultType ShouldEvaluateTo { get; }
-
- ///
- public string Arguments { get; }
-
- ///
- /// The command that this test provider is associated with.
- ///
- public Command Command { get; }
-
- internal TestProvider(Command command, string arguments, TestResultType shouldEvaluateTo)
- {
- Command = command;
- Arguments = arguments;
- ShouldEvaluateTo = shouldEvaluateTo;
- }
-
- #region Initializers
-
- ///
- /// Defines a collection of properties to configure and convert into a new instance of .
- ///
- /// The command to test.
- /// The arguments to test with.
- /// The result to test for.
- /// A fluent-pattern property object that can be converted into an instance when configured.
- public static TestProviderProperties From(Command command, string? arguments = null, TestResultType testResult = TestResultType.Success)
- => new TestProviderProperties().AddCommand(command).AddArguments(arguments).AddResult(testResult);
-
- #endregion
-}
\ No newline at end of file
diff --git a/src/Commands/Testing/TestProviderProperties.cs b/src/Commands/Testing/TestProviderProperties.cs
deleted file mode 100644
index aa6ad7ad..00000000
--- a/src/Commands/Testing/TestProviderProperties.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-namespace Commands.Testing;
-
-///
-/// A set of properties for a test provider.
-///
-public sealed class TestProviderProperties
-{
- private string? _arguments;
- private Command? _command;
- private TestResultType _result;
-
- ///
- /// Creates a new instance of .
- ///
- public TestProviderProperties()
- {
- _command = null;
- _result = TestResultType.Success;
- }
-
- ///
- /// Sets the command this provider should test.
- ///
- /// The command to set.
- /// The same for call-chaining.
- public TestProviderProperties AddCommand(Command command)
- {
- Assert.NotNull(command, nameof(command));
-
- _command = command;
-
- return this;
- }
-
- ///
- /// Sets the arguments this provider should test with.
- ///
- /// The arguments to set.
- /// The same for call-chaining.
- public TestProviderProperties AddArguments(string? arguments)
- {
- _arguments = arguments;
-
- return this;
- }
-
- ///
- /// Sets the result this provider should return.
- ///
- /// The result to set.
- /// The same for call-chaining.
- public TestProviderProperties AddResult(TestResultType result)
- {
- _result = result;
-
- return this;
- }
-
- ///
- /// Converts the properties to a new instance of .
- ///
- /// A new instance of .
- public TestProvider ToProvider()
- {
- Assert.NotNull(_command, nameof(_command));
-
- return new TestProvider(_command!, _arguments ?? string.Empty, _result);
- }
-}
diff --git a/src/Commands/Testing/TestUtilities.cs b/src/Commands/Testing/TestUtilities.cs
index 355bf8f0..a47ecda3 100644
--- a/src/Commands/Testing/TestUtilities.cs
+++ b/src/Commands/Testing/TestUtilities.cs
@@ -2,7 +2,8 @@
internal static class TestUtilities
{
- public static async ValueTask Test(this Command command, Func callerCreation, ITestProvider provider, CommandOptions options)
+ public static async ValueTask TestAgainst(this Command command, Func callerCreation, ITest provider, CommandOptions options)
+ where TContext : class, ICallerContext
{
TestResult GetResult(IResult result)
{
From 7062cb965d130fbedf722c8c3a38f8bbe4aec6b5 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Sun, 4 May 2025 16:23:17 +0200
Subject: [PATCH 2/6] Update gitignore
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 5fccc8a5..26615651 100644
--- a/.gitignore
+++ b/.gitignore
@@ -362,3 +362,4 @@ MigrationBackup/
# Fody - auto-generated XML schema
FodyWeavers.xsd
/Visual Studio 2022/Visualizers
+/src/Commands.Samples/Commands.Samples.Console/Properties
From d255a7f952daf0552e3f2931b162b808c87c4ecc Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Sun, 4 May 2025 16:23:29 +0200
Subject: [PATCH 3/6] Roll out reiterations
---
.../Hosting/Execution/HostedCommandOptions.cs | 2 +-
src/Commands.Hosting/Hosting/HostUtilities.cs | 14 +-
.../Hosting/ServiceUtilities.cs | 16 +--
.../Commands.Samples.Console/Program.cs | 4 +-
.../Commands.Samples.Core/Program.cs | 4 +-
.../Commands.Tests.Aot/Program.cs | 2 +-
.../Commands.Tests.Benchmarks/Program.cs | 12 +-
.../Commands.Tests.Console/Program.cs | 16 ++-
src/Commands/Commands.csproj | 5 -
.../Components/ComponentCollectionBase.cs | 4 +-
...nentCollection.cs => ComponentProvider.cs} | 16 +--
...ties.cs => ComponentProviderProperties.cs} | 112 +++++++--------
.../Core/Execution/ArgumentDictionary.cs | 70 +++++-----
src/Commands/Core/Execution/CommandContext.cs | 4 +-
src/Commands/Core/Execution/CommandModule.cs | 4 +-
src/Commands/Core/Execution/CommandOptions.cs | 4 +-
.../Core/Execution/ConsoleCallerContext.cs | 6 +-
.../{Components => }/Abstractions/ITest.cs | 0
.../Testing/Abstractions/ITestProvider.cs | 26 ++++
.../Abstractions/TestResultType.cs | 0
.../Abstractions/ITestCollection.cs | 22 ---
.../Components/Abstractions/ITestProvider.cs | 17 ---
.../Testing/Components/TestCollection.cs | 98 -------------
src/Commands/Testing/Components/TestGroup.cs | 109 ---------------
src/Commands/Testing/Execution/TestContext.cs | 14 --
src/Commands/Testing/{Components => }/Test.cs | 0
.../Attributes => }/TestAttribute.cs | 3 +-
.../{Components => }/TestProperties.cs | 0
src/Commands/Testing/TestProvider.cs | 131 ++++++++++++++++++
.../Testing/TestProviderProperties.cs | 119 ++++++++++++++++
.../Testing/{Results => }/TestResult.cs | 27 ++--
src/Commands/Testing/TestUtilities.cs | 12 +-
32 files changed, 446 insertions(+), 427 deletions(-)
rename src/Commands/Core/Components/{ComponentCollection.cs => ComponentProvider.cs} (87%)
rename src/Commands/Core/Components/{ComponentCollectionProperties.cs => ComponentProviderProperties.cs} (51%)
rename src/Commands/Testing/{Components => }/Abstractions/ITest.cs (100%)
create mode 100644 src/Commands/Testing/Abstractions/ITestProvider.cs
rename src/Commands/Testing/{Components => }/Abstractions/TestResultType.cs (100%)
delete mode 100644 src/Commands/Testing/Components/Abstractions/ITestCollection.cs
delete mode 100644 src/Commands/Testing/Components/Abstractions/ITestProvider.cs
delete mode 100644 src/Commands/Testing/Components/TestCollection.cs
delete mode 100644 src/Commands/Testing/Components/TestGroup.cs
delete mode 100644 src/Commands/Testing/Execution/TestContext.cs
rename src/Commands/Testing/{Components => }/Test.cs (100%)
rename src/Commands/Testing/{Components/Attributes => }/TestAttribute.cs (92%)
rename src/Commands/Testing/{Components => }/TestProperties.cs (100%)
create mode 100644 src/Commands/Testing/TestProvider.cs
create mode 100644 src/Commands/Testing/TestProviderProperties.cs
rename src/Commands/Testing/{Results => }/TestResult.cs (63%)
diff --git a/src/Commands.Hosting/Hosting/Execution/HostedCommandOptions.cs b/src/Commands.Hosting/Hosting/Execution/HostedCommandOptions.cs
index 5e09e912..324d9f12 100644
--- a/src/Commands.Hosting/Hosting/Execution/HostedCommandOptions.cs
+++ b/src/Commands.Hosting/Hosting/Execution/HostedCommandOptions.cs
@@ -30,7 +30,7 @@ public sealed class HostedCommandOptions
/// This behavior drastically changes execution flow, and as such, there are a few things to consider when using it:
///
///
- /// The end-user must provide an implementation of to the that is used to execute the command, in order to handle the result of the command.
+ /// The end-user must provide an implementation of to the that is used to execute the command, in order to handle the result of the command.
///
///
/// Objects, specifically those scoped to more than a single command must be made thread-safe, meaning they must be able to handle multiple requests at once.
diff --git a/src/Commands.Hosting/Hosting/HostUtilities.cs b/src/Commands.Hosting/Hosting/HostUtilities.cs
index 4607d35a..26672c12 100644
--- a/src/Commands.Hosting/Hosting/HostUtilities.cs
+++ b/src/Commands.Hosting/Hosting/HostUtilities.cs
@@ -19,31 +19,31 @@ public static IHostBuilder ConfigureComponents(this IHostBuilder builder)
///
///
- /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
+ /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
/// The same for call-chaining.
- public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action configureAction)
+ public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action configureAction)
=> ConfigureComponents(builder, (ctx, props) => configureAction(props));
///
///
- /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
+ /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
/// The same for call-chaining.
- public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action configureAction)
+ public static IHostBuilder ConfigureComponents(this IHostBuilder builder, Action configureAction)
=> ConfigureComponents(builder, configureAction);
///
/// The implementation of to consider the factory for executing commands using this host as the lifetime.
///
- /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
+ /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
/// The same for call-chaining.
public static IHostBuilder ConfigureComponents<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactory>
- (this IHostBuilder builder, Action configureAction)
+ (this IHostBuilder builder, Action configureAction)
where TFactory : CommandExecutionFactory
{
Assert.NotNull(builder, nameof(builder));
Assert.NotNull(configureAction, nameof(configureAction));
- var properties = new ComponentCollectionProperties();
+ var properties = new ComponentProviderProperties();
var services = builder.ConfigureServices((ctx, services) =>
{
configureAction(ctx, properties);
diff --git a/src/Commands.Hosting/Hosting/ServiceUtilities.cs b/src/Commands.Hosting/Hosting/ServiceUtilities.cs
index c9aeeee7..199d1763 100644
--- a/src/Commands.Hosting/Hosting/ServiceUtilities.cs
+++ b/src/Commands.Hosting/Hosting/ServiceUtilities.cs
@@ -16,16 +16,16 @@ public static class ServiceUtilities
/// Additionally, it provides a factory based execution mechanism for commands, implementing a singleton , scoped and transient .
///
///
- /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
+ /// An action responsible for configuring a newly created instance of in preparation for building an implementation of to execute commands with.
/// The same for call-chaining.
public static IServiceCollection AddComponentCollection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactory>
- (this IServiceCollection services, Action configureAction)
+ (this IServiceCollection services, Action configureAction)
where TFactory : class, IExecutionFactory
{
Assert.NotNull(services, nameof(services));
Assert.NotNull(configureAction, nameof(configureAction));
- var properties = new ComponentCollectionProperties();
+ var properties = new ComponentProviderProperties();
configureAction(properties);
@@ -34,7 +34,7 @@ public static class ServiceUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static IServiceCollection AddComponentCollection<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TFactory>
- (IServiceCollection services, ComponentCollectionProperties properties)
+ (IServiceCollection services, ComponentProviderProperties properties)
where TFactory : class, IExecutionFactory
{
if (services.Contains())
@@ -52,12 +52,12 @@ public static class ServiceUtilities
services.AddScoped();
services.AddTransient(typeof(ICallerContextAccessor<>), typeof(CallerContextAccessor<>));
- var collectionDescriptor = ServiceDescriptor.Singleton(x =>
+ var collectionDescriptor = ServiceDescriptor.Singleton(x =>
{
// Implement global result handler to dispose of the execution scope. This must be done last, even if the properties are mutated anywhere before.
properties.AddResultHandler(new ExecutionScopeResolver());
- var collection = properties.ToCollection();
+ var collection = properties.ToProvider();
return collection;
});
@@ -66,10 +66,10 @@ public static class ServiceUtilities
{
var provider = x.GetRequiredService();
- if (provider is ComponentCollection collection)
+ if (provider is ComponentProvider collection)
return collection.Configuration;
- throw new NotSupportedException($"The component collection is not available in the current context. Ensure that you are configuring an instance of {nameof(ComponentCollection)}.");
+ throw new NotSupportedException($"The component collection is not available in the current context. Ensure that you are configuring an instance of {nameof(ComponentProvider)}.");
});
services.Add(collectionDescriptor);
diff --git a/src/Commands.Samples/Commands.Samples.Console/Program.cs b/src/Commands.Samples/Commands.Samples.Console/Program.cs
index d5e2807d..a0841a94 100644
--- a/src/Commands.Samples/Commands.Samples.Console/Program.cs
+++ b/src/Commands.Samples/Commands.Samples.Console/Program.cs
@@ -7,10 +7,10 @@
var results = ResultHandler.For()
.AddDelegate((c, e, s) => c.Respond(e));
-var components = new ComponentCollectionProperties()
+var components = new ComponentProviderProperties()
.WithConfiguration(configuration)
.AddResultHandler(results)
- .ToCollection();
+ .ToProvider();
var tests = new TestCollectionProperties()
.AddCommands([.. components.GetCommands()])
diff --git a/src/Commands.Samples/Commands.Samples.Core/Program.cs b/src/Commands.Samples/Commands.Samples.Core/Program.cs
index 030f162f..a3308a39 100644
--- a/src/Commands.Samples/Commands.Samples.Core/Program.cs
+++ b/src/Commands.Samples/Commands.Samples.Core/Program.cs
@@ -13,9 +13,9 @@
Command.From(Divide, "divide", "div")
);
-var components = ComponentCollection.From(exit, mathCommands)
+var components = ComponentProvider.From(exit, mathCommands)
.AddComponentType()
- .ToCollection();
+ .ToProvider();
await components.Execute(new ConsoleCallerContext(args));
diff --git a/src/Commands.Tests/Commands.Tests.Aot/Program.cs b/src/Commands.Tests/Commands.Tests.Aot/Program.cs
index 785a8f4b..d78e1b3c 100644
--- a/src/Commands.Tests/Commands.Tests.Aot/Program.cs
+++ b/src/Commands.Tests/Commands.Tests.Aot/Program.cs
@@ -1,7 +1,7 @@
using Commands;
using Commands.Tests;
-var manager = new ComponentCollection(new DelegateResultHandler((c, e, s) => c.Respond(e)))
+var manager = new ComponentProvider(new DelegateResultHandler((c, e, s) => c.Respond(e)))
{
new CommandGroup(typeof(Module), configuration: new ComponentConfiguration()),
new CommandGroup("commandgroup")
diff --git a/src/Commands.Tests/Commands.Tests.Benchmarks/Program.cs b/src/Commands.Tests/Commands.Tests.Benchmarks/Program.cs
index 8e8420b4..2cb581de 100644
--- a/src/Commands.Tests/Commands.Tests.Benchmarks/Program.cs
+++ b/src/Commands.Tests/Commands.Tests.Benchmarks/Program.cs
@@ -5,7 +5,7 @@ namespace Commands.Tests;
public class BenchmarkCallerContext(string? input) : AsyncCallerContext
{
- public override ArgumentDictionary Arguments { get; } = ArgumentDictionary.From(input);
+ public override ArgumentDictionary Arguments { get; } = ArgumentDictionary.FromString(input);
public override Task Respond(object? response)
=> Task.CompletedTask;
@@ -27,18 +27,18 @@ public static void Command3() { }
[MemoryDiagnoser]
public class Program
{
- private static readonly ArgumentDictionary _args = ArgumentDictionary.From("command");
- private static readonly ComponentCollection _components = ComponentCollection.From()
+ private static readonly ArgumentDictionary _args = ArgumentDictionary.FromString("command");
+ private static readonly ComponentProvider _components = ComponentProvider.From()
.AddComponentType()
.AddComponent(Command.From(() => { }, "command"))
- .ToCollection();
+ .ToProvider();
static void Main()
=> BenchmarkRunner.Run();
[Benchmark]
public void CreateArguments()
- => ArgumentDictionary.From("command");
+ => ArgumentDictionary.FromString("command");
[Benchmark]
public void FindCommands()
@@ -56,7 +56,7 @@ public Task RunCommandNonBlocking()
});
[Benchmark]
- public ComponentCollection CollectionCreate()
+ public ComponentProvider CollectionCreate()
=> [];
[Benchmark]
diff --git a/src/Commands.Tests/Commands.Tests.Console/Program.cs b/src/Commands.Tests/Commands.Tests.Console/Program.cs
index 16698421..b8ba76f1 100644
--- a/src/Commands.Tests/Commands.Tests.Console/Program.cs
+++ b/src/Commands.Tests/Commands.Tests.Console/Program.cs
@@ -1,7 +1,7 @@
using Commands;
using Commands.Testing;
-var components = new ComponentCollectionProperties()
+var components = new ComponentProviderProperties()
.AddComponentTypes(typeof(Program).Assembly.GetExportedTypes())
.AddResultHandler(ResultHandler.From((c, e, s) => c.Respond(e)))
.AddComponent(
@@ -11,15 +11,17 @@
c.Respond(command);
}, "help"))
- .ToCollection();
+ .ToProvider();
-var tests = TestCollection.From(components.GetCommands())
- .ToCollection();
+var tests = components.GetCommands().Select(x => TestProvider.From(x).ToProvider());
-var results = await tests.Execute((str) => new TestContext(str));
+foreach (var test in tests)
+{
+ var result = await test.Test(x => new ConsoleCallerContext(x));
-if (results.Count(x => x.Success) == tests.Count)
- Console.WriteLine("All tests ran succesfully.");
+ if (result.Any(x => !x.Success))
+ throw new InvalidOperationException($"A command test failed to evaluate to success. Command: {test.Command}. Test: {result.FirstOrDefault(x => !x.Success).Test}");
+}
while (true)
await components.Execute(new ConsoleCallerContext(Console.ReadLine()));
\ No newline at end of file
diff --git a/src/Commands/Commands.csproj b/src/Commands/Commands.csproj
index e74312eb..e2ef75bf 100644
--- a/src/Commands/Commands.csproj
+++ b/src/Commands/Commands.csproj
@@ -52,10 +52,5 @@
\
-
-
-
-
-
diff --git a/src/Commands/Core/Components/ComponentCollectionBase.cs b/src/Commands/Core/Components/ComponentCollectionBase.cs
index 23da0e8a..0d321c5a 100644
--- a/src/Commands/Core/Components/ComponentCollectionBase.cs
+++ b/src/Commands/Core/Components/ComponentCollectionBase.cs
@@ -235,7 +235,7 @@ private List FilterComponents(IEnumerable components)
if (_items.Contains(component))
continue;
- if (this is ComponentCollection manager)
+ if (this is ComponentProvider manager)
{
// When a component is not searchable it means it has no names. Between a manager and a group, a different restriction applies to how this should be done.
if (!component.IsSearchable)
@@ -261,7 +261,7 @@ private List FilterComponents(IEnumerable components)
// Anything added to a group should be considered nested.
// Because of the nature of this design, we want to avoid folding anything but top level. This means that nested groups must be named.
if (component is not Command)
- throw new InvalidOperationException($"{nameof(CommandGroup)} instances without names can only be added to a {nameof(ComponentCollection)}.");
+ throw new InvalidOperationException($"{nameof(CommandGroup)} instances without names can only be added to a {nameof(ComponentProvider)}.");
discovered.Add(component);
}
diff --git a/src/Commands/Core/Components/ComponentCollection.cs b/src/Commands/Core/Components/ComponentProvider.cs
similarity index 87%
rename from src/Commands/Core/Components/ComponentCollection.cs
rename to src/Commands/Core/Components/ComponentProvider.cs
index dc3675ec..ecbd80fe 100644
--- a/src/Commands/Core/Components/ComponentCollection.cs
+++ b/src/Commands/Core/Components/ComponentProvider.cs
@@ -4,7 +4,7 @@
/// A concurrent implementation of the mechanism that allows commands to be executed using a provided set of arguments. This class cannot be inherited.
///
[DebuggerDisplay("Count = {Count}")]
-public sealed class ComponentCollection : ComponentCollectionBase, IExecutionProvider
+public sealed class ComponentProvider : ComponentCollectionBase, IExecutionProvider
{
private readonly ResultHandler[] _handlers;
@@ -20,14 +20,14 @@ public IReadOnlyCollection Handlers
=> _handlers;
///
- /// Creates a new instance of with the specified handlers.
+ /// Creates a new instance of with the specified handlers.
///
///
/// This overload supports enumerable service injection in order to create a manager from service definitions.
///
/// The configuration for this component manager.
/// A collection of handlers for post-execution processing of retrieved command input.
- public ComponentCollection(ComponentConfiguration configuration, IEnumerable handlers)
+ public ComponentProvider(ComponentConfiguration configuration, IEnumerable handlers)
{
Configuration = configuration;
@@ -36,10 +36,10 @@ public ComponentCollection(ComponentConfiguration configuration, IEnumerable
- /// Creates a new instance of with the specified handlers.
+ /// Creates a new instance of with the specified handlers.
///
/// A collection of handlers for post-execution processing of retrieved command input.
- public ComponentCollection(params ResultHandler[] handlers)
+ public ComponentProvider(params ResultHandler[] handlers)
{
_handlers = handlers;
@@ -141,12 +141,12 @@ private async Task WorkInternal(TContext context, CommandOpti
#region Initializers
///
- /// Defines a collection of properties to configure and convert into a new instance of .
+ /// Defines a collection of properties to configure and convert into a new instance of .
///
/// The components to add.
/// A fluent-pattern property object that can be converted into an instance when configured.
- public static ComponentCollectionProperties From(params IComponentProperties[] components)
- => new ComponentCollectionProperties().AddComponents(components);
+ public static ComponentProviderProperties From(params IComponentProperties[] components)
+ => new ComponentProviderProperties().AddComponents(components);
#endregion
}
diff --git a/src/Commands/Core/Components/ComponentCollectionProperties.cs b/src/Commands/Core/Components/ComponentProviderProperties.cs
similarity index 51%
rename from src/Commands/Core/Components/ComponentCollectionProperties.cs
rename to src/Commands/Core/Components/ComponentProviderProperties.cs
index c547855a..7ebbfe20 100644
--- a/src/Commands/Core/Components/ComponentCollectionProperties.cs
+++ b/src/Commands/Core/Components/ComponentProviderProperties.cs
@@ -1,9 +1,9 @@
namespace Commands;
///
-/// A set of properties for a component manager.
+/// A set of properties for a component provider.
///
-public sealed class ComponentCollectionProperties
+public sealed class ComponentProviderProperties
{
private readonly List _dynamicTypes;
@@ -13,9 +13,9 @@ public sealed class ComponentCollectionProperties
private ComponentConfigurationProperties? _configuration;
///
- /// Creates a new instance of .
+ /// Creates a new instance of .
///
- public ComponentCollectionProperties()
+ public ComponentProviderProperties()
{
_dynamicTypes = [];
_components = [];
@@ -25,14 +25,14 @@ public ComponentCollectionProperties()
}
///
- /// Adds a type to the component manager. This operation can include non-command module types, but they will be ignored when the manager is created.
+ /// Adds a type to the component provider. This operation can include non-command module types, but they will be ignored when the provider is created.
///
///
- /// Types are evaluated whether they implement , are not abstract, and have no open generic parameters when the manager is created. Any added types that do not match this constraint are ignored.
+ /// Types are evaluated whether they implement , are not abstract, and have no open generic parameters when the provider is created. Any added types that do not match this constraint are ignored.
///
/// The type to add. If the type is already added, it is ignored.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddComponentType(
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddComponentType(
#if NET8_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicNestedTypes)]
# endif
@@ -49,11 +49,11 @@ public ComponentCollectionProperties AddComponentType(
}
///
- /// Adds a type to the component manager. This operation can include non-command module types, but they will be ignored when the manager is created.
+ /// Adds a type to the component provider. This operation can include non-command module types, but they will be ignored when the provider is created.
///
/// The type definition to add. If the type is already added, it is ignored.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddComponentType<
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddComponentType<
#if NET8_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicNestedTypes)]
#endif
@@ -64,18 +64,18 @@ public ComponentCollectionProperties AddComponentType<
}
///
- /// Adds multiple types to the component manager. This operation can include non-command module types, but they will be ignored when the manager is created.
+ /// Adds multiple types to the component provider. This operation can include non-command module types, but they will be ignored when the provider is created.
///
///
- /// When is called on the properties, all types added to the properties are checked if they implement or .
+ /// When is called on the properties, all types added to the properties are checked if they implement or .
/// If any provided type does not implement said base type, it is ignored.
///
/// The types to add. If any type is already added, it is ignored.
- /// The same for call-chaining.
+ /// The same for call-chaining.
#if NET8_0_OR_GREATER
[UnconditionalSuppressMessage("AotAnalysis", "IL2072", Justification = "The types are supplied from user-facing implementation, it is up to the user to ensure that these types are available in AOT context.")]
#endif
- public ComponentCollectionProperties AddComponentTypes(params Type[] types)
+ public ComponentProviderProperties AddComponentTypes(params Type[] types)
{
foreach (var componentType in types)
AddComponentType(componentType);
@@ -84,14 +84,14 @@ public ComponentCollectionProperties AddComponentTypes(params Type[] types)
}
///
- /// Adds a component to the component manager.
+ /// Adds a component to the component provider.
///
///
- /// Commands added to the manager must have at least one name. Groups that are added are not required to have a name.
+ /// Commands added to the provider must have at least one name. Groups that are added are not required to have a name.
///
/// The component to add.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddComponent(IComponentProperties component)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddComponent(IComponentProperties component)
{
Assert.NotNull(component, nameof(component));
@@ -101,14 +101,14 @@ public ComponentCollectionProperties AddComponent(IComponentProperties component
}
///
- /// Adds multiple components to the component manager.
+ /// Adds multiple components to the component provider.
///
///
- /// Commands added to the manager must have at least one name. Groups that are added are not required to have a name.
+ /// Commands added to the provider must have at least one name. Groups that are added are not required to have a name.
///
/// The components to add.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddComponents(params IComponentProperties[] components)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddComponents(params IComponentProperties[] components)
{
foreach (var component in components)
AddComponent(component);
@@ -117,11 +117,11 @@ public ComponentCollectionProperties AddComponents(params IComponentProperties[]
}
///
- /// Adds a result handler to the component manager.
+ /// Adds a result handler to the component provider.
///
/// The handler to add.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddResultHandler(IResultHandlerProperties handler)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddResultHandler(IResultHandlerProperties handler)
{
Assert.NotNull(handler, nameof(handler));
@@ -131,39 +131,39 @@ public ComponentCollectionProperties AddResultHandler(IResultHandlerProperties h
}
///
- /// Adds a result handler to the component manager.
+ /// Adds a result handler to the component provider.
///
/// The handler to add.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddResultHandler(ResultHandler handler)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddResultHandler(ResultHandler handler)
=> AddResultHandler(new ResultHandlerProperties(handler));
///
- /// Adds a result handler to the component manager.
+ /// Adds a result handler to the component provider.
///
/// The context type for the handler to handle.
/// The delegate that is executed when the result of command execution is yielded.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddResultHandler(Action executionDelegate)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddResultHandler(Action executionDelegate)
where T : class, ICallerContext
=> AddResultHandler(new ResultHandlerProperties().AddDelegate(executionDelegate));
///
- /// Adds a result handler to the component manager.
+ /// Adds a result handler to the component provider.
///
/// The context type for the handler to handle.
/// The delegate that is executed when the result of command execution is yielded.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddResultHandler(Func executionDelegate)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddResultHandler(Func executionDelegate)
where T : class, ICallerContext
=> AddResultHandler(new ResultHandlerProperties().AddDelegate(executionDelegate));
///
- /// Adds multiple result handlers to the component manager.
+ /// Adds multiple result handlers to the component provider.
///
/// The handlers to add.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddResultHandlers(params IResultHandlerProperties[] handlers)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddResultHandlers(params IResultHandlerProperties[] handlers)
{
foreach (var handler in handlers)
AddResultHandler(handler);
@@ -172,11 +172,11 @@ public ComponentCollectionProperties AddResultHandlers(params IResultHandlerProp
}
///
- /// Adds multiple result handlers to the component manager.
+ /// Adds multiple result handlers to the component provider.
///
/// The handlers to add.
- /// The same for call-chaining.
- public ComponentCollectionProperties AddResultHandlers(params ResultHandler[] handlers)
+ /// The same for call-chaining.
+ public ComponentProviderProperties AddResultHandlers(params ResultHandler[] handlers)
{
foreach (var handler in handlers)
AddResultHandler(new ResultHandlerProperties(handler));
@@ -185,11 +185,11 @@ public ComponentCollectionProperties AddResultHandlers(params ResultHandler[] ha
}
///
- /// Sets the configuration for the component manager.
+ /// Sets the configuration for the component provider.
///
- /// The configuration which should configure defined components that are to be built for this manager.
- /// The same for call-chaining.
- public ComponentCollectionProperties WithConfiguration(ComponentConfigurationProperties configuration)
+ /// The configuration which should configure defined components that are to be built for this provider.
+ /// The same for call-chaining.
+ public ComponentProviderProperties WithConfiguration(ComponentConfigurationProperties configuration)
{
Assert.NotNull(configuration, nameof(configuration));
@@ -199,11 +199,11 @@ public ComponentCollectionProperties WithConfiguration(ComponentConfigurationPro
}
///
- /// Sets the configuration for the component manager.
+ /// Sets the configuration for the component provider.
///
- /// An action that configures a newly created instance of to be built for this manager.
- /// The same for call-chaining.
- public ComponentCollectionProperties WithConfiguration(Action configure)
+ /// An action that configures a newly created instance of to be built for this provider.
+ /// The same for call-chaining.
+ public ComponentProviderProperties WithConfiguration(Action configure)
{
Assert.NotNull(configure, nameof(configure));
@@ -216,20 +216,20 @@ public ComponentCollectionProperties WithConfiguration(Action
- /// Converts this set of properties to a new instance of .
+ /// Converts this set of properties to a new instance of .
///
- /// A new instance of .
- public ComponentCollection ToCollection()
+ /// A new instance of .
+ public ComponentProvider ToProvider()
{
_configuration ??= ComponentConfigurationProperties.Default;
var configuration = _configuration.ToConfiguration();
- var manager = new ComponentCollection(configuration, [.. _handlers.Select(handler => handler.ToHandler())]);
+ var provider = new ComponentProvider(configuration, [.. _handlers.Select(handler => handler.ToHandler())]);
- manager.AddRange(_components.Select(component => component.ToComponent(configuration: configuration)));
- manager.AddRange(ComponentUtilities.GetComponents(configuration, _dynamicTypes, null, false));
+ provider.AddRange(_components.Select(component => component.ToComponent(configuration: configuration)));
+ provider.AddRange(ComponentUtilities.GetComponents(configuration, _dynamicTypes, null, false));
- return manager;
+ return provider;
}
}
diff --git a/src/Commands/Core/Execution/ArgumentDictionary.cs b/src/Commands/Core/Execution/ArgumentDictionary.cs
index c23e531d..0fdb321a 100644
--- a/src/Commands/Core/Execution/ArgumentDictionary.cs
+++ b/src/Commands/Core/Execution/ArgumentDictionary.cs
@@ -17,32 +17,32 @@ public struct ArgumentDictionary
private int _index = 0;
- private readonly List _unnamedArgs;
+ private readonly string[] _unnamedArgs;
private readonly Dictionary _namedArgs;
internal int AvailableLength { get; private set; }
///
- /// Gets the number of arguments present in the set.
+ /// Gets the number of keys present in the dictionary.
///
public readonly int Count
- => _unnamedArgs.Count + _namedArgs.Count;
+ => _unnamedArgs.Length + _namedArgs.Count;
///
- /// Gets a key-value pair from the set of arguments, known by the provided .
+ /// Gets the value from the set of arguments, known by the provided .
///
/// The key under which this argument is known to the current array.
- /// A
+ /// An object representing the value belonging to the specified key. If no value exists but the key is represented in the dictionary, is returned instead.
/// Thrown when the provided is not found in the set.
- public readonly KeyValuePair this[string key]
+ public readonly object? this[string key]
{
get
{
if (_namedArgs.TryGetValue(key, out var value))
- return new(key, value);
+ return value;
if (_unnamedArgs.Contains(key, StringComparer.OrdinalIgnoreCase))
- return new(key, null);
+ return null;
throw new KeyNotFoundException();
}
@@ -53,22 +53,26 @@ public readonly int Count
///
/// The range of named arguments to enumerate in this set.
/// The comparer to evaluate keys in the inner named dictionary.
- public ArgumentDictionary(StringComparer? comparer, IEnumerable> args)
+ public ArgumentDictionary(IEnumerable> args, StringComparer? comparer)
{
_namedArgs = new(comparer);
- var unnamedFill = new List();
+ var unnamedFill = Array.Empty();
foreach (var kvp in args)
{
if (kvp.Value == null)
- unnamedFill.Add(kvp.Key);
+ {
+ Array.Resize(ref unnamedFill, unnamedFill.Length + 1);
+
+ unnamedFill[unnamedFill.Length] = kvp.Key;
+ }
else
_namedArgs[kvp.Key] = kvp.Value;
}
_unnamedArgs = unnamedFill;
- AvailableLength = _unnamedArgs.Count + _namedArgs.Count;
+ AvailableLength = _unnamedArgs.Length + _namedArgs.Count;
}
///
@@ -76,8 +80,8 @@ public ArgumentDictionary(StringComparer? comparer, IEnumerable
public ArgumentDictionary()
{
- _namedArgs = null!;
- _unnamedArgs = null!;
+ _namedArgs = [];
+ _unnamedArgs = [];
AvailableLength = 0;
}
@@ -92,7 +96,7 @@ internal bool TryGetValue(string parameterName, out object? value)
if (_namedArgs.TryGetValue(parameterName, out value!))
return true;
- if (_index >= _unnamedArgs.Count)
+ if (_index >= _unnamedArgs.Length)
return false;
value = _unnamedArgs[_index++];
@@ -106,7 +110,7 @@ internal readonly bool TryGetElementAt(int index, [NotNullWhen(true)] out string
internal readonly bool TryGetElementAt(int index, out string? value)
#endif
{
- if (index < _unnamedArgs.Count)
+ if (index < _unnamedArgs.Length)
{
value = _unnamedArgs[index];
return true;
@@ -141,21 +145,12 @@ internal void SetParseIndex(int index)
#region Initializers
- ///
- public static ArgumentDictionary From(string? input, StringComparer? comparer = null)
- => From(input, [' '], comparer);
-
- ///
- public static ArgumentDictionary From(string[] input, StringComparer? comparer = null)
- {
- if (input.Length == 0)
- return new();
-
- return new(comparer, ReadInternal(input));
- }
+ ///
+ public static ArgumentDictionary FromString(string? input, StringComparer? comparer = null)
+ => FromString(input, [' '], comparer);
///
- /// Reads the provided into an array of command arguments. This method will never throw, always returning a new .
+ /// Reads the provided into an array of command arguments. This operation will never throw, always returning a new .
///
///
/// The implementation is defined by the following rules:
@@ -175,13 +170,13 @@ public static ArgumentDictionary From(string[] input, StringComparer? comparer =
///
///
///
- /// The caller input to parse into a set of arguments.
+ /// The caller's input to parse into a set of arguments.
/// The characters to use as separators when splitting the input.
/// The comparer to use when comparing argument names.
///
- /// An array of arguments that can be used to search for a command or parse into a method.
+ /// An array of arguments that can be used to search for a command or parse into a delegate.
///
- public static ArgumentDictionary From(string? input, char[] separators, StringComparer? comparer = null)
+ public static ArgumentDictionary FromString(string? input, char[] separators, StringComparer? comparer = null)
{
if (string.IsNullOrWhiteSpace(input))
return new();
@@ -191,7 +186,16 @@ public static ArgumentDictionary From(string? input, char[] separators, StringCo
if (split.Length == 0)
return new();
- return new(comparer, ReadInternal(split));
+ return new(ReadInternal(split), comparer);
+ }
+
+ ///
+ public static ArgumentDictionary FromArguments(string[] input, StringComparer? comparer = null)
+ {
+ if (input.Length == 0)
+ return new();
+
+ return new(ReadInternal(input), comparer);
}
private static IEnumerable> ReadInternal(string[] input)
diff --git a/src/Commands/Core/Execution/CommandContext.cs b/src/Commands/Core/Execution/CommandContext.cs
index 3d64b781..2ad21c4c 100644
--- a/src/Commands/Core/Execution/CommandContext.cs
+++ b/src/Commands/Core/Execution/CommandContext.cs
@@ -26,9 +26,9 @@ public class CommandContext(T caller, Command command, CommandOptions options
public Command Command { get; } = command;
///
- /// Gets the that triggered the command, if this command was invoked from one.
+ /// Gets the that triggered the command, if this command was invoked from one.
///
- public ComponentCollection? Manager => Options.Manager;
+ public ComponentProvider? Manager => Options.Manager;
///
/// Sends a response to the caller of the command.
diff --git a/src/Commands/Core/Execution/CommandModule.cs b/src/Commands/Core/Execution/CommandModule.cs
index e7d54531..b3285dcf 100644
--- a/src/Commands/Core/Execution/CommandModule.cs
+++ b/src/Commands/Core/Execution/CommandModule.cs
@@ -40,9 +40,9 @@ public abstract class CommandModule
public Command Command { get; internal set; } = null!;
///
- /// Gets the that invoked this command. This property is if the command was not invoked by a .
+ /// Gets the that invoked this command. This property is if the command was not invoked by a .
///
- public ComponentCollection? Manager { get; internal set; }
+ public ComponentProvider? Manager { get; internal set; }
///
/// Sends a response to the caller.
diff --git a/src/Commands/Core/Execution/CommandOptions.cs b/src/Commands/Core/Execution/CommandOptions.cs
index 8fef0834..4a5b8188 100644
--- a/src/Commands/Core/Execution/CommandOptions.cs
+++ b/src/Commands/Core/Execution/CommandOptions.cs
@@ -9,7 +9,7 @@ namespace Commands;
public sealed class CommandOptions
{
// A reference to the component manager that called the command, if any.
- internal ComponentCollection? Manager;
+ internal ComponentProvider? Manager;
///
/// Gets or sets the services for running the request.
@@ -50,7 +50,7 @@ public sealed class CommandOptions
/// This behavior drastically changes execution flow, and as such, there are a few things to consider when using it:
///
///
- /// The end-user must provide an implementation of to the that is used to execute the command, in order to handle the result of the command.
+ /// The end-user must provide an implementation of to the that is used to execute the command, in order to handle the result of the command.
///
///
/// Objects, specifically those scoped to more than a single command must be made thread-safe, meaning they must be able to handle multiple requests at once.
diff --git a/src/Commands/Core/Execution/ConsoleCallerContext.cs b/src/Commands/Core/Execution/ConsoleCallerContext.cs
index d608629f..3824b2ce 100644
--- a/src/Commands/Core/Execution/ConsoleCallerContext.cs
+++ b/src/Commands/Core/Execution/ConsoleCallerContext.cs
@@ -16,7 +16,7 @@ public class ConsoleCallerContext : ICallerContext
///
/// A raw string which will be parsed into a set of arguments.
public ConsoleCallerContext(string? input)
- => Arguments = ArgumentDictionary.From(input);
+ => Arguments = ArgumentDictionary.FromString(input);
///
/// Creates a new instance of with the specified input.
@@ -26,7 +26,7 @@ public ConsoleCallerContext(string? input)
///
/// The CLI arguments passed to the application upon entry.
public ConsoleCallerContext(string[] input)
- => Arguments = ArgumentDictionary.From(input);
+ => Arguments = ArgumentDictionary.FromArguments(input);
///
/// Sends a response to the console.
@@ -34,7 +34,7 @@ public ConsoleCallerContext(string[] input)
/// The message to send.
public virtual void Respond(object? message)
{
- if (message is IEnumerable enumerable)
+ if (message is IEnumerable enumerable and not string)
{
foreach (var item in enumerable)
Console.WriteLine(item);
diff --git a/src/Commands/Testing/Components/Abstractions/ITest.cs b/src/Commands/Testing/Abstractions/ITest.cs
similarity index 100%
rename from src/Commands/Testing/Components/Abstractions/ITest.cs
rename to src/Commands/Testing/Abstractions/ITest.cs
diff --git a/src/Commands/Testing/Abstractions/ITestProvider.cs b/src/Commands/Testing/Abstractions/ITestProvider.cs
new file mode 100644
index 00000000..636999a2
--- /dev/null
+++ b/src/Commands/Testing/Abstractions/ITestProvider.cs
@@ -0,0 +1,26 @@
+namespace Commands.Testing;
+
+///
+/// Implements a mechanism for testing a .
+///
+public interface ITestProvider : ICollection, IEnumerable
+{
+ ///
+ /// Gets the command which the tests contained in this should be tested against.
+ ///
+ public Command Command { get; }
+
+ ///
+ /// Sequentially tests all available instances aaginst the contained within this type using the provided and options.
+ ///
+ ///
+ /// When specifying the of this operation, the value of is ignored.
+ /// This is because the test execution expects to yield results directly back to the caller, and cannot do this in detached context.
+ ///
+ /// The type of that this test sequence should use to test with.
+ /// A delegate that yields an implementation of based on the input value for every new test.
+ /// A collection of options that determine how every test against this command is ran.
+ /// A containing an with the result of every test yielded by this operation.
+ public ValueTask> Test(Func callerCreation, CommandOptions? options = null)
+ where TContext : class, ICallerContext;
+}
diff --git a/src/Commands/Testing/Components/Abstractions/TestResultType.cs b/src/Commands/Testing/Abstractions/TestResultType.cs
similarity index 100%
rename from src/Commands/Testing/Components/Abstractions/TestResultType.cs
rename to src/Commands/Testing/Abstractions/TestResultType.cs
diff --git a/src/Commands/Testing/Components/Abstractions/ITestCollection.cs b/src/Commands/Testing/Components/Abstractions/ITestCollection.cs
deleted file mode 100644
index 1952f791..00000000
--- a/src/Commands/Testing/Components/Abstractions/ITestCollection.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-namespace Commands.Testing;
-
-///
-///
-///
-///
-public interface ITestCollection : ICollection, IEnumerable
-{
- ///
- ///
- ///
- ///
- ///
- //public int AddRange(IEnumerable items);
-
- ///
- ///
- ///
- ///
- ///
- //public int RemoveRange(IEnumerable items);
-}
diff --git a/src/Commands/Testing/Components/Abstractions/ITestProvider.cs b/src/Commands/Testing/Components/Abstractions/ITestProvider.cs
deleted file mode 100644
index 314d87fb..00000000
--- a/src/Commands/Testing/Components/Abstractions/ITestProvider.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Commands.Testing;
-
-///
-///
-///
-public interface ITestProvider : ITestCollection
-{
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public ValueTask> Execute(Func callerCreation, CommandOptions? options = null)
- where TContext : class, ICallerContext;
-}
diff --git a/src/Commands/Testing/Components/TestCollection.cs b/src/Commands/Testing/Components/TestCollection.cs
deleted file mode 100644
index 343e350c..00000000
--- a/src/Commands/Testing/Components/TestCollection.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-namespace Commands.Testing;
-
-///
-///
-///
-public sealed class TestCollection : ITestProvider
-{
- private TestGroup[] _tests;
-
- ///
- /// Gets the number of groups contained in this collection.
- ///
- public int Count
- => _tests.Length;
-
- ///
- ///
- ///
- ///
- public TestCollection(params TestGroup[] tests)
- {
- _tests = tests;
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public async ValueTask> Execute(Func callerCreation, CommandOptions? options = null)
- where TContext : class, ICallerContext
- {
- options ??= new CommandOptions();
-
- var results = new IEnumerable[_tests.Length];
-
- for (var i = 0; i < _tests.Length; i++)
- results[i] = await _tests[i].Run(callerCreation, options).ConfigureAwait(false);
-
- return results.SelectMany(x => x);
- }
-
- ///
- public void Add(TestGroup item)
- {
- Assert.NotNull(item, nameof(item));
-
- Array.Resize(ref _tests, _tests.Length);
-
- _tests[_tests.Length] = item;
- }
-
- ///
- public void Clear()
- => _tests = [];
-
- ///
- public bool Contains(TestGroup item)
- {
- Assert.NotNull(item, nameof(item));
-
- return _tests.Contains(item);
- }
-
- ///
- public void CopyTo(TestGroup[] array, int arrayIndex)
- => _tests.CopyTo(array, arrayIndex);
-
- ///
- public bool Remove(TestGroup item)
- {
- Assert.NotNull(item, nameof(item));
-
- var indexOf = Array.IndexOf(_tests, item);
-
- if (indexOf == -1)
- return false;
-
- for (var i = indexOf; i < _tests.Length - 1; i++)
- _tests[i] = _tests[i + 1];
-
- Array.Resize(ref _tests, _tests.Length - 1);
-
- return true;
- }
-
- ///
- public IEnumerator GetEnumerator()
- => ((IEnumerable)_tests).GetEnumerator();
-
- bool ICollection.IsReadOnly
- => false;
-
- IEnumerator IEnumerable.GetEnumerator()
- => GetEnumerator();
-}
diff --git a/src/Commands/Testing/Components/TestGroup.cs b/src/Commands/Testing/Components/TestGroup.cs
deleted file mode 100644
index bc8974d9..00000000
--- a/src/Commands/Testing/Components/TestGroup.cs
+++ /dev/null
@@ -1,109 +0,0 @@
-namespace Commands.Testing;
-
-///
-/// Represents a group of implementations to be tested against the that this group targets.
-///
-public sealed class TestGroup : ITestCollection
-{
- private ITest[] _tests;
-
- ///
- /// Gets the command which the tests contained in this should be tested against.
- ///
- public Command Command { get; }
-
- ///
- /// Gets the number of implementations contained in this group.
- ///
- public int Count
- => _tests.Length;
-
- ///
- /// Creates a new targetting the provided command, including the provided tests.
- ///
- ///
- ///
- public TestGroup(Command command, params ITest[] tests)
- {
- Assert.NotNull(command, nameof(command));
- Assert.NotNull(tests, nameof(tests));
-
- Command = command;
-
- _tests = tests;
- }
-
- ///
- ///
- ///
- ///
- ///
- ///
- public async ValueTask> Run(Func callerCreation, CommandOptions options)
- where TContext : class, ICallerContext
- {
- Assert.NotNull(callerCreation, nameof(callerCreation));
- Assert.NotNull(options, nameof(options));
-
- var results = new TestResult[_tests.Length];
-
- for (var i = 0; i < _tests.Length; i++)
- results[i] = await Command.TestAgainst(callerCreation, _tests[i], options).ConfigureAwait(false);
-
- return results;
- }
-
- ///
- public void Add(ITest item)
- {
- Assert.NotNull(item, nameof(item));
-
- Array.Resize(ref _tests, _tests.Length);
-
- _tests[_tests.Length] = item;
- }
-
- ///
- public void Clear()
- => _tests = [];
-
- ///
- public bool Contains(ITest item)
- {
- Assert.NotNull(item, nameof(item));
-
- return _tests.Contains(item);
- }
-
- ///
- public void CopyTo(ITest[] array, int arrayIndex)
- => _tests.CopyTo(array, arrayIndex);
-
- ///
- public bool Remove(ITest item)
- {
- Assert.NotNull(item, nameof(item));
-
- var indexOf = Array.IndexOf(_tests, item);
-
- if (indexOf == -1)
- return false;
-
- for (var i = indexOf; i < _tests.Length - 1; i++)
- _tests[i] = _tests[i + 1];
-
- Array.Resize(ref _tests, _tests.Length - 1);
-
- return true;
- }
-
- ///
- public IEnumerator GetEnumerator()
- => ((IEnumerable)_tests).GetEnumerator();
-
- bool ICollection.IsReadOnly
- => false;
-
- IEnumerator IEnumerable.GetEnumerator()
- => GetEnumerator();
-}
diff --git a/src/Commands/Testing/Execution/TestContext.cs b/src/Commands/Testing/Execution/TestContext.cs
deleted file mode 100644
index f983a03b..00000000
--- a/src/Commands/Testing/Execution/TestContext.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-namespace Commands.Testing;
-
-///
-/// A caller context for testing purposes. When responding, the message is ignored.
-///
-/// The input to use for the context.
-public class TestContext(string? input) : ICallerContext
-{
- ///
- public ArgumentDictionary Arguments { get; } = ArgumentDictionary.From(input);
-
- ///
- public void Respond(object? message) { } // Deliberately emptied.
-}
diff --git a/src/Commands/Testing/Components/Test.cs b/src/Commands/Testing/Test.cs
similarity index 100%
rename from src/Commands/Testing/Components/Test.cs
rename to src/Commands/Testing/Test.cs
diff --git a/src/Commands/Testing/Components/Attributes/TestAttribute.cs b/src/Commands/Testing/TestAttribute.cs
similarity index 92%
rename from src/Commands/Testing/Components/Attributes/TestAttribute.cs
rename to src/Commands/Testing/TestAttribute.cs
index d2d04785..e14a1b04 100644
--- a/src/Commands/Testing/Components/Attributes/TestAttribute.cs
+++ b/src/Commands/Testing/TestAttribute.cs
@@ -1,4 +1,5 @@
-namespace Commands.Testing;
+
+namespace Commands.Testing;
///
/// An attribute that is used to define a test for a command.
diff --git a/src/Commands/Testing/Components/TestProperties.cs b/src/Commands/Testing/TestProperties.cs
similarity index 100%
rename from src/Commands/Testing/Components/TestProperties.cs
rename to src/Commands/Testing/TestProperties.cs
diff --git a/src/Commands/Testing/TestProvider.cs b/src/Commands/Testing/TestProvider.cs
new file mode 100644
index 00000000..25140e43
--- /dev/null
+++ b/src/Commands/Testing/TestProvider.cs
@@ -0,0 +1,131 @@
+namespace Commands.Testing;
+
+///
+/// Represents a provider containing implementations to be tested against the that this group targets.
+///
+[DebuggerDisplay("{ToString()}")]
+public sealed class TestProvider : ITestProvider
+{
+ private ITest[] _tests;
+
+ ///
+ public Command Command { get; }
+
+ ///
+ /// Gets the number of implementations contained in this group.
+ ///
+ public int Count
+ => _tests.Length;
+
+ ///
+ /// Creates a new targetting the provided command, including the provided tests.
+ ///
+ ///
+ /// This constructor adds all implementations of marked on defined execution delegates, alongside the provided .
+ ///
+ /// The command that should be tested against.
+ /// A variable collection of tests this command should be tested with.
+ public TestProvider(Command command, params ITest[] tests)
+ {
+ Assert.NotNull(command, nameof(command));
+ Assert.NotNull(tests, nameof(tests));
+
+ Command = command;
+
+ _tests = [.. tests, .. command.Attributes.OfType()];
+ }
+
+ ///
+ public async ValueTask> Test(Func callerCreation, CommandOptions? options = null)
+ where TContext : class, ICallerContext
+ {
+ Assert.NotNull(callerCreation, nameof(callerCreation));
+
+ // Permit a fast path for empty providers.
+ if (_tests.Length == 0)
+ return [];
+
+ options ??= new CommandOptions();
+
+ var results = new TestResult[_tests.Length];
+
+ for (var i = 0; i < _tests.Length; i++)
+ results[i] = await Command.TestUsing(callerCreation, _tests[i], options).ConfigureAwait(false);
+
+ return results;
+ }
+
+ ///
+ public void Add(ITest item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ Array.Resize(ref _tests, _tests.Length + 1);
+
+ _tests[_tests.Length] = item;
+ }
+
+ ///
+ public void Clear()
+ => _tests = [];
+
+ ///
+ public bool Contains(ITest item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ return _tests.Contains(item);
+ }
+
+ ///
+ public void CopyTo(ITest[] array, int arrayIndex)
+ => _tests.CopyTo(array, arrayIndex);
+
+ ///
+ public bool Remove(ITest item)
+ {
+ Assert.NotNull(item, nameof(item));
+
+ var indexOf = Array.IndexOf(_tests, item);
+
+ if (indexOf == -1)
+ return false;
+
+ for (var i = indexOf; i < _tests.Length - 1; i++)
+ _tests[i] = _tests[i + 1];
+
+ Array.Resize(ref _tests, _tests.Length - 1);
+
+ return true;
+ }
+
+ ///
+ public IEnumerator GetEnumerator()
+ => ((IEnumerable)_tests).GetEnumerator();
+
+ ///
+ public override string ToString()
+ => $"Count = {Count}\nCommand = {Command}";
+
+ #region Internals
+
+ bool ICollection.IsReadOnly
+ => false;
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+
+ #endregion
+
+ #region Initializers
+
+ ///
+ /// Defines a collection of properties from the provided command, to configure and construct a new instance of from.
+ ///
+ /// The command to be represented by the constructed type, being a testable interface to evaluate execution for.
+ /// A new instance of to configure and construct into a new instance of .
+ public static TestProviderProperties From(Command command)
+ => new TestProviderProperties().WithCommand(command);
+
+ #endregion
+}
diff --git a/src/Commands/Testing/TestProviderProperties.cs b/src/Commands/Testing/TestProviderProperties.cs
new file mode 100644
index 00000000..90c71926
--- /dev/null
+++ b/src/Commands/Testing/TestProviderProperties.cs
@@ -0,0 +1,119 @@
+namespace Commands.Testing;
+
+///
+/// Represents a collection of properties with which a new instance of can be constructed.
+///
+public sealed class TestProviderProperties
+{
+ private Command _command;
+ private readonly List _tests;
+
+ ///
+ /// Creates a new instance of to create a from.
+ ///
+ public TestProviderProperties()
+ {
+ _tests = [];
+ _command = null!;
+ }
+
+ ///
+ /// Sets the . Calling this method multiple times replaces the old value with the new one.
+ ///
+ /// The to provide as the target of the these properties create.
+ /// The same for call-chaining.
+ public TestProviderProperties WithCommand(Command command)
+ {
+ Assert.NotNull(command, nameof(command));
+
+ _command = command;
+
+ return this;
+ }
+
+ ///
+ /// Adds an implementation of to the properties.
+ ///
+ /// The test to add.
+ /// The same for call-chaining.
+ public TestProviderProperties AddTest(ITest test)
+ {
+ Assert.NotNull(test, nameof(test));
+
+ _tests.Add(test);
+
+ return this;
+ }
+
+ ///
+ /// Adds the constructed to the properties.
+ ///
+ /// The properties to add.
+ /// The same for call-chaining.
+ public TestProviderProperties AddTest(TestProperties properties)
+ {
+ Assert.NotNull(properties, nameof(properties));
+
+ _tests.Add(properties.ToTest());
+
+ return this;
+ }
+
+ ///
+ /// Creates a new instance of and configures it using the provided action, adding it to the properties.
+ ///
+ /// The configuration for the properties to add.
+ /// The same for call-chaining.
+ public TestProviderProperties AddTest(Action configureProperties)
+ {
+ Assert.NotNull(configureProperties, nameof(configureProperties));
+
+ var properties = new TestProperties();
+
+ configureProperties(properties);
+
+ _tests.Add(properties.ToTest());
+
+ return this;
+ }
+
+ ///
+ /// Adds a collection of implementations to the properties.
+ ///
+ /// The tests to add.
+ /// The same for call-chaining.
+ public TestProviderProperties AddTests(IEnumerable tests)
+ {
+ Assert.NotNull(tests, nameof(tests));
+
+ foreach (var test in tests)
+ AddTest(test);
+
+ return this;
+ }
+
+ ///
+ /// Adds a collection of implementations to the properties.
+ ///
+ /// The tests to add.
+ /// The same for call-chaining.
+ public TestProviderProperties AddTests(params ITest[] tests)
+ {
+ Assert.NotNull(tests, nameof(tests));
+
+ foreach (var test in tests)
+ AddTest(test);
+
+ return this;
+ }
+
+ ///
+ /// Creates a new instance of from the configured properties.
+ ///
+ ///
+ /// This operation adds all defined on defined execution delegates of any command to the tests of the resulting instance.
+ ///
+ /// A new instance of .
+ public TestProvider ToProvider()
+ => new(_command, [.. _tests]);
+}
diff --git a/src/Commands/Testing/Results/TestResult.cs b/src/Commands/Testing/TestResult.cs
similarity index 63%
rename from src/Commands/Testing/Results/TestResult.cs
rename to src/Commands/Testing/TestResult.cs
index aa731af8..cf159b4c 100644
--- a/src/Commands/Testing/Results/TestResult.cs
+++ b/src/Commands/Testing/TestResult.cs
@@ -3,20 +3,21 @@
///
/// A result type which contains the result of a test execution.
///
+[DebuggerDisplay("{ToString()}")]
public readonly struct TestResult : IResult
{
///
- /// The command that was tested.
+ /// Gets the executed test.
///
- public Command Command { get; }
+ public ITest Test { get; }
///
- /// The actual result of the test. If the test succeeded, this will be the same as .
+ /// Gets the actual result of the test. If the test succeeded, this will be the same as .
///
public TestResultType ActualResult { get; }
///
- /// The expected result of the test.
+ /// Gets the expected result of the test.
///
public TestResultType ExpectedResult { get; }
@@ -26,9 +27,9 @@
///
public bool Success { get; }
- private TestResult(Command command, TestResultType expectedResult, TestResultType actualResult, Exception? exception, bool success)
+ private TestResult(ITest test, TestResultType expectedResult, TestResultType actualResult, Exception? exception, bool success)
{
- Command = command;
+ Test = test;
ExpectedResult = expectedResult;
ActualResult = actualResult;
Exception = exception;
@@ -37,7 +38,7 @@ private TestResult(Command command, TestResultType expectedResult, TestResultTyp
///
public override string ToString()
- => $"Command = {Command} \nSuccess = {(Exception == null ? "True" : $"False \nException = {Exception.Message}")}";
+ => $"Test = {Test} \nSuccess = {(Exception == null ? "True" : $"False \nException = {Exception.Message}")}";
///
/// Gets a string representation of this result.
@@ -50,20 +51,20 @@ public string ToString(bool inline)
///
/// Creates a new representing a successful test execution.
///
- /// The command that was tested.
+ /// The test that was tested.
/// The result type of the test execution.
///
- public static TestResult FromSuccess(Command command, TestResultType resultType)
- => new(command, resultType, resultType, null, true);
+ public static TestResult FromSuccess(ITest test, TestResultType resultType)
+ => new(test, resultType, resultType, null, true);
///
/// Creates a new representing a failed test execution.
///
- /// The command that was tested.
+ /// The test that was tested.
/// The expected result of the test.
/// The actual result of the test execution.
/// An exception that might have occurred during test execution.
///
- public static TestResult FromError(Command command, TestResultType expectedResult, TestResultType actualResult, Exception exception)
- => new(command, expectedResult, actualResult, exception, false);
+ public static TestResult FromError(ITest test, TestResultType expectedResult, TestResultType actualResult, Exception exception)
+ => new(test, expectedResult, actualResult, exception, false);
}
diff --git a/src/Commands/Testing/TestUtilities.cs b/src/Commands/Testing/TestUtilities.cs
index a47ecda3..d7d937e6 100644
--- a/src/Commands/Testing/TestUtilities.cs
+++ b/src/Commands/Testing/TestUtilities.cs
@@ -2,16 +2,16 @@
internal static class TestUtilities
{
- public static async ValueTask TestAgainst(this Command command, Func callerCreation, ITest provider, CommandOptions options)
+ public static async ValueTask TestUsing(this Command command, Func callerCreation, ITest test, CommandOptions options)
where TContext : class, ICallerContext
{
TestResult GetResult(IResult result)
{
TestResult CompareReturn(TestResultType targetType, Exception exception)
{
- return provider.ShouldEvaluateTo == targetType
- ? TestResult.FromSuccess(command, provider.ShouldEvaluateTo)
- : TestResult.FromError(command, provider.ShouldEvaluateTo, targetType, exception);
+ return test.ShouldEvaluateTo == targetType
+ ? TestResult.FromSuccess(test, test.ShouldEvaluateTo)
+ : TestResult.FromError(test, test.ShouldEvaluateTo, targetType, exception);
}
return result.Exception switch
@@ -26,9 +26,9 @@ TestResult CompareReturn(TestResultType targetType, Exception exception)
};
}
- var fullName = string.IsNullOrWhiteSpace(provider.Arguments)
+ var fullName = string.IsNullOrWhiteSpace(test.Arguments)
? command.GetFullName(false)
- : command.GetFullName(false) + ' ' + provider.Arguments;
+ : command.GetFullName(false) + ' ' + test.Arguments;
var runResult = await command.Run(callerCreation(fullName), options).ConfigureAwait(false);
From 8647e132984dc8b3f42ee806791d5979033f9406 Mon Sep 17 00:00:00 2001
From: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
Date: Sun, 4 May 2025 16:35:00 +0200
Subject: [PATCH 4/6] A few more minor adjustments to argumentdict
---
src/Commands/Core/Components/Command.cs | 6 +++---
src/Commands/Core/Execution/ArgumentDictionary.cs | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/Commands/Core/Components/Command.cs b/src/Commands/Core/Components/Command.cs
index f8c822e1..d454619b 100644
--- a/src/Commands/Core/Components/Command.cs
+++ b/src/Commands/Core/Components/Command.cs
@@ -167,9 +167,9 @@ public async ValueTask Run(TContext caller, CommandOptions op
object?[] parameters;
- if (!HasParameters && args.AvailableLength == 0)
+ if (!HasParameters && args.RemainingLength == 0)
parameters = [];
- else if (MaxLength == args.AvailableLength || (MaxLength <= args.AvailableLength && HasRemainder) || (MaxLength > args.AvailableLength && MinLength <= args.AvailableLength))
+ else if (MaxLength == args.RemainingLength || (MaxLength <= args.RemainingLength && HasRemainder) || (MaxLength > args.RemainingLength && MinLength <= args.RemainingLength))
{
var arguments = await ComponentUtilities.Parse(this, caller, args, options).ConfigureAwait(false);
@@ -184,7 +184,7 @@ public async ValueTask Run(TContext caller, CommandOptions op
}
}
else
- return ParseResult.FromError(new CommandOutOfRangeException(this, args.AvailableLength));
+ return ParseResult.FromError(new CommandOutOfRangeException(this, args.RemainingLength));
if (!options.SkipConditions)
{
diff --git a/src/Commands/Core/Execution/ArgumentDictionary.cs b/src/Commands/Core/Execution/ArgumentDictionary.cs
index 0fdb321a..719b222d 100644
--- a/src/Commands/Core/Execution/ArgumentDictionary.cs
+++ b/src/Commands/Core/Execution/ArgumentDictionary.cs
@@ -20,7 +20,7 @@ public struct ArgumentDictionary
private readonly string[] _unnamedArgs;
private readonly Dictionary _namedArgs;
- internal int AvailableLength { get; private set; }
+ internal int RemainingLength { get; private set; }
///
/// Gets the number of keys present in the dictionary.
@@ -72,7 +72,7 @@ public ArgumentDictionary(IEnumerable> args, Strin
}
_unnamedArgs = unnamedFill;
- AvailableLength = _unnamedArgs.Length + _namedArgs.Count;
+ RemainingLength = _unnamedArgs.Length + _namedArgs.Count;
}
///
@@ -82,7 +82,7 @@ public ArgumentDictionary()
{
_namedArgs = [];
_unnamedArgs = [];
- AvailableLength = 0;
+ RemainingLength = 0;
}
#region Internals
@@ -138,7 +138,7 @@ internal readonly IEnumerable