anchor.diff:  let oneSecond = TimeSpan.FromSeconds(1.0)
   
   let equal expected actual = Expect.equal actual expected "are equal"
  +
  +let envVarOrNone varName =
  +    let s = Environment.GetEnvironmentVariable(varName)
  +    if String.IsNullOrEmpty(s) then None else Some s
   
   let usingTempDirectoryAsync f =
       async {
  
positive.code:                     | ex -> failtest $"Expected a timeout but got {ex}"
              }
  
          "No event if disposed",
          fun usePolling tempFolder ->
              async {
                  let newFilePath = Path.Combine(tempFolder, "newFile")
                  let changedFilePath = Path.Combine(tempFolder, "changedFile")
                  let deletedFilePath = Path.Combine(tempFolder, "deletedFile")
                  let srcForRenamedFilePath = Path.Combine(tempFolder, "srcForRenamedFile")
                  let dstForRenamedFilePath = Path.Combine(tempFolder, "dstForRenamedFile")
  
                  File.WriteAllText(changedFilePath, "content")
                  File.WriteAllText(deletedFilePath, "content")
                  File.WriteAllText(srcForRenamedFilePath, "content")
  
                  use watcher = createWatcher tempFolder usePolling []
                  let tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously)
                  let filesChanged = new HashSet<string>()
  
                  let onFileChange = fun path ->
                      filesChanged.Add(path) |> ignore
                      tcs.TrySetResult(true) |> ignore
  
                  watcher.OnFileChange.Subscribe(onFileChange) |> ignore
                  watcher.OnError.Subscribe(fun err -> printfn "Watcher error: %A" <| err.GetException()) |> ignore
                  watcher.EnableRaisingEvents <- true
  
                  // Dispose
                  watcher.Dispose()
  
                  // On Unix the file write time is in 1s increments;
                  // if we don't wait, there's a chance that the polling
                  // watcher will not detect the change.
                  //
                  // FIXME: Asynchronously waiting with `do! Async.Sleep(1000)`
                  //        results in the FSW not reporting our events anymmore (?!)
                  //        (I tested this on Ubuntu WSL2 only)
                  System.Threading.Thread.Sleep(oneSecond)
  
                  File.WriteAllText(newFilePath, "content")
                  File.WriteAllText(changedFilePath, "new content")
                  File.Delete(deletedFilePath)
                  File.Move(srcForRenamedFilePath, dstForRenamedFilePath)
  
                  let mutable error = None
                  try
                      do! Async.TimeoutAfter(negativeTimeout, tcs.Task) |> Async.AwaitTaskUnwrappingException |> Async.Ignore
                  with | ex -> error <- Some ex
  
                  match error with
                  | None -> failtest $"The watcher detected {filesChanged.Count} change(s)!"
                  | Some ex ->
                      match ex with
                      | :? TimeoutException -> () // Expected
                      | ex -> failtest $"Expected a timeout but got {ex}"
              }
  
          "Delete sub-directory",
          fun usePolling tempFolder ->
              async {
  
negative.code:                 watcher.OnError.Subscribe(fun err -> printfn "Watcher error: %A" <| err.GetException()) |> ignore
                  watcher.EnableRaisingEvents <- true
  
                  // Dispose
                  watcher.Dispose()
  
                  // On Unix the file write time is in 1s increments;
                  // if we don't wait, there's a chance that the polling
  