Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions tests/FSharp.Collections.Immutable.Tests/Assert.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/// <summary>
/// Assertions on F# <see cref="T:Microsoft.FSharp.Core.FSharpOption`1"/>,
/// <see cref="T:Microsoft.FSharp.Core.FSharpValueOption`1"/> and <see cref="T:Microsoft.FSharp.Core.FSharpResult`2"/>
/// values as extensions of <see cref="T:Microsoft.VisualStudio.TestTools.UnitTesting.Assert"/>.
/// <para>
/// The assertions whose names start with <c>Want</c> return the unwrapped value or fail the test; their <c>Is</c>
/// twins discard the value.
/// </para>
/// </summary>
[<AutoOpen>]
module FSharp.Collections.Immutable.Tests.AssertExtensions

open System.Runtime.InteropServices
open Microsoft.VisualStudio.TestTools.UnitTesting

// MSTest takes the message of a failure as a text that is not null, while an optional message that the caller
// omitted is null.
let private messageOrEmpty (message : string | null) : string =
match message with
| null -> ""
| message -> message

type Assert with

/// <summary>
/// Returns the value of <see cref="T:Microsoft.FSharp.Core.FSharpOption`1.Some"/>; fails the test with
/// <paramref name="message"/> on <see cref="T:Microsoft.FSharp.Core.FSharpOption`1.None"/>.
/// </summary>
static member WantSome (value, [<Optional>] message : string | null) =
match value with
| Some some -> some
| None ->
Assert.Fail (messageOrEmpty message)
Unchecked.defaultof<_>

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="value"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpOption`1.Some"/>.
/// </summary>
static member IsSome (value, [<Optional>] message : string | null) = Assert.WantSome (value, message) |> ignore

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="value"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpOption`1.None"/>.
/// </summary>
static member IsNone (value, [<Optional>] message : string | null) =
match value with
| Some _ -> Assert.Fail (messageOrEmpty message)
| None -> ()

/// <summary>
/// Returns the value of <see cref="T:Microsoft.FSharp.Core.FSharpValueOption`1.ValueSome"/>; fails the test
/// with <paramref name="message"/> on <see cref="T:Microsoft.FSharp.Core.FSharpValueOption`1.ValueNone"/>.
/// </summary>
static member WantValueSome (value, [<Optional>] message : string | null) =
match value with
| ValueSome some -> some
| ValueNone ->
Assert.Fail (messageOrEmpty message)
Unchecked.defaultof<_>

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="value"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpValueOption`1.ValueSome"/>.
/// </summary>
static member IsValueSome (value, [<Optional>] message : string | null) = Assert.WantValueSome (value, message) |> ignore

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="value"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpValueOption`1.ValueNone"/>.
/// </summary>
static member IsValueNone (value, [<Optional>] message : string | null) =
match value with
| ValueSome _ -> Assert.Fail (messageOrEmpty message)
| ValueNone -> ()

/// <summary>
/// Returns the value of <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Ok"/>; fails the test with
/// <paramref name="message"/> and the error on <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Error"/>.
/// </summary>
static member WantOk (value, [<Optional>] message : string | null) =
match value with
| Ok ok -> ok
| Error error ->
match message with
| null -> Assert.Fail (string error)
| message -> Assert.Fail ($"'{message}': {error}")
Unchecked.defaultof<_>

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="value"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Ok"/>.
/// </summary>
static member IsOk (value, [<Optional>] message : string | null) = Assert.WantOk (value, message) |> ignore

/// <summary>
/// Returns the error of <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Error"/>; fails the test with
/// <paramref name="message"/> and the value on <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Ok"/>.
/// </summary>
static member WantError (value, [<Optional>] message : string | null) =
match value with
| Error error -> error
| Ok value ->
match message with
| null -> Assert.Fail (string value)
| message -> Assert.Fail ($"'{message}': {value}")
Unchecked.defaultof<_>

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="value"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Error"/>.
/// </summary>
static member IsError (value, [<Optional>] message : string | null) = Assert.WantError (value, message) |> ignore

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="value"/> is the default value of its
/// type.
/// </summary>
static member inline IsDefaultOf< ^T> (value : ^T, [<Optional>] message : string) =
Assert.AreEqual (box value, box Unchecked.defaultof< ^T>, message)

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="actual"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Ok"/> holding <paramref name="expected"/>.
/// </summary>
static member inline OkEquals< ^R, 'E> (expected : ^R, actual : Result< ^R, 'E >, [<Optional>] message : string | null) =
Assert.AreEqual (box expected, box (Assert.WantOk (actual, message)), message)

/// <summary>
/// Fails the test with <paramref name="message"/> unless <paramref name="actual"/> is
/// <see cref="T:Microsoft.FSharp.Core.FSharpResult`2.Error"/> holding <paramref name="expected"/>.
/// </summary>
static member inline ErrorEquals<'R, ^E> (expected : ^E, actual : Result<'R, ^E>, [<Optional>] message : string | null) =
Assert.AreEqual (box expected, box (Assert.WantError (actual, message)), message)
156 changes: 156 additions & 0 deletions tests/FSharp.Collections.Immutable.Tests/AssertExtensionsTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
namespace FSharp.Collections.Immutable.Tests

open System
open Microsoft.VisualStudio.TestTools.UnitTesting

/// <summary>
/// The assertions on options, value options and results that extend
/// <see cref="T:Microsoft.VisualStudio.TestTools.UnitTesting.Assert"/>.
/// </summary>
[<TestClass; AssertExtensionsTestCategory>]
type AssertExtensionsTests () =

/// The message of the failure that an assertion raises.
static let failureOf (assertion : unit -> unit) : string =
let failure = Assert.Throws<AssertFailedException>(Action assertion, "The assertion must fail")
failure.Message

static let assertContains (failure : string) (expected : string) (message : string) =
StringAssert.Contains (failure, expected, StringComparison.Ordinal, message)

// The empty option and value option of a known type: a type annotation cannot sit in a tuple of arguments.
static let noInt : int option = None
static let noValueInt : int voption = ValueNone

// ---- option ----

[<TestMethod>]
member _.``WantSome returns the value of Some`` () =
Assert.AreEqual<int>(5, Assert.WantSome (Some 5, "message"), "WantSome must return the value of Some")

[<TestMethod>]
member _.``WantSome fails with the message on None`` () =
let failure = failureOf (fun () -> Assert.WantSome (noInt, "the value is missing") |> ignore)
assertContains failure "the value is missing" "WantSome must fail with its message on None"

[<TestMethod>]
member _.``IsSome passes for Some and fails for None`` () =
Assert.IsSome (Some 5, "message")
let failure = failureOf (fun () -> Assert.IsSome (noInt, "the value is missing"))
assertContains failure "the value is missing" "IsSome must fail with its message on None"

[<TestMethod>]
member _.``IsNone passes for None and fails for Some`` () =
Assert.IsNone (noInt, "message")
let failure = failureOf (fun () -> Assert.IsNone (Some 5, "the value is there"))
assertContains failure "the value is there" "IsNone must fail with its message on Some"

// ---- voption ----

[<TestMethod>]
member _.``WantValueSome returns the value of ValueSome`` () =
Assert.AreEqual<int>(5, Assert.WantValueSome (ValueSome 5, "message"), "WantValueSome must return the value")

[<TestMethod>]
member _.``WantValueSome fails with the message on ValueNone`` () =
let failure =
failureOf (fun () ->
Assert.WantValueSome (noValueInt, "the value is missing")
|> ignore
)

assertContains failure "the value is missing" "WantValueSome must fail with its message on ValueNone"

[<TestMethod>]
member _.``IsValueSome passes for ValueSome and fails for ValueNone`` () =
Assert.IsValueSome (ValueSome 5, "message")
let failure = failureOf (fun () -> Assert.IsValueSome (noValueInt, "the value is missing"))
assertContains failure "the value is missing" "IsValueSome must fail with its message on ValueNone"

[<TestMethod>]
member _.``IsValueNone passes for ValueNone and fails for ValueSome`` () =
Assert.IsValueNone (noValueInt, "message")
let failure = failureOf (fun () -> Assert.IsValueNone (ValueSome 5, "the value is there"))
assertContains failure "the value is there" "IsValueNone must fail with its message on ValueSome"

// ---- result ----

[<TestMethod>]
member _.``WantOk returns the value of Ok`` () =
let result : Result<int, string> = Ok 5
Assert.AreEqual<int>(5, Assert.WantOk (result, "message"), "WantOk must return the value of Ok")

[<TestMethod>]
member _.``WantOk fails with the message and the error on Error`` () =
let result : Result<int, string> = Error "the error"
let failure = failureOf (fun () -> Assert.WantOk (result, "the call must succeed") |> ignore)
assertContains failure "the call must succeed" "WantOk must fail with its message on Error"
assertContains failure "the error" "WantOk must fail with the error"

[<TestMethod>]
member _.``WantOk fails with the error alone when it has no message`` () =
let result : Result<int, string> = Error "the error"
let failure = failureOf (fun () -> Assert.WantOk (result) |> ignore)
assertContains failure "the error" "WantOk must fail with the error"

[<TestMethod>]
member _.``IsOk passes for Ok and fails for Error`` () =
let ok : Result<int, string> = Ok 5
let error : Result<int, string> = Error "the error"
Assert.IsOk (ok, "message")
let failure = failureOf (fun () -> Assert.IsOk (error, "the call must succeed"))
assertContains failure "the call must succeed" "IsOk must fail with its message on Error"

[<TestMethod>]
member _.``WantError returns the error of Error`` () =
let result : Result<int, string> = Error "the error"
Assert.AreEqual<string>("the error", Assert.WantError (result, "message"), "WantError must return the error")

[<TestMethod>]
member _.``WantError fails with the message and the value on Ok`` () =
let result : Result<int, string> = Ok 5
let failure = failureOf (fun () -> Assert.WantError (result, "the call must fail") |> ignore)
assertContains failure "the call must fail" "WantError must fail with its message on Ok"
assertContains failure "5" "WantError must fail with the value"

[<TestMethod>]
member _.``IsError passes for Error and fails for Ok`` () =
let ok : Result<int, string> = Ok 5
let error : Result<int, string> = Error "the error"
Assert.IsError (error, "message")
let failure = failureOf (fun () -> Assert.IsError (ok, "the call must fail"))
assertContains failure "the call must fail" "IsError must fail with its message on Ok"

[<TestMethod>]
member _.``OkEquals passes for the expected value and fails for another value or for Error`` () =
let ok : Result<int, string> = Ok 5
Assert.OkEquals (5, ok, "message")
let other : Result<int, string> = Ok 6
let different = failureOf (fun () -> Assert.OkEquals (5, other, "the value must be 5"))
assertContains different "the value must be 5" "OkEquals must fail with its message on another value"
let error : Result<int, string> = Error "the error"
let failure = failureOf (fun () -> Assert.OkEquals (5, error, "the call must succeed"))
assertContains failure "the error" "OkEquals must fail with the error on Error"

[<TestMethod>]
member _.``ErrorEquals passes for the expected error and fails for another error or for Ok`` () =
let error : Result<int, string> = Error "the error"
Assert.ErrorEquals ("the error", error, "message")
let other : Result<int, string> = Error "another error"
let different =
failureOf (fun () -> Assert.ErrorEquals ("the error", other, "the error must be the expected one"))
assertContains different "the error must be the expected one" "ErrorEquals must fail with its message on another error"
let ok : Result<int, string> = Ok 5
let failure = failureOf (fun () -> Assert.ErrorEquals ("the error", ok, "the call must fail"))
assertContains failure "5" "ErrorEquals must fail with the value on Ok"

// ---- default values ----

[<TestMethod>]
member _.``IsDefaultOf passes for the default value and fails for another one`` () =
Assert.IsDefaultOf (0, "message")
Assert.IsDefaultOf (noInt, "message")
let number = failureOf (fun () -> Assert.IsDefaultOf (1, "the number must be the default"))
assertContains number "the number must be the default" "IsDefaultOf must fail with its message on a non-default number"
let option = failureOf (fun () -> Assert.IsDefaultOf (Some 1, "the option must be the default"))
assertContains option "the option must be the default" "IsDefaultOf must fail with its message on a non-default option"
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,15 @@ type SeqLikeAccessContract<'C> (ops : SeqOps<'C>) =
let struct (collection, elements) = this.Create input
let tail = ops.TryTail collection

Assert.AreEqual<bool>(
not (Array.isEmpty elements),
tail.IsSome,
$"%s{ops.Name}.tryTail must return a tail exactly for a collection with elements"
)
if Array.isEmpty elements then
Assert.IsValueNone (tail, $"%s{ops.Name}.tryTail must return nothing for an empty collection")
else
let tail =
Assert.WantValueSome (tail, $"%s{ops.Name}.tryTail must return a tail for a collection with elements")

// CollectionAssert compares the elements of the tail and reports the first one that differs
match tail with
| ValueSome tail ->
// CollectionAssert compares the elements of the tail and reports the first one that differs
CollectionAssert.AreEqual (
Array.tail elements,
ops.ToArray tail,
$"%s{ops.Name}.tryTail must return the elements after the first one"
)
| ValueNone -> ()
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
</ItemGroup>

<ItemGroup>
<Compile Include="Assert.fs" />
<Compile Include="TestCategories.fs" />
<Compile Include="AssertExtensionsTests.fs" />
<Compile Include="Contracts\Mirror.fs" />
<Compile Include="Contracts\ExceptionAssert.fs" />
<Compile Include="Contracts\Inputs.fs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -599,8 +599,10 @@ type FlatListBuilderTests () =
FlatListBuilder.replace oldValue 99 builder |> ignore

CollectionAssert.AreEqual (
FlatListOracles.replaceFirst (=) oldValue 99 input
|> ValueOption.get,
Assert.WantValueSome (
FlatListOracles.replaceFirst (=) oldValue 99 input,
"The test data must contain the value to replace"
),
Seq.toArray builder,
"FlatListBuilder.replace must replace the first equal element"
)
Expand All @@ -626,8 +628,10 @@ type FlatListBuilderTests () =
|> ignore

CollectionAssert.AreEqual (
FlatListOracles.replaceFirst FlatListOracles.equalIgnoringCase oldValue "X" names
|> ValueOption.get,
Assert.WantValueSome (
FlatListOracles.replaceFirst FlatListOracles.equalIgnoringCase oldValue "X" names,
"The test data must contain a string that the comparer finds equal"
),
Seq.toArray builder,
"FlatListBuilder.replaceWith must replace the first element that the comparer finds equal"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,21 @@ type FlatListOptionTests () =

[<TestMethod>]
member _.``tryTail returns an option`` () =
let expected = FSharp.Collections.Immutable.FlatList.tryTail array
let expected =
Assert.WantValueSome (
FSharp.Collections.Immutable.FlatList.tryTail array,
"FlatList.tryTail must return a tail for a flat list with elements"
)

let found : FlatList<int> option = FlatList.tryTail array
Assert.AreEqual<bool>(expected.IsSome, found.IsSome, "Option.FlatList.tryTail must return a tail when the default does")
let tail = Assert.WantSome (found, "Option.FlatList.tryTail must return a tail when the default does")

// The tails are compared element by element
match expected, found with
| ValueSome expected, Some found ->
CollectionAssert.AreEqual (
FlatList.toArray expected,
FlatList.toArray found,
"Option.FlatList.tryTail must return the tail of the default"
)
| _ -> ()
CollectionAssert.AreEqual (
FlatList.toArray expected,
FlatList.toArray tail,
"Option.FlatList.tryTail must return the tail of the default"
)

[<TestMethod>]
member _.``pick takes a chooser of options`` () =
Expand Down
Loading
Loading