<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <title>Test the IOUtils file I/O API</title>
  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
  <link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
  <script src="file_ioutils_test_fixtures.js"></script>
  <script>
    "use strict";

    const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
    const { ObjectUtils } = ChromeUtils.import("resource://gre/modules/ObjectUtils.jsm");

    add_task(async function test_stat() {
      info("Test attempt to stat a regular empty file");

      const emptyFileName = PathUtils.join(PathUtils.tempDir, "test_stat_empty.tmp");
      await createFile(emptyFileName);

      const emptyFileInfo = await IOUtils.stat(emptyFileName);
      is(emptyFileInfo.size, 0, "IOUtils::stat can get correct (empty) file size");
      is(emptyFileInfo.path, emptyFileName, "IOUtils::stat result contains the path");
      is(emptyFileInfo.type, "regular", "IOUtils::stat can stat regular (empty) files");
      Assert.less(
        (emptyFileInfo.lastModified - new Date().valueOf()),
        1000, // Allow for 1 second deviation in case of slow tests.
        "IOUtils::stat can get the last modification date for a regular file"
      );

      info("Test attempt to stat a regular binary file");
      const tempFileName = PathUtils.join(PathUtils.tempDir, "test_stat_binary.tmp");
      const bytes = Uint8Array.of(...new Array(50).keys());
      await createFile(tempFileName, bytes);

      const fileInfo = await IOUtils.stat(tempFileName);
      is(fileInfo.size, 50, "IOUtils::stat can get correct file size");
      is(fileInfo.path, tempFileName, "IOUtils::stat result contains the path");
      is(fileInfo.type, "regular", "IOUtils::stat can stat regular files");
      Assert.less(
        (fileInfo.lastModified - new Date().valueOf()),
        1000, // Allow for 1 second deviation in case of slow tests.
        "IOUtils::stat can get the last modification date for a regular file"
      );

      info("Test attempt to stat a directory");
      const tempDirName = PathUtils.join(PathUtils.tempDir, "test_stat_dir.tmp.d");
      await IOUtils.makeDirectory(tempDirName);

      const dirInfo = await IOUtils.stat(tempDirName);
      is(dirInfo.size, -1, "IOUtils::stat reports -1 size for directories")
      is(fileInfo.path, tempFileName, "IOUtils::stat result contains the path");
      is(fileInfo.type, "regular", "IOUtils::stat can stat directories");
      Assert.less(
        (fileInfo.lastModified - new Date().valueOf()),
        1000, // Allow for 1 second deviation in case of slow tests.
        "IOUtils::stat can get the last modification date for a regular file"
      );
      Assert.less(
        (fileInfo.lastAccessed - new Date().valueOf()),
        1000,
        "IOUtils::stat can get the last access date for a regular file"
      );

      await cleanup(emptyFileName, tempFileName, tempDirName)
    });

    add_task(async function test_stat_failures() {
      info("Test attempt to stat a non-existing file");

      const notExistsFile = PathUtils.join(PathUtils.tempDir, "test_stat_not_exists.tmp");

      await Assert.rejects(
        IOUtils.stat(notExistsFile),
        /Could not stat file\(.*\) because it does not exist/,
        "IOUtils::stat throws if the target file does not exist"
      );
    });

    add_task(async function test_setModificationTime_and_stat() {
      const tmpFileName = PathUtils.join(PathUtils.tempDir, "test_setModificationTime_and_stat.tmp");
      {
        info("Test attempt to setModificationTime a file");
        await createFile(tmpFileName);

        const oldFileInfo = await IOUtils.stat(tmpFileName);
        await sleep(500);

        // Now update the time stamp.
        const stamp = await IOUtils.setModificationTime(tmpFileName);
        const newFileInfo = await IOUtils.stat(tmpFileName);

        ok(
          newFileInfo.lastModified > oldFileInfo.lastModified,
          "IOUtils::setModificationTime can update the lastModified time stamp on the file system"
        );
        is(
          stamp,
          newFileInfo.lastModified,
          "IOUtils::setModificationTime returns the updated time stamp."
        );
        is(
          newFileInfo.lastAccessed,
          oldFileInfo.lastAccessed,
          "IOUtils::setModificationTime does not change lastAccessed"
        );

        await sleep(500);

        const newerStamp = await IOUtils.setAccessTime(tmpFileName);
        const newerFileInfo = await IOUtils.stat(tmpFileName);

        ok(
          newerFileInfo.lastAccessed > newFileInfo.lastAccessed,
          "IOUtils::setAccessTime can update the lastAccessed time stamp on the file system"
        );
        is(
          newerStamp,
          newerFileInfo.lastAccessed,
          "IOUtils::setAccessTime returns the updated time stamp."
        );
        is(
          newerFileInfo.lastModified,
          newFileInfo.lastModified,
          "IOUtils::setAccessTime does not change lastModified"
        );
      }

      const tmpDirName = PathUtils.join(PathUtils.tempDir, "test_setModificationTime_and_stat.tmp.d");
      {
        info("Test attempt to setModificationTime a directory");
        await createDir(tmpDirName);

        const oldFileInfo = await IOUtils.stat(tmpDirName);
        await sleep(500);

        const stamp = await IOUtils.setModificationTime(tmpDirName);
        const newFileInfo = await IOUtils.stat(tmpDirName);

        ok(
          newFileInfo.lastModified > oldFileInfo.lastModified,
          "IOUtils::setModificationTime can update the lastModified time stamp on a directory"
        );
        is(
          stamp,
          newFileInfo.lastModified,
          "IOUtils::setModificationTime returns the updated time stamp on a directory"
        );
      }

      await cleanup(tmpFileName, tmpDirName);
    });

    add_task(async function test_setModificationTime_custom_mod_time() {
      const tempFileName = PathUtils.join(PathUtils.tempDir, "test_setModificationTime_custom_mod_time.tmp");
      await createFile(tempFileName);
      const originalInfo = await IOUtils.stat(tempFileName);
      const now = originalInfo.lastModified;

      const oneMinute = 60 * 1000; // milliseconds

      info("Test attempt to set modification time to the future");
      const future = now + oneMinute;
      let newModTime = await IOUtils.setModificationTime(tempFileName, future);
      const futureInfo = await IOUtils.stat(tempFileName);
      Assert.less(originalInfo.lastModified, futureInfo.lastModified, "IOUtils::setModificationTime can set a future modification time for the file");

      is(newModTime, futureInfo.lastModified, "IOUtils::setModificationTime returns the updated time stamp");
      is(newModTime, future, "IOUtils::setModificationTime return value matches the argument value exactly");

      info("Test attempt to set modification time to the past");
      const past = now - 2 * oneMinute;
      newModTime = await IOUtils.setModificationTime(tempFileName, past);
      const pastInfo = await IOUtils.stat(tempFileName);
      Assert.greater(originalInfo.lastModified, pastInfo.lastModified, "IOUtils::setModificationTime can set a past modification time for the file");

      is(newModTime, pastInfo.lastModified, "IOUtils::setModificationTime returns the updated time stamp");
      is(newModTime, past, "IOUtils::setModificationTime return value matches the argument value exactly");

      await cleanup(tempFileName);
    });

    add_task(async function test_stat_btime() {
      if (["Darwin", "WINNT"].includes(Services.appinfo.OS)) {
        const tempFileName = PathUtils.join(PathUtils.tempDir, "test_stat_btime.tmp");
        await createFile(tempFileName);
        const originalInfo = await IOUtils.stat(tempFileName);

        const future = originalInfo.lastModified + 6000;
        await IOUtils.setModificationTime(tempFileName, future);
        const futureInfo = await IOUtils.stat(tempFileName);

        ok(originalInfo.hasOwnProperty("creationTime"), "originalInfo has creationTime field");
        ok(originalInfo.creationTime !== undefined && originalInfo.creationTime !== null, "originalInfo has non-null creationTime");

        ok(futureInfo.hasOwnProperty("creationTime"), "futureInfo has creationTime field");
        ok(futureInfo.creationTime !== undefined && futureInfo.creationTime !== null, "futureInfo has non-null creationTime");

        is(originalInfo.creationTime, futureInfo.creationTime, "creationTime matches");

        await cleanup(tempFileName);
      } else {
        ok(true, `skipping test_stat_btime() on unsupported platform ${Services.appinfo.OS}`);
      }
    });

    add_task(async function test_setModificationTime_failures() {
      info("Test attempt to setModificationTime a non-existing file");
      const notExistsFile = PathUtils.join(PathUtils.tempDir, "test_setModificationTime_not_exists.tmp");

      await Assert.rejects(
        IOUtils.setModificationTime(notExistsFile),
        /Could not set modification time of file\(.*\) because it does not exist/,
        "IOUtils::setModificationTime throws if the target file does not exist"
      );

      info("Test attempt to set modification time to Epoch");
      const tempFileName = PathUtils.join(PathUtils.tempDir, "test_setModificationTime_epoch.tmp");
      await createFile(tempFileName);

      await Assert.rejects(
        IOUtils.setModificationTime(tempFileName, 0),
        /Refusing to set the modification time of file\(.*\) to 0/,
        "IOUtils::setModificationTime cannot set the file modification time to Epoch"
      );

      await cleanup(tempFileName);
    });
  </script>
</head>

<body>
  <p id="display"></p>
  <div id="content" style="display: none"></div>
  <pre id="test"></pre>
</body>

</html>
