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:                 watcher.OnError.Subscribe(fun err -> printfn "Watcher error: %A" <| err.GetException()) |> ignore
                  watcher.EnableRaisingEvents <- true
  
                  // Disable
                  watcher.EnableRaisingEvents <- false
  
                  // On Unix the file write time is in 1s increments;
                  // if we don't wait, there's a chance that the polling
  
negative.code:                 Expect.containsAll filesChanged [ srcFile; dstFile ] ""
              }
  
          // TODO: This test is failing in CI so disabling it for now
          if envVarOrNone "CI" |> Option.isNone then
              "Files in sub-directory",
              fun usePolling tempFolder ->
                  async {
                      let subDirPath = Path.Combine(tempFolder, "subdir")
                      Directory.CreateDirectory(subDirPath) |> ignore
  
                      let newFilePath = Path.Combine(subDirPath, "newFile")
                      let changedFilePath = Path.Combine(subDirPath, "changedFile")
                      let deletedFilePath = Path.Combine(subDirPath, "deletedFile")
                      let srcForRenamedFilePath = Path.Combine(subDirPath, "srcForRenamedFile")
                      let dstForRenamedFilePath = Path.Combine(subDirPath, "dstForRenamedFile")
  
                      File.WriteAllText(changedFilePath, "content")
                      File.WriteAllText(deletedFilePath, "content")
                      File.WriteAllText(srcForRenamedFilePath, "content")
  
                      let allExpectedChanges = [
                          newFilePath
                          changedFilePath
                          deletedFilePath
                          srcForRenamedFilePath
                          dstForRenamedFilePath
                          ]
  
                      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
                          if filesChanged.Count >= allExpectedChanges.Length
                          then tcs.TrySetResult(true) |> ignore
  
                      watcher.OnFileChange.Subscribe(onFileChange) |> ignore
                      watcher.OnError.Subscribe(fun err -> printfn "Watcher error: %A" <| err.GetException()) |> ignore
                      watcher.EnableRaisingEvents <- true
  
                      // 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, "")
                      File.WriteAllText(changedFilePath, "changed content")
                      File.Delete(deletedFilePath)
                      File.Move(srcForRenamedFilePath, dstForRenamedFilePath)
  
                      try
                          do! Async.TimeoutAfter(defaultTimeout, tcs.Task) |> Async.AwaitTaskUnwrappingException |> Async.Ignore
                      with | :? TimeoutException as ex -> failtest $"Got {filesChanged.Count} of the expected {allExpectedChanges.Length} events. {ex.Message}"
  
                      // Note: the polling watcher currently returns changes for directories when
                      // their last write time is modified. This means we get a change for the sub-directory itself
                      // (creating a file inside a directory changes its last write time)
                      // This does not seem to be a problem...
                      Expect.containsAll filesChanged allExpectedChanges ""
                  }
  
          "No event if disabled",
          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
  
                  // Disable
                  watcher.EnableRaisingEvents <- false
  
                  // On Unix the file write time is in 1s increments;
                  // if we don't wait, there's a chance that the polling
  