view third_party/libuv/test/test-fs-event.c @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 948de3f54cea
children
line wrap: on
line source

/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "uv.h"
#include "task.h"

#include <string.h>
#include <fcntl.h>

#if defined(__APPLE__) && !TARGET_OS_IPHONE
# include <AvailabilityMacros.h>
#endif

static uv_fs_event_t fs_event;
static const char file_prefix[] = "fsevent-";
static const int fs_event_file_count = 16;
#if (defined(__APPLE__) && !defined(__TSAN__)) || defined(_WIN32)
static const char file_prefix_in_subdir[] = "subdir";
static int fs_multievent_cb_called;
#endif
static uv_timer_t timer;
static int timer_cb_called;
static int close_cb_called;
static int fs_event_created;
static int fs_event_removed;
static int fs_event_cb_called;
#if defined(PATH_MAX)
static char fs_event_filename[PATH_MAX];
#else
static char fs_event_filename[1024];
#endif  /* defined(PATH_MAX) */
static int timer_cb_touch_called;
static int timer_cb_exact_called;

static void fs_event_fail(uv_fs_event_t* handle,
                          const char* filename,
                          int events,
                          int status) {
  ASSERT(0 && "should never be called");
}

static void create_dir(const char* name) {
  int r;
  uv_fs_t req;
  r = uv_fs_mkdir(NULL, &req, name, 0755, NULL);
  ASSERT(r == 0 || r == UV_EEXIST);
  uv_fs_req_cleanup(&req);
}

static void create_file(const char* name) {
  int r;
  uv_file file;
  uv_fs_t req;

  r = uv_fs_open(NULL, &req, name, UV_FS_O_WRONLY | UV_FS_O_CREAT,
                 S_IWUSR | S_IRUSR,
                 NULL);
  ASSERT_GE(r, 0);
  file = r;
  uv_fs_req_cleanup(&req);
  r = uv_fs_close(NULL, &req, file, NULL);
  ASSERT_OK(r);
  uv_fs_req_cleanup(&req);
}

static int delete_dir(const char* name) {
  int r;
  uv_fs_t req;
  r = uv_fs_rmdir(NULL, &req, name, NULL);
  uv_fs_req_cleanup(&req);
  return r;
}

static int delete_file(const char* name) {
  int r;
  uv_fs_t req;
  r = uv_fs_unlink(NULL, &req, name, NULL);
  uv_fs_req_cleanup(&req);
  return r;
}

static void touch_file(const char* name) {
  int r;
  uv_file file;
  uv_fs_t req;
  uv_buf_t buf;

  r = uv_fs_open(NULL, &req, name, UV_FS_O_RDWR, 0, NULL);
  ASSERT_GE(r, 0);
  file = r;
  uv_fs_req_cleanup(&req);

  buf = uv_buf_init("foo", 4);
  r = uv_fs_write(NULL, &req, file, &buf, 1, -1, NULL);
  ASSERT_GE(r, 0);
  uv_fs_req_cleanup(&req);

  r = uv_fs_close(NULL, &req, file, NULL);
  ASSERT_OK(r);
  uv_fs_req_cleanup(&req);
}

static void close_cb(uv_handle_t* handle) {
  ASSERT_NOT_NULL(handle);
  close_cb_called++;
}

static void fail_cb(uv_fs_event_t* handle,
                    const char* path,
                    int events,
                    int status) {
  ASSERT(0 && "fail_cb called");
}

static void fs_event_cb_dir(uv_fs_event_t* handle, const char* filename,
  int events, int status) {
  ++fs_event_cb_called;
  ASSERT_PTR_EQ(handle, &fs_event);
  ASSERT_OK(status);
  ASSERT_EQ(events, UV_CHANGE);
  #if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
  ASSERT_OK(strcmp(filename, "file1"));
  #else
  ASSERT(filename == NULL || strcmp(filename, "file1") == 0);
  #endif
  ASSERT_OK(uv_fs_event_stop(handle));
  uv_close((uv_handle_t*)handle, close_cb);
}

static void fs_event_cb_del_dir(uv_fs_event_t* handle,
                                const char* filename,
                                int events,
                                int status) {
  ++fs_event_cb_called;
  ASSERT_PTR_EQ(handle, &fs_event);
  ASSERT_OK(status);
  ASSERT(events == UV_CHANGE || events == UV_RENAME);
  /* There is a bug in the FreeBSD kernel where the filename is sometimes NULL.
   * Refs: https://github.com/libuv/libuv/issues/4606
   */
  #if defined(__FreeBSD__)
  ASSERT(filename == NULL || strcmp(filename, "watch_del_dir") == 0);
  #else
  ASSERT_OK(strcmp(filename, "watch_del_dir"));
  #endif
  ASSERT_OK(uv_fs_event_stop(handle));
  uv_close((uv_handle_t*)handle, close_cb);
}

static const char* fs_event_get_filename(int i) {
  snprintf(fs_event_filename,
           sizeof(fs_event_filename),
           "watch_dir/%s%d",
           file_prefix,
           i);
  return fs_event_filename;
}

static void fs_event_create_files(uv_timer_t* handle) {
  /* Make sure we're not attempting to create files we do not intend */
  ASSERT_LT(fs_event_created, fs_event_file_count);

  /* Create the file */
  create_file(fs_event_get_filename(fs_event_created));

  if (++fs_event_created < fs_event_file_count) {
    /* Create another file on a different event loop tick.  We do it this way
     * to avoid fs events coalescing into one fs event. */
    ASSERT_OK(uv_timer_start(&timer, fs_event_create_files, 100, 0));
  }
}

static void fs_event_del_dir(uv_timer_t* handle) {
  int r;

  r = delete_dir("watch_del_dir");
  ASSERT_OK(r);

  uv_close((uv_handle_t*)handle, close_cb);
}

static void fs_event_unlink_files(uv_timer_t* handle) {
  int r;
  int i;

  /* NOTE: handle might be NULL if invoked not as timer callback */
  if (handle == NULL) {
    /* Unlink all files */
    for (i = 0; i < 16; i++) {
      r = delete_file(fs_event_get_filename(i));
      if (handle != NULL)
        ASSERT_OK(r);
    }
  } else {
    /* Make sure we're not attempting to remove files we do not intend */
    ASSERT_LT(fs_event_removed, fs_event_file_count);

    /* Remove the file */
    ASSERT_OK(delete_file(fs_event_get_filename(fs_event_removed)));

    if (++fs_event_removed < fs_event_file_count) {
      /* Remove another file on a different event loop tick.  We do it this way
       * to avoid fs events coalescing into one fs event. */
      ASSERT_OK(uv_timer_start(&timer, fs_event_unlink_files, 1, 0));
    }
  }
}

static void fs_event_cb_dir_multi_file(uv_fs_event_t* handle,
                                       const char* filename,
                                       int events,
                                       int status) {
  fs_event_cb_called++;
  ASSERT_PTR_EQ(handle, &fs_event);
  ASSERT_OK(status);
  ASSERT(events == UV_CHANGE || events == UV_RENAME);
#if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
  ASSERT_NOT_NULL(filename);
  ASSERT_MEM_EQ(filename, file_prefix, sizeof(file_prefix) - 1);
#else
  if (filename != NULL)
    ASSERT_MEM_EQ(filename, file_prefix, sizeof(file_prefix) - 1);
#endif

  if (fs_event_created + fs_event_removed == fs_event_file_count) {
    /* Once we've processed all create events, delete all files */
    ASSERT_OK(uv_timer_start(&timer, fs_event_unlink_files, 1, 0));
  } else if (fs_event_cb_called == 2 * fs_event_file_count) {
    /* Once we've processed all create and delete events, stop watching */
    uv_close((uv_handle_t*) &timer, close_cb);
    uv_close((uv_handle_t*) handle, close_cb);
  }
}

#if (defined(__APPLE__) && !defined(__TSAN__)) || defined(_WIN32)
static const char* fs_event_get_filename_in_subdir(int i) {
  snprintf(fs_event_filename,
           sizeof(fs_event_filename),
           "watch_dir/subdir/%s%d",
           file_prefix,
           i);
  return fs_event_filename;
}

static void fs_event_create_files_in_subdir(uv_timer_t* handle) {
  /* Make sure we're not attempting to create files we do not intend */
  ASSERT_LT(fs_event_created, fs_event_file_count);

  /* Create the file */
  create_file(fs_event_get_filename_in_subdir(fs_event_created));

  if (++fs_event_created < fs_event_file_count) {
    /* Create another file on a different event loop tick.  We do it this way
     * to avoid fs events coalescing into one fs event. */
    ASSERT_OK(uv_timer_start(&timer, fs_event_create_files_in_subdir, 100, 0));
  }
}

static void fs_event_unlink_files_in_subdir(uv_timer_t* handle) {
  int r;
  int i;

  /* NOTE: handle might be NULL if invoked not as timer callback */
  if (handle == NULL) {
    /* Unlink all files */
    for (i = 0; i < 16; i++) {
      r = delete_file(fs_event_get_filename_in_subdir(i));
      if (handle != NULL)
        ASSERT_OK(r);
    }
  } else {
    /* Make sure we're not attempting to remove files we do not intend */
    ASSERT_LT(fs_event_removed, fs_event_file_count);

    /* Remove the file */
    ASSERT_OK(delete_file(fs_event_get_filename_in_subdir(fs_event_removed)));

    if (++fs_event_removed < fs_event_file_count) {
      /* Remove another file on a different event loop tick.  We do it this way
       * to avoid fs events coalescing into one fs event. */
      ASSERT_OK(uv_timer_start(&timer,
                               fs_event_unlink_files_in_subdir,
                               1,
                               0));
    }
  }
}

static void fs_event_cb_dir_multi_file_in_subdir(uv_fs_event_t* handle,
                                                 const char* filename,
                                                 int events,
                                                 int status) {
#ifdef _WIN32
  /* Each file created (or deleted) will cause this callback to be called twice
   * under Windows: once with the name of the file, and second time with the
   * name of the directory. We will ignore the callback for the directory
   * itself. */
  if (filename && strcmp(filename, file_prefix_in_subdir) == 0)
    return;
#endif
  /* It may happen that the "subdir" creation event is captured even though
   * we started watching after its actual creation.
   */
  if (strcmp(filename, "subdir") == 0)
    return;

  fs_multievent_cb_called++;
  ASSERT_PTR_EQ(handle, &fs_event);
  ASSERT_OK(status);
  ASSERT(events == UV_CHANGE || events == UV_RENAME);
  #if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
  ASSERT_OK(strncmp(filename,
                    file_prefix_in_subdir,
                    sizeof(file_prefix_in_subdir) - 1));
  #else
  ASSERT_NE(filename == NULL ||
            strncmp(filename,
                    file_prefix_in_subdir,
                    sizeof(file_prefix_in_subdir) - 1) == 0, 0);
  #endif

  if (fs_event_created == fs_event_file_count &&
      fs_multievent_cb_called == fs_event_created) {
    /* Once we've processed all create events, delete all files */
    ASSERT_OK(uv_timer_start(&timer,
                             fs_event_unlink_files_in_subdir,
                             1,
                             0));
  } else if (fs_multievent_cb_called == 2 * fs_event_file_count) {
    /* Once we've processed all create and delete events, stop watching */
    ASSERT_EQ(fs_event_removed, fs_event_file_count);
    uv_close((uv_handle_t*) &timer, close_cb);
    uv_close((uv_handle_t*) handle, close_cb);
  }
}
#endif

static void fs_event_cb_file(uv_fs_event_t* handle, const char* filename,
  int events, int status) {
  ++fs_event_cb_called;
  ASSERT_PTR_EQ(handle, &fs_event);
  ASSERT_OK(status);
  ASSERT_EQ(events, UV_CHANGE);
  #if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
  ASSERT_OK(strcmp(filename, "file2"));
  #else
  ASSERT(filename == NULL || strcmp(filename, "file2") == 0);
  #endif
  ASSERT_OK(uv_fs_event_stop(handle));
  uv_close((uv_handle_t*)handle, close_cb);
}

static void fs_event_cb_file_current_dir(uv_fs_event_t* handle,
  const char* filename, int events, int status) {
  ++fs_event_cb_called;

  ASSERT_PTR_EQ(handle, &fs_event);
  ASSERT_OK(status);
  ASSERT_EQ(events, UV_CHANGE);
  #if defined(__APPLE__) || defined(_WIN32) || defined(__linux__)
  ASSERT_OK(strcmp(filename, "watch_file"));
  #else
  ASSERT(filename == NULL || strcmp(filename, "watch_file") == 0);
  #endif

  uv_close((uv_handle_t*)handle, close_cb);
}

static void timer_cb_file(uv_timer_t* handle) {
  ++timer_cb_called;

  if (timer_cb_called == 1) {
    touch_file("watch_dir/file1");
  } else {
    touch_file("watch_dir/file2");
    uv_close((uv_handle_t*)handle, close_cb);
  }
}

static void timer_cb_touch(uv_timer_t* timer) {
  uv_close((uv_handle_t*)timer, NULL);
  touch_file((char*) timer->data);
  timer_cb_touch_called++;
}

static void timer_cb_exact(uv_timer_t* handle) {
  int r;

  if (timer_cb_exact_called == 0) {
    touch_file("watch_dir/file.js");
  } else {
    uv_close((uv_handle_t*)handle, NULL);
    r = uv_fs_event_stop(&fs_event);
    ASSERT_OK(r);
    uv_close((uv_handle_t*) &fs_event, NULL);
  }

  ++timer_cb_exact_called;
}

static void timer_cb_watch_twice(uv_timer_t* handle) {
  uv_fs_event_t* handles = handle->data;
  uv_close((uv_handle_t*) (handles + 0), NULL);
  uv_close((uv_handle_t*) (handles + 1), NULL);
  uv_close((uv_handle_t*) handle, NULL);
}

static void fs_event_cb_close(uv_fs_event_t* handle,
                              const char* filename,
                              int events,
                              int status) {
  ASSERT_OK(status);

  ASSERT_LT(fs_event_cb_called, 3);
  ++fs_event_cb_called;

  if (fs_event_cb_called == 3) {
    uv_close((uv_handle_t*) handle, close_cb);
  }
}


TEST_IMPL(fs_event_watch_dir) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#elif defined(__MVS__)
  RETURN_SKIP("Directory watching not supported on this platform.");
#elif defined(__APPLE__) && defined(__TSAN__)
  RETURN_SKIP("Times out under TSAN.");
#endif

  uv_loop_t* loop = uv_default_loop();
  int r;

  /* Setup */
  fs_event_unlink_files(NULL);
  delete_file("watch_dir/file2");
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");
  create_dir("watch_dir");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_cb_dir_multi_file, "watch_dir", 0);
  ASSERT_OK(r);
  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);
  r = uv_timer_start(&timer, fs_event_create_files, 100, 0);
  ASSERT_OK(r);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(fs_event_cb_called, fs_event_created + fs_event_removed);
  ASSERT_EQ(2, close_cb_called);

  /* Cleanup */
  fs_event_unlink_files(NULL);
  delete_file("watch_dir/file2");
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_watch_delete_dir) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#elif defined(__MVS__)
  RETURN_SKIP("Directory watching not supported on this platform.");
#elif defined(__APPLE__) && defined(__TSAN__)
  RETURN_SKIP("Times out under TSAN.");
#endif

  uv_loop_t* loop = uv_default_loop();
  int r;

  /* Setup */
  fs_event_unlink_files(NULL);
  delete_dir("watch_del_dir/");
  create_dir("watch_del_dir");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_cb_del_dir, "watch_del_dir", 0);
  ASSERT_OK(r);
  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);
  r = uv_timer_start(&timer, fs_event_del_dir, 100, 0);
  ASSERT_OK(r);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(1, fs_event_cb_called);
  ASSERT_EQ(2, close_cb_called);

  /* Cleanup */
  fs_event_unlink_files(NULL);

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}


TEST_IMPL(fs_event_watch_dir_recursive) {
#if defined(__APPLE__) && defined(__TSAN__)
  RETURN_SKIP("Times out under TSAN.");
#elif defined(__APPLE__) || defined(_WIN32)
  uv_loop_t* loop;
  int r;
  uv_fs_event_t fs_event_root;

  /* Setup */
  loop = uv_default_loop();
  fs_event_unlink_files(NULL);
  delete_file("watch_dir/file2");
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/subdir");
  delete_dir("watch_dir/");
  create_dir("watch_dir");
  create_dir("watch_dir/subdir");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event,
                        fs_event_cb_dir_multi_file_in_subdir,
                        "watch_dir",
                        UV_FS_EVENT_RECURSIVE);
  ASSERT_OK(r);
  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);
  r = uv_timer_start(&timer, fs_event_create_files_in_subdir, 100, 0);
  ASSERT_OK(r);

#ifndef _WIN32
  /* Also try to watch the root directory.
   * This will be noisier, so we're just checking for any couple events to happen. */
  r = uv_fs_event_init(loop, &fs_event_root);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event_root,
                        fs_event_cb_close,
                        "/",
                        UV_FS_EVENT_RECURSIVE);
  ASSERT_OK(r);
#else
  fs_event_cb_called += 3;
  close_cb_called += 1;
  (void)fs_event_root;
#endif

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(fs_multievent_cb_called, fs_event_created + fs_event_removed);
  ASSERT_EQ(3, fs_event_cb_called);
  ASSERT_EQ(3, close_cb_called);

  /* Cleanup */
  fs_event_unlink_files_in_subdir(NULL);
  delete_file("watch_dir/file2");
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/subdir");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
#else
  RETURN_SKIP("Recursive directory watching not supported on this platform.");
#endif
}

#ifdef _WIN32
TEST_IMPL(fs_event_watch_dir_short_path) {
  uv_loop_t* loop;
  uv_fs_t req;
  int has_shortnames;
  int r;

  /* Setup */
  loop = uv_default_loop();
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");
  create_dir("watch_dir");
  create_file("watch_dir/file1");

  /* Newer version of Windows ship with
     HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsDisable8dot3NameCreation
     not equal to 0. So we verify the files we created are addressable by a 8.3
     short name */
  has_shortnames = uv_fs_stat(NULL, &req, "watch_~1", NULL) != UV_ENOENT;
  if (has_shortnames) {
    r = uv_fs_event_init(loop, &fs_event);
    ASSERT_OK(r);
    r = uv_fs_event_start(&fs_event, fs_event_cb_dir, "watch_~1", 0);
    ASSERT_OK(r);
    r = uv_timer_init(loop, &timer);
    ASSERT_OK(r);
    r = uv_timer_start(&timer, timer_cb_file, 100, 0);
    ASSERT_OK(r);

    uv_run(loop, UV_RUN_DEFAULT);

    ASSERT_EQ(1, fs_event_cb_called);
    ASSERT_EQ(1, timer_cb_called);
    ASSERT_EQ(1, close_cb_called);
  }

  /* Cleanup */
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);

  if (!has_shortnames)
    RETURN_SKIP("Was not able to address files with 8.3 short name.");

  return 0;
}
#endif


TEST_IMPL(fs_event_watch_file) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif

  uv_loop_t* loop = uv_default_loop();
  int r;

  /* Setup */
  delete_file("watch_dir/file2");
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");
  create_dir("watch_dir");
  create_file("watch_dir/file1");
  create_file("watch_dir/file2");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_cb_file, "watch_dir/file2", 0);
  ASSERT_OK(r);
  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);
  r = uv_timer_start(&timer, timer_cb_file, 100, 100);
  ASSERT_OK(r);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(1, fs_event_cb_called);
  ASSERT_EQ(2, timer_cb_called);
  ASSERT_EQ(2, close_cb_called);

  /* Cleanup */
  delete_file("watch_dir/file2");
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_watch_file_exact_path) {
  /*
    This test watches a file named "file.jsx" and modifies a file named
    "file.js". The test verifies that no events occur for file.jsx.
  */

#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif

  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();

  /* Setup */
  delete_file("watch_dir/file.js");
  delete_file("watch_dir/file.jsx");
  delete_dir("watch_dir/");
  create_dir("watch_dir");
  create_file("watch_dir/file.js");
  create_file("watch_dir/file.jsx");
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12)
  /* Empirically, FSEvents seems to (reliably) report the preceding
   * create_file events prior to macOS 10.11.6 in the subsequent fs_watch
   * creation, but that behavior hasn't been observed to occur on newer
   * versions. Give a long delay here to let the system settle before running
   * the test. */
  uv_sleep(1100);
  uv_update_time(loop);
#endif

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_fail, "watch_dir/file.jsx", 0);
  ASSERT_OK(r);
  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);
  r = uv_timer_start(&timer, timer_cb_exact, 100, 100);
  ASSERT_OK(r);
  r = uv_run(loop, UV_RUN_DEFAULT);
  ASSERT_OK(r);
  ASSERT_EQ(2, timer_cb_exact_called);

  /* Cleanup */
  delete_file("watch_dir/file.js");
  delete_file("watch_dir/file.jsx");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_watch_file_twice) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif
  const char path[] = "test/fixtures/empty_file";
  uv_fs_event_t watchers[2];
  uv_timer_t timer;
  uv_loop_t* loop;

  loop = uv_default_loop();
  timer.data = watchers;

  ASSERT_OK(uv_fs_event_init(loop, watchers + 0));
  ASSERT_OK(uv_fs_event_start(watchers + 0, fail_cb, path, 0));
  ASSERT_OK(uv_fs_event_init(loop, watchers + 1));
  ASSERT_OK(uv_fs_event_start(watchers + 1, fail_cb, path, 0));
  ASSERT_OK(uv_timer_init(loop, &timer));
  ASSERT_OK(uv_timer_start(&timer, timer_cb_watch_twice, 10, 0));
  ASSERT_OK(uv_run(loop, UV_RUN_DEFAULT));

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_watch_file_current_dir) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif
  uv_timer_t timer;
  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();

  /* Setup */
  delete_file("watch_file");
  create_file("watch_file");
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_12)
  /* Empirically, kevent seems to (sometimes) report the preceding
   * create_file events prior to macOS 10.11.6 in the subsequent fs_event_start
   * So let the system settle before running the test. */
  uv_sleep(1100);
  uv_update_time(loop);
#endif

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event,
                        fs_event_cb_file_current_dir,
                        "watch_file",
                        0);
  ASSERT_OK(r);


  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);

  timer.data = "watch_file";
  r = uv_timer_start(&timer, timer_cb_touch, 1100, 0);
  ASSERT_OK(r);

  ASSERT_OK(timer_cb_touch_called);
  ASSERT_OK(fs_event_cb_called);
  ASSERT_OK(close_cb_called);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(1, timer_cb_touch_called);
  /* FSEvents on macOS sometimes sends one change event, sometimes two. */
  ASSERT_NE(0, fs_event_cb_called);
  ASSERT_EQ(1, close_cb_called);

  /* Cleanup */
  delete_file("watch_file");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

#ifdef _WIN32
TEST_IMPL(fs_event_watch_file_root_dir) {
  uv_loop_t* loop;
  int r;

  const char* sys_drive = getenv("SystemDrive");
  char path[] = "\\\\?\\X:\\bootsect.bak";

  ASSERT_NOT_NULL(sys_drive);
  strncpy(path + sizeof("\\\\?\\") - 1, sys_drive, 1);

  loop = uv_default_loop();

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fail_cb, path, 0);
  if (r == UV_ENOENT)
    RETURN_SKIP("bootsect.bak doesn't exist in system root.\n");
  ASSERT_OK(r);

  uv_close((uv_handle_t*) &fs_event, NULL);

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}
#endif

TEST_IMPL(fs_event_no_callback_after_close) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif

  uv_loop_t* loop = uv_default_loop();
  int r;

  /* Setup */
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");
  create_dir("watch_dir");
  create_file("watch_dir/file1");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event,
                        fs_event_cb_file,
                        "watch_dir/file1",
                        0);
  ASSERT_OK(r);


  uv_close((uv_handle_t*)&fs_event, close_cb);
  touch_file("watch_dir/file1");
  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_OK(fs_event_cb_called);
  ASSERT_EQ(1, close_cb_called);

  /* Cleanup */
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_no_callback_on_close) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif

  uv_loop_t* loop = uv_default_loop();
  int r;

  /* Setup */
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");
  create_dir("watch_dir");
  create_file("watch_dir/file1");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event,
                        fs_event_cb_file,
                        "watch_dir/file1",
                        0);
  ASSERT_OK(r);

  uv_close((uv_handle_t*)&fs_event, close_cb);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_OK(fs_event_cb_called);
  ASSERT_EQ(1, close_cb_called);

  /* Cleanup */
  delete_file("watch_dir/file1");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}


static void timer_cb(uv_timer_t* handle) {
  int r;

  r = uv_fs_event_init(handle->loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_fail, ".", 0);
  ASSERT_OK(r);

  uv_close((uv_handle_t*)&fs_event, close_cb);
  uv_close((uv_handle_t*)handle, close_cb);
}


TEST_IMPL(fs_event_immediate_close) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif
  uv_timer_t timer;
  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();

  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);

  r = uv_timer_start(&timer, timer_cb, 1, 0);
  ASSERT_OK(r);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(2, close_cb_called);

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}


TEST_IMPL(fs_event_close_with_pending_event) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif
  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();

  create_dir("watch_dir");
  create_file("watch_dir/file");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_fail, "watch_dir", 0);
  ASSERT_OK(r);

  /* Generate an fs event. */
  touch_file("watch_dir/file");

  uv_close((uv_handle_t*)&fs_event, close_cb);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(1, close_cb_called);

  /* Clean up */
  delete_file("watch_dir/file");
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_close_with_pending_delete_event) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif
  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();

  create_dir("watch_dir");
  create_file("watch_dir/file");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_fail, "watch_dir/file", 0);
  ASSERT_OK(r);

  /* Generate an fs event. */
  delete_file("watch_dir/file");

  /* Allow time for the remove event to propagate to the pending list. */
  /* XXX - perhaps just for __sun? */
  uv_sleep(1100);
  uv_update_time(loop);

  uv_close((uv_handle_t*)&fs_event, close_cb);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(1, close_cb_called);

  /* Clean up */
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_close_in_callback) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#elif defined(__MVS__)
  RETURN_SKIP("Directory watching not supported on this platform.");
#elif defined(__APPLE__) && defined(__TSAN__)
  RETURN_SKIP("Times out under TSAN.");
#endif
  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();

  fs_event_unlink_files(NULL);
  create_dir("watch_dir");

  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_cb_close, "watch_dir", 0);
  ASSERT_OK(r);

  r = uv_timer_init(loop, &timer);
  ASSERT_OK(r);
  r = uv_timer_start(&timer, fs_event_create_files, 100, 0);
  ASSERT_OK(r);

  uv_run(loop, UV_RUN_DEFAULT);

  uv_close((uv_handle_t*)&timer, close_cb);

  uv_run(loop, UV_RUN_ONCE);

  ASSERT_EQ(2, close_cb_called);
  ASSERT_EQ(3, fs_event_cb_called);

  /* Clean up */
  fs_event_unlink_files(NULL);
  delete_dir("watch_dir/");

  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_start_and_close) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif
  uv_loop_t* loop;
  uv_fs_event_t fs_event1;
  uv_fs_event_t fs_event2;
  int r;

  loop = uv_default_loop();

  create_dir("watch_dir");

  r = uv_fs_event_init(loop, &fs_event1);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event1, fs_event_cb_dir, "watch_dir", 0);
  ASSERT_OK(r);

  r = uv_fs_event_init(loop, &fs_event2);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event2, fs_event_cb_dir, "watch_dir", 0);
  ASSERT_OK(r);

  uv_close((uv_handle_t*) &fs_event2, close_cb);
  uv_close((uv_handle_t*) &fs_event1, close_cb);

  uv_run(loop, UV_RUN_DEFAULT);

  ASSERT_EQ(2, close_cb_called);

  delete_dir("watch_dir/");
  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_getpath) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif
  uv_loop_t* loop = uv_default_loop();
  unsigned i;
  int r;
  char buf[1024];
  size_t len;
  const char* const watch_dir[] = {
    "watch_dir",
    "watch_dir/",
    "watch_dir///",
    "watch_dir/subfolder/..",
    "watch_dir//subfolder//..//",
  };

  create_dir("watch_dir");
  create_dir("watch_dir/subfolder");


  for (i = 0; i < ARRAY_SIZE(watch_dir); i++) {
    r = uv_fs_event_init(loop, &fs_event);
    ASSERT_OK(r);
    len = sizeof buf;
    r = uv_fs_event_getpath(&fs_event, buf, &len);
    ASSERT_EQ(r, UV_EINVAL);
    r = uv_fs_event_start(&fs_event, fail_cb, watch_dir[i], 0);
    ASSERT_OK(r);
    len = 1;
    r = uv_fs_event_getpath(&fs_event, buf, &len);
    ASSERT_EQ(r, UV_ENOBUFS);
    ASSERT_LT(len, sizeof buf); /* sanity check */
    ASSERT_EQ(len, strlen(watch_dir[i]) + 1);
    r = uv_fs_event_getpath(&fs_event, buf, &len);
    ASSERT_OK(r);
    ASSERT_EQ(len, strlen(watch_dir[i]));
    ASSERT(strcmp(buf, watch_dir[i]) == 0);
    r = uv_fs_event_stop(&fs_event);
    ASSERT_OK(r);
    uv_close((uv_handle_t*) &fs_event, close_cb);

    uv_run(loop, UV_RUN_DEFAULT);

    ASSERT_EQ(1, close_cb_called);
    close_cb_called = 0;
  }

  delete_dir("watch_dir/");
  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

TEST_IMPL(fs_event_watch_invalid_path) {
#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif

  uv_loop_t* loop;
  int r;

  loop = uv_default_loop();
  r = uv_fs_event_init(loop, &fs_event);
  ASSERT_OK(r);
  r = uv_fs_event_start(&fs_event, fs_event_cb_file, "<:;", 0);
  ASSERT(r);
  ASSERT_OK(uv_is_active((uv_handle_t*) &fs_event));
  r = uv_fs_event_start(&fs_event, fs_event_cb_file, "", 0);
  ASSERT(r);
  ASSERT_OK(uv_is_active((uv_handle_t*) &fs_event));
  MAKE_VALGRIND_HAPPY(loop);
  return 0;
}

static int fs_event_cb_stop_calls;

static void fs_event_cb_stop(uv_fs_event_t* handle, const char* path,
                             int events, int status) {
  uv_fs_event_stop(handle);
  fs_event_cb_stop_calls++;
}

TEST_IMPL(fs_event_stop_in_cb) {
  uv_fs_event_t fs;
  uv_timer_t timer;
  char path[] = "fs_event_stop_in_cb.txt";

#if defined(NO_FS_EVENTS)
  RETURN_SKIP(NO_FS_EVENTS);
#endif

  delete_file(path);
  create_file(path);

  ASSERT_OK(uv_fs_event_init(uv_default_loop(), &fs));
  ASSERT_OK(uv_fs_event_start(&fs, fs_event_cb_stop, path, 0));

  /* Note: timer_cb_touch() closes the handle. */
  timer.data = path;
  ASSERT_OK(uv_timer_init(uv_default_loop(), &timer));
  ASSERT_OK(uv_timer_start(&timer, timer_cb_touch, 100, 0));

  ASSERT_OK(fs_event_cb_stop_calls);
  ASSERT_OK(timer_cb_touch_called);

  ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT));

  ASSERT_EQ(1, fs_event_cb_stop_calls);
  ASSERT_EQ(1, timer_cb_touch_called);

  uv_close((uv_handle_t*) &fs, NULL);
  ASSERT_OK(uv_run(uv_default_loop(), UV_RUN_DEFAULT));
  ASSERT_EQ(1, fs_event_cb_stop_calls);

  delete_file(path);

  MAKE_VALGRIND_HAPPY(uv_default_loop());
  return 0;
}