Last active
November 2, 2018 07:00
-
-
Save michael-wolfenden/f224dcf829416427a9b8260f7bd498c4 to your computer and use it in GitHub Desktop.
FsCheck Generic Rules Test
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
<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