Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: fscheck/FsCheck
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3.0.1
Choose a base ref
...
head repository: fscheck/FsCheck
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
  • 6 commits
  • 12 files changed
  • 3 contributors

Commits on Jan 25, 2025

  1. Improving doc comment

    AT-WH authored and kurtschelfthout committed Jan 25, 2025
    Copy the full SHA
    dcc9ec2 View commit details
  2. Support Async<'Testable> & Task<'Testable> (#694)

    Support async & task as first-class testables
    brianrourkeboll authored Jan 25, 2025
    Copy the full SHA
    b88b0b9 View commit details

Commits on Jan 30, 2025

  1. Add Release target.

    kurtschelfthout committed Jan 30, 2025
    Copy the full SHA
    b463b78 View commit details
  2. Update release notes.

    kurtschelfthout committed Jan 30, 2025
    Copy the full SHA
    0d62898 View commit details
  3. Ship it

    kurtschelfthout committed Jan 30, 2025
    Copy the full SHA
    6fbc1f9 View commit details
  4. Bump version to 3.1.0

    kurtschelfthout committed Jan 30, 2025
    Copy the full SHA
    7499215 View commit details
4 changes: 4 additions & 0 deletions FsCheck Release Notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
### 3.1.0 - 30 January 2025

* Support Async<'Testable> & Task<'Testable>. Slight behavior change: an explicit upcast to non-generic `Task` (`:> Task`) is now needed for tests involving `Task<'T>` where `'T` is not itself a testable type. (by Brian Rourke Boll)

### 3.0.1 - 22 January 2025

* FsCheck.Xunit: target net462 explicitly, so xunit picks the right runner on .NET Framework.
8 changes: 7 additions & 1 deletion build.fsx
Original file line number Diff line number Diff line change
@@ -600,6 +600,12 @@ match args |> List.map (fun s -> s.ToLowerInvariant ()) with
let haveTested = runDotnetTest haveCleaned
let havePacked = packNuGet haveTested
pushNuGet haveTested havePacked |> ignore<HavePushed>
| ["-t"; "release"] ->
let haveCleaned = doClean ()
let haveTested = runDotnetTest haveCleaned
let havePacked = packNuGet haveTested
pushNuGet haveTested havePacked |> ignore
gitHubRelease haveTested |> ignore
| ["-t"; "buildversion"] ->
let haveCleaned = doClean ()
appveyorBuildVersion haveCleaned |> ignore<HaveUpdatedBuildVersion>
@@ -610,4 +616,4 @@ match args |> List.map (fun s -> s.ToLowerInvariant ()) with
let args =
args
|> String.concat " "
failwith $"Unrecognised arguments. Supply '-t [RunTests,Clean,CI,Build,Tests,GHRelease,Docs,WatchDocs,ReleaseDocs,NugetPack,NugetPush,BuildVersion,AssemblyInfo]'. Unrecognised args were: %s{args}"
failwith $"Unrecognised arguments. Supply '-t [RunTests,Clean,CI,Build,Tests,GHRelease,Docs,WatchDocs,ReleaseDocs,NugetPack,NugetPush,BuildVersion,AssemblyInfo,Release]'. Unrecognised args were: %s{args}"
2 changes: 1 addition & 1 deletion examples/FsCheck.Examples/Examples.fs
Original file line number Diff line number Diff line change
@@ -194,7 +194,7 @@ Check.One(bigSize,fun (s:Simple) -> match s with Leaf2 _ -> false | Void3 -> fal

Check.One(bigSize,fun i -> (-10 < i && i < 0) || (0 < i) && (i < 10 ))
Check.Quick (fun opt -> match opt with None -> false | Some b -> b )
Check.Quick (fun opt -> match opt with Some n when n<0 -> false | Some n when n >= 0 -> true | _ -> true )
Check.Quick (fun opt -> match opt with Some n when n < 0 -> false | Some n when n >= 0 -> true | _ -> true )

let prop_RevId' (xs:list<int>) (x:int) = if (xs.Length > 2) && (x >10) then false else true
Check.Quick prop_RevId'
56 changes: 56 additions & 0 deletions examples/FsCheck.NUnit.CSharpExamples/SyncVersusAsyncExamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Threading.Tasks;
using FsCheck.Fluent;
using NUnit.Framework;

namespace FsCheck.NUnit.CSharpExamples;

public class SyncVersusAsyncExamples
{
[Property]
public Property Property_ShouldPass(bool b)
{
return (b ^ !b).Label("b ^ !b");
}

[Property]
public Property Property_ShouldFail(bool b)
{
return (b && !b).Label("b && !b");
}

[Property]
public async Task Task_ShouldPass(bool b)
{
await DoSomethingAsync();
Assert.That(b ^ !b);
}

[Property]
public async Task Task_Exception_ShouldFail(bool b)
{
await DoSomethingAsync();
Assert.That(b && !b);
}

[Property]
public async Task Task_Cancelled_ShouldFail(bool b)
{
await Task.Run(() => Assert.That(b ^ !b), new System.Threading.CancellationToken(canceled: true));
}

[Property]
public async Task<Property> TaskProperty_ShouldPass(bool b)
{
await DoSomethingAsync();
return (b ^ !b).Label("b ^ !b");
}

[Property]
public async Task<Property> TaskProperty_ShouldFail(bool b)
{
await DoSomethingAsync();
return (b && !b).Label("b && !b");
}

private static async Task DoSomethingAsync() => await Task.Yield();
}
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
<ItemGroup>
<Content Include="App.config" />
<Compile Include="PropertyExamples.fs" />
<Compile Include="SyncVersusAsyncExamples.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" />
29 changes: 29 additions & 0 deletions examples/FsCheck.NUnit.Examples/SyncVersusAsyncExamples.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace FsCheck.NUnit.Examples

open FsCheck.FSharp
open FsCheck.NUnit

module SyncVersusAsyncExamples =
let private doSomethingAsync () = async { return () }

[<Property>]
let ``Sync - should pass`` b =
b = b |> Prop.label "b = b"

[<Property>]
let ``Sync - should fail`` b =
b = not b |> Prop.label "b = not b"

[<Property>]
let ``Async - should pass`` b =
async {
do! doSomethingAsync ()
return b = b |> Prop.label "b = b"
}

[<Property>]
let ``Async - should fail`` b =
async {
do! doSomethingAsync ()
return b = not b |> Prop.label "b = not b"
}
57 changes: 57 additions & 0 deletions examples/FsCheck.XUnit.CSharpExamples/SyncVersusAsyncExamples.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Threading.Tasks;
using FsCheck.Fluent;
using FsCheck.Xunit;
using Xunit;

namespace FsCheck.XUnit.CSharpExamples;

public class SyncVersusAsyncExamples
{
[Property]
public Property Property_ShouldPass(bool b)
{
return (b ^ !b).Label("b ^ !b");
}

[Property]
public Property Property_ShouldFail(bool b)
{
return (b && !b).Label("b && !b");
}

[Property]
public async Task Task_ShouldPass(bool b)
{
await DoSomethingAsync();
Assert.True(b ^ !b);
}

[Property]
public async Task Task_Exception_ShouldFail(bool b)
{
await DoSomethingAsync();
Assert.True(b && !b);
}

[Property]
public async Task Task_Cancelled_ShouldFail(bool b)
{
await Task.Run(() => Assert.True(b ^ !b), new System.Threading.CancellationToken(canceled: true));
}

[Property]
public async Task<Property> TaskProperty_ShouldPass(bool b)
{
await DoSomethingAsync();
return (b ^ !b).Label("b ^ !b");
}

[Property]
public async Task<Property> TaskProperty_ShouldFail(bool b)
{
await DoSomethingAsync();
return (b && !b).Label("b && !b");
}

private static async Task DoSomethingAsync() => await Task.Yield();
}
1 change: 1 addition & 0 deletions src/FsCheck.Xunit/PropertyAttribute.fs
Original file line number Diff line number Diff line change
@@ -129,6 +129,7 @@ type public PropertyAttribute() =

///If set, the seed to use to start testing. Allows reproduction of previous runs. You can just paste
///the tuple from the output window, e.g. 12344,12312 or (123,123).
///Additionally, you can also specify a start size as the third parameter, e.g. 12344,12312,10 or (123,123,10).
member __.Replay with get() = replay and set(v) = replay <- v; config <- {config with Replay = if String.IsNullOrEmpty v then None else Some v}
///If set, run tests in parallel. Useful for Task/async related work and heavy number crunching
///Environment.ProcessorCount have been found to be useful default.
5 changes: 2 additions & 3 deletions src/FsCheck/FSharp.Prop.fs
Original file line number Diff line number Diff line change
@@ -67,9 +67,8 @@ module Prop =
{ r with Labels = Set.add l r.Labels }) |> Future
Prop.mapResult add

/// Turns a testable type into a property. Testables are unit, boolean, Lazy testables, Gen testables, functions
/// from a type for which a generator is know to a testable, tuples up to 6 tuple containing testables, and lists
/// containing testables.
/// Turns a testable type into a property. Testables are unit, Boolean, Lazy testables, Gen testables,
/// Async testables, Task testables, and functions from a type for which a generator is known to a testable.
[<CompiledName("OfTestable")>]
let ofTestable (testable:'Testable) =
property testable
45 changes: 25 additions & 20 deletions src/FsCheck/Testable.fs
Original file line number Diff line number Diff line change
@@ -46,7 +46,7 @@ type ResultContainer =
match (l,r) with
| (Value vl,Value vr) -> f (vl, vr) |> Value
| (Future tl,Value vr) -> tl.ContinueWith (fun (x :Task<Result>) -> f (x.Result, vr)) |> Future
| (Value vl,Future tr) -> tr.ContinueWith (fun (x :Task<Result>) -> f (x.Result, vl)) |> Future
| (Value vl,Future tr) -> tr.ContinueWith (fun (x :Task<Result>) -> f (vl, x.Result)) |> Future
| (Future tl,Future tr) -> tl.ContinueWith (fun (x :Task<Result>) ->
tr.ContinueWith (fun (y :Task<Result>) -> f (x.Result, y.Result))) |> TaskExtensions.Unwrap |> Future
static member (&&&) (l,r) = ResultContainer.MapResult2(Result.ResAnd, l, r)
@@ -113,15 +113,6 @@ module private Testable =
|> Value
|> ofResult

let ofTaskBool (b:Task<bool>) :Property =
b.ContinueWith (fun (x:Task<bool>) ->
match (x.IsCanceled, x.IsFaulted) with
| (false,false) -> Res.ofBool x.Result
| (_,true) -> Res.failedException x.Exception
| (true,_) -> Res.failedCancelled)
|> Future
|> ofResult

let ofTask (b:Task) :Property =
b.ContinueWith (fun (x:Task) ->
match (x.IsCanceled, x.IsFaulted) with
@@ -131,6 +122,26 @@ module private Testable =
|> Future
|> ofResult

let ofTaskGeneric (t : Task<'T>) : Property =
Property (fun arbMap ->
Gen.promote (fun runner ->
Shrink.ofValue (Future (
t.ContinueWith (fun (t : Task<'T>) ->
match t.IsCanceled, t.IsFaulted with
| _, true -> Task.FromResult (Res.failedException t.Exception)
| true, _ -> Task.FromResult Res.failedCancelled
| false, false ->
let prop = property t.Result
let gen = Property.GetGen arbMap prop
let shrink = runner gen

let value, shrinks = Shrink.getValue shrink
assert Seq.isEmpty shrinks
match value with
| Value result -> Task.FromResult result
| Future resultTask -> resultTask)
|> _.Unwrap()))))

let mapShrinkResult (f:Shrink<ResultContainer> -> _) a =
fun arbMap ->
property a
@@ -194,21 +205,15 @@ module private Testable =
static member Bool() =
{ new ITestable<bool> with
member __.Property b = Prop.ofBool b }
static member TaskBool() =
{ new ITestable<Task<bool>> with
member __.Property b = Prop.ofTaskBool b }
static member Task() =
{ new ITestable<Task> with
member __.Property b = Prop.ofTask b }
static member TaskGeneric() =
{ new ITestable<Task<'T>> with
member __.Property b = Prop.ofTask (b :> Task) }
static member AsyncBool() =
{ new ITestable<Async<bool>> with
member __.Property b = Prop.ofTaskBool <| Async.StartAsTask b }
static member Async() =
{ new ITestable<Async<unit>> with
member __.Property b = Prop.ofTask <| Async.StartAsTask b }
member __.Property t = Prop.ofTaskGeneric t }
static member AsyncGeneric() =
{ new ITestable<Async<'T>> with
member __.Property a = Prop.ofTaskGeneric <| Async.StartAsTask a }
static member Lazy() =
{ new ITestable<Lazy<'T>> with
member __.Property b =
Loading