Dynamic Assertion Generation
Very early draft on dynamic assertion generation like aluded to in #2315 .
Will update the below text when ready for actual review:
Please check the following before creating a Pull Request
- If this is a new feature or piece of functionality, have you started a discussion and gotten agreement on it?
- If it fixes a bug or problem, is there an issue to track it? If not, create one first and link it please so there's clear visibility.
- Did you write tests to ensure you code works properly?
Currently the styling and conventions are not yet compatible with TUnit yet, this will be changed soon, but I had to close out early today due to irl things.
I've mainly opened up this as a very early PR draft for the following reasons:
- Is the project name and location of
TUnit.Assertions.SourceGeneratorscorrect - Are new external dependencies like
IsExternalInitallowed - Am I free to follow my own design pattern when it comes to the generators, or is there a set pattern I should follow
- Is the project name and location of
TUnit.Assertions.SourceGeneratorscorrect
Works for me!
- Are new external dependencies like
IsExternalInitallowed
I've been using Polyfill library. Bringing that in should suffice.
- Am I free to follow my own design pattern when it comes to the generators, or is there a set pattern I should follow
Go for it. I'll just add comments if I'm not sure about something :smile:
I've been hit with something of a roadblock trying to use Polyfill, when trying to use PolyFill as a package in the generator I get the build errors that it adding polyfill source generated files twice, and when I remove it again I get the following error when trying to run my own generator
CSC: Error CS8785 : Generator 'GenerateAssertionsGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'ArgumentException' with message 'The hintName 'Polyfills.Polyfill.g.cs' of the added source file must be unique within a generator. (Parameter 'hintName')'
I feel I'm missing something in either the setup of my own package with Polyfill or with how my generator is setup all together? (yes I know polyfill is currently not added to the current iteration of the TUnit.Assertions.SourceGenerators but I get errors either way, and searching for answers isnt helping...)
I've not experienced that. Is it set to private assets etc?
Like in here: https://github.com/thomhurst/TUnit/blob/main/TUnit.Core.SourceGenerator/TUnit.Core.SourceGenerator.csproj
The errors have now gone away after I fully reinstalled the repo... C# generators sometimes be a strange beast. There must have gone something wrong during an installation of a package. Sorry for the unnecessary ping
Still needs a lot of lifting to work with something like a span<T>, or basically anything that uses a generic type argument(s), and testing, and proper diagnostic names, better structure, etc.. but it is a start
But I already have the following implemented
- Special types like char, string etc should use the
char.IsDigit(value)format instead of the normalvalue.IsDigit() - Is and IsNot variants
- objects like Uri, which have
Is...propeties are used as properties and not tried as methods
example
[GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsAbsoluteUri))]
// [GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsBaseOf))] // TODO requires extra options
[GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsDefaultPort))]
[GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsFile))]
[GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsLoopback))]
[GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsUnc))]
[GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsWellFormedOriginalString))]
// [GenerateAssertion<Uri>(AssertionType.Is, nameof(Uri.IsWellFormedUriString))] // TODO requires extra options
public static partial class UriIsExtensions {
}
generates =>
// <auto-generated />
using TUnit.Assertions.Extensions;
using TUnit.Assertions.AssertConditions;
using TUnit.Assertions.AssertConditions.Interfaces;
using TUnit.Assertions.AssertionBuilders;
using TUnit.Assertions.AssertionBuilders.Wrappers;
namespace TUnit.Assertions.Assertions.Uris;
public static partial class UriIsExtensions
{
public static InvokableValueAssertionBuilder<Uri> IsAbsoluteUri(this IValueSource<Uri> valueSource)
{
return valueSource.RegisterAssertion(new FuncValueAssertCondition<Uri, int>(0,
(value, _, self) =>
{
if (value is null)
{
self.FailWithMessage("Actual Uri is null");
return false;
}
return value.IsAbsoluteUri;
},
(s, _, _) => "'{s}' was not an AbsoluteUri",
"to be a AbsoluteUri"),
[]
);
}
public static InvokableValueAssertionBuilder<Uri> IsDefaultPort(this IValueSource<Uri> valueSource)
{
return valueSource.RegisterAssertion(new FuncValueAssertCondition<Uri, int>(0,
(value, _, self) =>
{
if (value is null)
{
self.FailWithMessage("Actual Uri is null");
return false;
}
return value.IsDefaultPort;
},
(s, _, _) => "'{s}' was not a DefaultPort",
"to be a DefaultPort"),
[]
);
}
public static InvokableValueAssertionBuilder<Uri> IsFile(this IValueSource<Uri> valueSource)
{
return valueSource.RegisterAssertion(new FuncValueAssertCondition<Uri, int>(0,
(value, _, self) =>
{
if (value is null)
{
self.FailWithMessage("Actual Uri is null");
return false;
}
return value.IsFile;
},
(s, _, _) => "'{s}' was not a File",
"to be a File"),
[]
);
}
public static InvokableValueAssertionBuilder<Uri> IsLoopback(this IValueSource<Uri> valueSource)
{
return valueSource.RegisterAssertion(new FuncValueAssertCondition<Uri, int>(0,
(value, _, self) =>
{
if (value is null)
{
self.FailWithMessage("Actual Uri is null");
return false;
}
return value.IsLoopback;
},
(s, _, _) => "'{s}' was not a Loopback",
"to be a Loopback"),
[]
);
}
public static InvokableValueAssertionBuilder<Uri> IsUnc(this IValueSource<Uri> valueSource)
{
return valueSource.RegisterAssertion(new FuncValueAssertCondition<Uri, int>(0,
(value, _, self) =>
{
if (value is null)
{
self.FailWithMessage("Actual Uri is null");
return false;
}
return value.IsUnc;
},
(s, _, _) => "'{s}' was not an Unc",
"to be a Unc"),
[]
);
}
public static InvokableValueAssertionBuilder<Uri> IsWellFormedOriginalString(this IValueSource<Uri> valueSource)
{
return valueSource.RegisterAssertion(new FuncValueAssertCondition<Uri, int>(0,
(value, _, self) =>
{
if (value is null)
{
self.FailWithMessage("Actual Uri is null");
return false;
}
return value.IsWellFormedOriginalString();
},
(s, _, _) => "'{s}' was not a WellFormedOriginalString",
"to be a WellFormedOriginalString"),
[]
);
}
}
Nice work! Looking good 😄
Sory for the inactivity, have been ill for a week and then there was my own wedding. Back to programming now, and I wanted to ask what the norm is for updating this out of date branch, "update with merge commit" or "update with rebase"?
Hope you're feeling better and congratulations!! :)
I normally just do a merge, I find rebases annoying when conflicts start
@AnnaSasDev do you want a hand with this at all?
@AnnaSasDev do you want a hand with this at all?
Sorry for the inactivity. Had been trying to make methods with more parameters work as well, like the Uri.IsBaseOf(Uri other) but this proved a bit too out of scope for this pr maybe?
Currently trying to do the next things (incomplete list)
- [ ] reformat the code so it adheres to the existing codebase style
- [ ] proper diagnostic reporting on the AssertionHolder (partial etc...)
- [ ] correct naming of the diagnostics already present, and checking if more are required
- [ ] testing of the newly created methods, to ensure the logical output on positive and negative branches as those which they are based upon.
I dont mind the help, especially when it comes to diagnostics. In my own projects I have limited experience with them as I dont have such a userbase as TUnit, by very very far.
Something else I noticed is that the editorconfig file is rather unpopulated? Maybe having a built out editorconfig file might help with the coding style?
This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 10 days.
This PR was closed because it has been stalled for 10 days with no activity.