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
6 changes: 6 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
│ ├── TaskSeq.fs – TaskSeq integration
│ └── UniqueKey.fs – unique key helpers
├── tests/Cosmos.Tests/ – MSTest integration test project
├── tests/Cosmos.Tests.Infrastructure/ – shared test fixtures, emulator settings and assertion helpers
├── build/ – FAKE build scripts
└── docsSrc/ – FSharp.Formatting documentation source
```
Expand Down Expand Up @@ -228,6 +229,11 @@ module MyTypeExtensions =
* The emulator must be running locally or installed via the `copilot-setup-steps.yml` workflow.
* Emulator endpoint: `https://127.0.0.1:8081`
* Emulator primary key: `C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==`
* `COSMOS_EMULATOR_ENDPOINT` overrides the emulator endpoint the tests connect to.
* `COSMOS_EMULATOR_KEY` overrides the emulator key the tests authenticate with.
* `COSMOS_EMULATOR_PARTITION_COUNT` is the number of partitions the emulator offers (default `25`, the Windows emulator's default `/PartitionCount`); the fixtures keep at most that many test containers at a time, one partition each.
* `COSMOS_TEST_LEFTOVER_AGE_MINUTES` is how many minutes a `fsac-test-` database must stay unmodified before the leftover sweep of `[<AssemblyInitialize>]` and `[<AssemblyCleanup>]` deletes it (default `60`), so that the live databases of another test process on the same emulator are kept; `0` deletes every test database and is safe only while no other test process uses the emulator.
* Every test project under `tests/` references `tests/Cosmos.Tests.Infrastructure` through `tests/Directory.Build.props`; a new test project gets it without a `ProjectReference` of its own.
* `CollectionAssert` cannot work with F# lists – use F# array syntax (`[| ... |]`) instead.
* `StringAssert` has overloads with `StringComparison`.
* Use `Assert.Contains` instead of `Assert.IsTrue (str.Contains ..., "message")`, and do not put the actual value into the message.
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="10.0.401" />
<PackageVersion Include="MSTest" Version="4.2.3" />
<PackageVersion Include="MSTest.TestFramework" Version="4.2.3" />
<PackageVersion Include="NuGet.Common" Version="6.14.3" />
<PackageVersion Include="NuGet.Protocol" Version="6.14.3" />
<PackageVersion Include="Octokit" Version="14.0.0" />
Expand Down
1 change: 1 addition & 0 deletions FSharp.Azure.Cosmos.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"path": "FSharp.Azure.Cosmos.slnx",
"projects": [
"src\\Cosmos\\FSharp.Azure.Cosmos.fsproj",
"tests\\Cosmos.Tests.Infrastructure\\FSharp.Azure.Cosmos.Tests.Infrastructure.fsproj",
"tests\\Cosmos.Tests\\FSharp.Azure.Cosmos.Tests.fsproj"
]
}
Expand Down
1 change: 1 addition & 0 deletions FSharp.Azure.Cosmos.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
</Folder>
<Project Path="build/build.fsproj" />
<Project Path="src/Cosmos/FSharp.Azure.Cosmos.fsproj" />
<Project Path="tests/Cosmos.Tests.Infrastructure/FSharp.Azure.Cosmos.Tests.Infrastructure.fsproj" />
<Project Path="tests/Cosmos.Tests/FSharp.Azure.Cosmos.Tests.fsproj" />
</Solution>
26 changes: 19 additions & 7 deletions build/build.fs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,16 @@ let testsCodeGlob =

let srcGlob = rootDirectory </> "src/**/*.??proj"

let testsGlob = rootDirectory </> "tests/**/*.??proj"
/// Every project under tests/, the helper libraries shared by the test projects included
let testsDirectoryGlob = rootDirectory </> "tests/**/*.??proj"

let srcAndTest = !!srcGlob ++ testsGlob
/// <summary>
/// The test applications only: helper libraries such as the test infrastructure do not end in <c>.Tests</c>,
/// so <c>dotnet test</c> and <c>dotnet watch test</c> never try to run them.
/// </summary>
let testsGlob = rootDirectory </> "tests/**/*.Tests.??proj"

let srcAndTest = !!srcGlob ++ testsDirectoryGlob

let distDir = rootDirectory </> "dist"

Expand Down Expand Up @@ -246,7 +253,7 @@ let clean _ =
[ "bin"; "temp"; distDir; coverageReportDir; testResultsDir ]
|> Shell.cleanDirs

!!srcGlob ++ testsGlob
!!srcGlob ++ testsDirectoryGlob
|> Seq.collect (fun p ->
[ "bin"; "obj" ]
|> Seq.map (fun sp -> IO.Path.GetDirectoryName p </> sp)
Expand Down Expand Up @@ -394,8 +401,8 @@ let generateCoverageReport _ =
sprintf "-targetdir:\"%s\"" coverageReportDir
// Add source dir
sprintf "-sourcedirs:\"%s\"" sourceDirs
// Ignore test assemblies
sprintf "-assemblyfilters:\"%s\"" "-*.Tests"
// Ignore test assemblies and the helper libraries they share
sprintf "-assemblyfilters:\"%s\"" "-*.Tests;-*.Tests.Infrastructure"
// Generate HTML and Cobertura reports
sprintf "-reporttypes:%s" "Html;Cobertura"
]
Expand Down Expand Up @@ -615,10 +622,15 @@ let initTargets (ctx : Context.FakeExecutionContext) =
&& String.Equals (value, "PublishToGitHub", StringComparison.OrdinalIgnoreCase)
)

/// Defines a dependency - y is dependent on x. Finishes the chain.
/// <summary>
/// Defines a dependency - <paramref name="y"/> is dependent on <paramref name="x"/>. Finishes the chain.
/// </summary>
let (==>!) x y = x ==> y |> ignore

/// Defines a soft dependency. x must run before y, if it is present, but y does not require x to be run. Finishes the chain.
/// <summary>
/// Defines a soft dependency. <paramref name="x"/> must run before <paramref name="y"/>, if it is present, but
/// <paramref name="y"/> does not require <paramref name="x"/> to be run. Finishes the chain.
/// </summary>
let (?=>!) x y = x ?=> y |> ignore
//-----------------------------------------------------------------------------
// Hide Secrets in Logger
Expand Down
136 changes: 136 additions & 0 deletions tests/Cosmos.Tests.Infrastructure/Assert.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
namespace FSharp.Azure.Cosmos.Tests

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

/// <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="Assert"/>.
/// <para>
/// Assertions such as <see cref="Assert.WantSome"/> return the unwrapped value or fail the test; their twins such as
/// <see cref="Assert.IsSome"/> discard the value.
/// </para>
/// </summary>
[<AutoOpen>]
module AssertExtensions =

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 (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 (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 (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 (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)

/// <summary>
/// Fails the test with <paramref name="message"/>; typed so that it can stand in for a value of any type.
/// </summary>
static member FailWithData<'T> ([<Optional>] message : string | null) =
Assert.Fail (message)
Unchecked.defaultof<'T>
Loading
Loading