-
Notifications
You must be signed in to change notification settings - Fork 157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic creation of Arbitraries on demand #580
Comments
Yes, it should be possible. The trick is to register a completely generic Arbitrary, and delegate to the default I haven't tested this, but something like: type MyArb =
static member MyDerive<'T>() : Arbitrary<'T> =
if CanHandle(typeof<'T>) then
// create an instance of 'Arbitrary<'T>
else
Arb.Default.Derive<'T>()
Arb.register<MyArb>() should be possible to get working. Let me know if you get stuck. |
Great! Works exactly as I needed. Thanks! |
One question though. Is it possible to create an arbitrary using reflection given the Type? Something like this would be helpful: Arb.Default.Derive(Type type) |
I can call the var deriveMethod = typeof(Arb)
.GetMethod(nameof(Arb.Default.Derive))?
.MakeGenericMethod(valueType);
var valueTypeArb = deriveMethod?.Invoke(null, null); But now I would like to call |
Don't think that's currently exposed - do I understand correctly that you don't really want to generate any new types reflectively, but that the types you generate will impose filters/other constraint on existing types like string etc? |
Yes, that was the idea. Though I was thinking about that as a last resort for the types I don't have covered yet. The types I would like to handle automatically are the wrappers around primitive types. This is the way to implement single case unions in C#. Every such type has the three elements:
Something like this: Arb.From(valueType)
.Filter(predicateFunc)
.Generator
.Select(newFunc)
.ToArbitrary(); It works fine for simple scenarios with insignificant limitations like a string of specific size. For the case of a type that represents an MongoDB ObjectId which is strictly a string with 24 hex characters the approach doesn't work. Very tiny chance of fscheck generating those. So I'm now thinking that I would have to abandon that initial idea and implement specific types of predicates like this would be for the ObjectId type: if (default(PRED) is StrObjectId)
{
return Gen.Elements(Enumerable.Range('0', 10).Concat(Enumerable.Range('a', 6)))
.Select(Convert.ToByte)
.ArrayOf(24)
.Select(Encoding.ASCII.GetString)
.Select(NewType<NEWTYPE, string, PRED>.New)
.ToArbitrary();
} I will have it all done in a generic class, so I will know the types while constructing the arbitraries. Then based on the specific types I will construct that helper class using reflection and MakeGeneric. |
I think for now it's easiest to have your own Arbitrary instances implement a type like IArbitrary so you can cast them in a similar way. But I'll think about how to handle this sort of scenario going forward - probably exposing |
Just tried the version 3.0.0-beta1 and couldn't figure out how to implement the scenario with the dynamic arbitraries anymore for the case of nested types. Here is an example: using System;
using FsCheck;
using FsCheck.Fluent;
using FsCheck.Xunit;
namespace ArbMapTests
{
public class PersonId
{
internal PersonId(string value) => Value = value ?? throw new ArgumentNullException(nameof(value));
public string Value { get; }
public static PersonId New(string value) => new(value);
}
public class Person
{
public PersonId PersonId { get; set; }
}
public class MyArbitraries
{
public static Arbitrary<T> GenericArb<T>() where T : class
{
if (typeof(T).Name != "PersonId") return ArbMap.Default.ArbFor<T>();
return ArbMap.Default
.GeneratorFor<StringNoNulls>()
.Select(s => (T)(object) PersonId.New(s.Item))
.ToArbitrary();
}
}
public class ArbMapTests
{
[Property(Arbitrary = new [] {typeof(MyArbitraries)})]
public void PersonTest(Person person)
{
}
}
} I think the problem is in the line when I call |
Yes, it's no longer possible because I hid the I'm considering what to do about this; for the moment I prefer your initial suggestion of allowing dynamic type tests somehow, with a boolean handler, to register these types. Otherwise the self-referential aspects are pretty confusing. |
Thank you. On a related note, when I have MyArbitraries class describing all the domain types, would it be possible to have it provided to all Xunit [Property(Arbitrary = new [] { typeof(MyArbitraries) })]
public void TestCase(Person person)
{
} If it's not supported yet, I will create another issue. Maybe having another attribute for providing it on the class level. |
Yes, check out the |
Perfect! Exactly what I needed. Thank you! |
@dgrozenok do you feel there's an outstanding issue here, or should it be closed? |
I think we're all good here (but documentation....) |
I would like to be able to dynamically generate arbitraries for classes that are not know to FsCheck or not registered yet. Is it possible to register such a class in a similar to Json.NET custom converters manner? I would then have a class with the method
that FsCheck would call when encounters an unknown type. And then I would have to define the two methods for returning the corresponding Arbitrary and Shrinker based on the type parameter.
Having this functionality it would be possible then to register a class that would create generators for LanguageExt
NewType<>
based classes in C#. For example for the class like this:It would be possible to generate the arbitrary based on the generic type parameters as
Now I have to define the arbitraries for every such type, while I think it could be done dynamically.
The text was updated successfully, but these errors were encountered: