Skip to content

Instantly share code, notes, and snippets.

@michael-wolfenden
Last active November 2, 2018 07:00
Show Gist options
  • Save michael-wolfenden/f224dcf829416427a9b8260f7bd498c4 to your computer and use it in GitHub Desktop.
Save michael-wolfenden/f224dcf829416427a9b8260f7bd498c4 to your computer and use it in GitHub Desktop.
FsCheck Generic Rules Test
/*
<Query Kind="Program">
<NuGetReference>FsCheck.Xunit</NuGetReference>
<NuGetReference>TypeShape</NuGetReference>
<NuGetReference>xunit</NuGetReference>
<NuGetReference>xunit.runner.utility</NuGetReference>
<Namespace>FsCheck</Namespace>
<Namespace>FsCheck.Xunit</Namespace>
<Namespace>TypeShape</Namespace>
<Namespace>TypeShape.Core</Namespace>
<Namespace>Xunit</Namespace>
<Namespace>Xunit.Runners</Namespace>
<Namespace>System.Runtime.Serialization</Namespace>
<CopyLocal>true</CopyLocal>
</Query>
*/
void Main()
{
// https://github.com/louthy/language-ext/tree/master/LanguageExt.Core/ClassInstances/Eq
// https://github.com/fantasyland/fantasy-land#setoid
XUnitRunner.Run(Assembly.GetExecutingAssembly());
}
public class Tests {
[Fact]
public void Reflexivity()
{
SetoidTester.VerifyPredicate(new ReflexivityPredicate());
}
[Fact]
public void Symmetry()
{
SetoidTester.VerifyPredicate(new SymmetryPredicate());
}
}
private class ReflexivityPredicate : SetoidTester.IPredicate
{
public bool Invoke<SetoidA, A>(SetoidA setoidA, A x) where SetoidA : Setoid<A> =>
setoidA.Equals(x, x);
}
private class SymmetryPredicate : SetoidTester.IPredicate2
{
public bool Invoke<SetoidA, A>(SetoidA setoidA, A x, A y) where SetoidA : Setoid<A> =>
setoidA.Equals(x, y) ? setoidA.Equals(y, x) : true;
}
public static class SetoidTester
{
private static readonly IDictionary<Type, Type> _setoidMap = new Dictionary<Type, Type>
{
[typeof(string)] = typeof(SetoidString),
[typeof(int)] = typeof(SetoidInt),
[typeof(string[])] = typeof(SetoidArray<SetoidString, string>)
};
private static void Verify(ITest test) =>
_setoidMap
.Select(pair => (TypeOfA: pair.Key, TypeOfSetoidOfA: pair.Value))
.Select(pair => Core.TypeShape.Create(pair.TypeOfA).Accept(new Visitor(test, pair.TypeOfSetoidOfA)))
.ToList();
public static void VerifyPredicate(IPredicate predicate) =>
Verify(new PredicateTest(predicate));
public static void VerifyPredicate(IPredicate2 predicate) =>
Verify(new Predicate2Test(predicate));
private class Visitor : Core.ITypeShapeVisitor<bool>
{
private ITest _test;
private Type _typeOfSetoidOfA;
public Visitor(ITest test, Type typeOfSetoidOfA) => (_test, _typeOfSetoidOfA) = (test, typeOfSetoidOfA);
public bool Visit<A>() => _test.Invoke<A>(_typeOfSetoidOfA);
}
public interface ITest
{
bool Invoke<A>(Type typeOfSetoidOfA);
}
public class PredicateTest : ITest
{
private IPredicate _predicate;
public PredicateTest(IPredicate predicate) => (_predicate) = (predicate);
public bool Invoke<A>(Type typeOfSetoidOfA)
{
var setoidInstance = CreateSetoidInstance<A>(typeOfSetoidOfA);
Prop.ForAll(
Arb.From<A>().Generator.Or(Gen.Constant(default(A))).ToArbitrary(),
a => _predicate.Invoke(setoidInstance, a)
)
.QuickCheckThrowOnFailure();
return true;
}
}
public class Predicate2Test : ITest
{
private IPredicate2 _predicate;
public Predicate2Test(IPredicate2 predicate) => (_predicate) = (predicate);
public bool Invoke<A>(Type typeOfSetoidOfA)
{
var setoidInstance = CreateSetoidInstance<A>(typeOfSetoidOfA);
Prop.ForAll(
Arb.From<A>().Generator.Or(Gen.Constant(default(A))).ToArbitrary(),
Arb.From<A>().Generator.Or(Gen.Constant(default(A))).ToArbitrary(),
(a, b) => _predicate.Invoke(setoidInstance, a, b)
)
.QuickCheckThrowOnFailure();
return true;
}
}
public interface IPredicate
{
bool Invoke<SetoidA, A>(SetoidA setoidA, A a) where SetoidA: Setoid<A>;
}
public interface IPredicate2
{
bool Invoke<SetoidA, A>(SetoidA setoidA, A a, A b) where SetoidA : Setoid<A>;
}
public static Setoid<A> CreateSetoidInstance<A>(Type typeOfSetoidOfA) =>
(Setoid<A>) FormatterServices.GetUninitializedObject(typeOfSetoidOfA);
}
public interface Setoid<A>
{
bool Equals(A x, A y);
}
public struct SetoidString : Setoid<string>
{
public bool Equals(string x, string y) => x == y;
}
public struct SetoidInt : Setoid<int>
{
public bool Equals(int a, int b) => a == b;
}
public struct SetoidArray<SetoidA, A> : Setoid<A[]> where SetoidA : struct, Setoid<A>
{
public bool Equals(A[] x, A[] y)
{
if (x == null) return y == null;
if (y == null) return false;
if (x.Length != y.Length) return false;
for (int i = 0; i < x.Length; i++)
{
if (!default(SetoidA).Equals(x[i], y[i])) return false;
}
return true;
}
}
public struct SetoidTuple<SetoidA, A, SetoidB, B> : Setoid<(A, B)>
where SetoidA : struct, Setoid<A>
where SetoidB : struct, Setoid<B>
{
public bool Equals((A, B) x, (A, B) y) =>
default(SetoidA).Equals(x.Item1, y.Item1) &&
default(SetoidB).Equals(x.Item2, y.Item2);
}
// see: https://github.com/xunit/samples.xunit/blob/1173213302fd3a45bd6e4303371d2f78f1743552/TestRunner/Program.cs
public class XUnitRunner
{
private Assembly _testAssembly;
private object _consoleLock = new object();
private ManualResetEvent finished = new ManualResetEvent(false);
private int _result = 0;
private XUnitRunner(Assembly testAssembly) =>
_testAssembly = testAssembly ?? throw new ArgumentNullException(nameof(testAssembly));
public static int Run(Assembly testAssembly) =>
new XUnitRunner(testAssembly).Run();
private int Run()
{
var newTestAssemblyLocation = CopyTestAssemblyToTheSameFolderAsTheXUnitAssemblies();
using (var runner = AssemblyRunner.WithoutAppDomain(newTestAssemblyLocation))
{
runner.OnDiscoveryComplete = OnDiscoveryComplete;
runner.OnExecutionComplete = OnExecutionComplete;
runner.OnTestFailed = OnTestFailed;
runner.OnTestSkipped = OnTestSkipped;
Console.WriteLine("Discovering...");
runner.Start();
finished.WaitOne();
finished.Dispose();
return _result;
}
}
private string CopyTestAssemblyToTheSameFolderAsTheXUnitAssemblies()
{
var testAssemblyFolder = new DirectoryInfo(Path.GetDirectoryName(_testAssembly.Location));
var xunitFolder = new DirectoryInfo(Path.GetDirectoryName(typeof(Xunit.Assert).Assembly.Location));
var isXunitInASubDirectory = xunitFolder.Parent.FullName == testAssemblyFolder.FullName;
if (!isXunitInASubDirectory)
throw new InvalidOperationException("Please enable 'Copy all non-framework references to a single local folder' (F4 -> Advanced).");
var newTestAssemblyPath = Path.Combine(xunitFolder.FullName, Path.GetFileName(_testAssembly.Location));
File.Copy(_testAssembly.Location, newTestAssemblyPath, overwrite: true);
return newTestAssemblyPath;
}
private void OnDiscoveryComplete(DiscoveryCompleteInfo info)
{
lock (_consoleLock)
Console.WriteLine($"Running {info.TestCasesToRun} of {info.TestCasesDiscovered} tests...");
}
private void OnExecutionComplete(ExecutionCompleteInfo info)
{
lock (_consoleLock)
Console.WriteLine($"Finished: {info.TotalTests} tests in {Math.Round(info.ExecutionTime, 3)}s ({info.TestsFailed} failed, {info.TestsSkipped} skipped)");
finished.Set();
}
private void OnTestFailed(TestFailedInfo info)
{
lock (_consoleLock)
{
Console.WriteLine("[FAIL] {0}: {1}", info.TestDisplayName, info.ExceptionMessage);
if (info.ExceptionStackTrace != null)
Console.WriteLine(info.ExceptionStackTrace);
}
_result = 1;
}
private void OnTestSkipped(TestSkippedInfo info)
{
lock (_consoleLock)
{
Console.WriteLine("[SKIP] {0}: {1}", info.TestDisplayName, info.SkipReason);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment