package jnr.posix;

import jnr.constants.platform.Access;
import jnr.constants.platform.Fcntl;
import jnr.constants.platform.Errno;
import jnr.constants.platform.OpenFlags;
import jnr.ffi.Pointer;
import jnr.posix.util.Platform;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

public class FileTest {
    private static POSIX posix;

    @BeforeClass
    public static void setUpClass() throws Exception {
        posix = POSIXFactory.getPOSIX(new DummyPOSIXHandler(), true);
    }

    @Test
    public void utimesTest() throws Throwable {
        // FIXME: On Windows this is working but providing wrong numbers and therefore getting wrong results.
        if (!Platform.IS_WINDOWS) {
            File f = File.createTempFile("utimes", null);

            int rval = posix.utimes(f.getAbsolutePath(), new long[]{800, 200}, new long[]{900, 300});
            assertEquals("utimes did not return 0", 0, rval);

            FileStat stat = posix.stat(f.getAbsolutePath());

            assertEquals("atime seconds failed", 800, stat.atime());
            assertEquals("mtime seconds failed", 900, stat.mtime());

            // The nano secs part is available in other stat implementations. We really just want to verify that the
            // nsec portion of the timeval is passed through to the POSIX call.
            // Mac seems to fail this test sporadically.
            if (stat instanceof NanosecondFileStat && !Platform.IS_MAC) {
                NanosecondFileStat linuxStat = (NanosecondFileStat) stat;

                assertEquals("atime useconds failed", 200000, linuxStat.aTimeNanoSecs());
                assertEquals("mtime useconds failed", 300000, linuxStat.mTimeNanoSecs());
            }

            f.delete();
        }
    }

    @Test
    public void utimesDefaultValuesTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            File f = File.createTempFile("utimes", null);

            long oldTime = posix.stat(f.getAbsolutePath()).mtime();
            Thread.sleep(2000);

            int rval = posix.utimes(f.getAbsolutePath(), null, null);
            assertEquals("utimes did not return 0", 0, rval);

            FileStat stat = posix.stat(f.getAbsolutePath());

            assertTrue("atime failed", stat.atime() > oldTime);
            assertTrue("mtime failed", stat.mtime() > oldTime);

            f.delete();
        }
    }

    @Test
    public void utimesPointerTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            File f = File.createTempFile("utimes", null);

            Pointer times = jnr.ffi.Runtime.getSystemRuntime().getMemoryManager().allocateDirect(8 * 4); // long[2][2] == 4 longs.
            times.putLong(0, 800);
            times.putLong(8, 200);
            times.putLong(16, 900);
            times.putLong(24, 300);

            int rval = posix.utimes(f.getAbsolutePath(), times);
            assertEquals("utimes did not return 0", 0, rval);

            FileStat stat = posix.stat(f.getAbsolutePath());

            assertEquals("atime seconds failed", 800, stat.atime());
            assertEquals("mtime seconds failed", 900, stat.mtime());

            // The nano secs part is available in other stat implementations.  We use Linux x86_64 because it's
            // representative.  We really just want to verify that the usec portion of the timeval is passed throug
            // to the POSIX call.
            if (hasNanosecondPrecision(stat)) {
                NanosecondFileStat linuxStat = (NanosecondFileStat) stat;

//                assertEquals("atime useconds failed", 200000, linuxStat.aTimeNanoSecs());
                assertEquals("mtime useconds failed", 300000, linuxStat.mTimeNanoSecs());
            }

            f.delete();
        }
    }

    @Test
    public void utimensatAbsolutePath() throws Throwable {
        File file = File.createTempFile("utimensat", null);
        utimensat(file, 0);
    }

    @Test
    public void utimensatRelativePath() throws Throwable {
        String path = "utimensat";
        File file = new File(path);
        file.createNewFile();
        int parentFd = posix.open(".", OpenFlags.O_DIRECTORY.intValue(), 0444);
        utimensat(file, parentFd);
    }

    @Test
    public void futimens() throws Throwable {
        File file = File.createTempFile("futimens", null);
        FileStat fileStat = posix.stat(file.getPath());
        if (!hasNanosecondPrecision(fileStat)) {
            file.delete();
            return;
        }

        long atimeSeconds = fileStat.atime()+1;
        long mtimeSeconds = fileStat.mtime()-1;
        long atimeNanoSeconds = 123456789;
        long mtimeNanoSeconds = 135;
        int fd = posix.open(file.getAbsolutePath(), OpenFlags.O_RDWR.intValue(), 0444);
        posix.futimens(fd,
                new long[] {atimeSeconds, atimeNanoSeconds},
                new long[] {mtimeSeconds, mtimeNanoSeconds});
        assertStatNanoSecond(file, atimeSeconds, atimeNanoSeconds, mtimeSeconds, mtimeNanoSeconds);
    }

    @Test
    public void lutimesTest() throws Throwable {
        // FIXME: On Windows this is working but providing wrong numbers and therefore getting wrong results.
        if (!Platform.IS_WINDOWS) {
            File f1 = File.createTempFile("lutimes", null);
            File f2 = new File(f1.getParentFile(), "lutimes-link");
            posix.symlink(f1.getAbsolutePath(), f2.getAbsolutePath());

            int rval = posix.utimes(f1.getAbsolutePath(), new long[]{800, 0}, new long[]{900, 0});
            assertEquals("utimes did not return 0", 0, rval);

            rval = posix.lutimes(f2.getAbsolutePath(), new long[]{1800, 0}, new long[]{1900, 0});
            assertEquals("lutimes did not return 0", 0, rval);

            FileStat stat = posix.stat(f1.getAbsolutePath());
            assertEquals("atime seconds failed", 800, stat.atime());
            assertEquals("mtime seconds failed", 900, stat.mtime());

            stat = posix.lstat(f2.getAbsolutePath());
            assertEquals("atime seconds failed", 1800, stat.atime());
            assertEquals("mtime seconds failed", 1900, stat.mtime());

            f1.delete();
            f2.delete();
        }
    }

    @Test
    public void futimeTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            File f = File.createTempFile("jnr-posix-futime", "tmp");
            long oldTime = posix.stat(f.getAbsolutePath()).mtime();
            Thread.sleep(2000);
            int fd = posix.open(f.getAbsolutePath(), OpenFlags.O_RDWR.intValue(), 0666);
            int rval = posix.futimes(fd, null, null);
            assertEquals("futime did not return 0", 0, rval);
            long newTime = posix.stat(f.getAbsolutePath()).mtime();
            f.delete();
            assertTrue("mtime failed", newTime > oldTime);
        }
    }

    @Test
    public void linkTest() throws Throwable {
        File f1 = File.createTempFile("utime", null);
        File f2 = new File(f1.getAbsolutePath() + "link");
        int rval = posix.link(f1.getAbsolutePath(), f2.getAbsolutePath());
        assertEquals("link did not return 0", 0, rval);
        assertTrue("Link was not created", f2.exists());
        f1.delete();
        f2.delete();
    }

    @Test
    public void mkdirRelativeTest() throws Throwable {
        File dir = new File("tmp");
        int rval = posix.mkdir(dir.getPath(), 0);
        assertEquals("mkdir did not return 0", 0, rval);
        assertTrue("Directory was not created", dir.exists());
        dir.delete();
    }

    @Test
    public void mkdirAbsoluteTest() throws Throwable {
        File dir = new File("tmp");
        int rval = posix.mkdir(dir.getAbsolutePath(), 0);
        assertEquals("mkdir did not return 0", 0, rval);
        assertTrue("Directory was not created", dir.exists());
        dir.delete();
    }
    
    @Test
    public void flockTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            File tmp = File.createTempFile("flockTest", "tmp");
            RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
            RandomAccessFile raf2 = new RandomAccessFile(tmp, "rw");
            FileChannel fc = raf.getChannel();
            FileChannel fc2 = raf2.getChannel();
            FileDescriptor FD = JavaLibCHelper.getDescriptorFromChannel(fc);
            FileDescriptor FD2 = JavaLibCHelper.getDescriptorFromChannel(fc2);
            int fd = JavaLibCHelper.getfdFromDescriptor(FD);
            int fd2 = JavaLibCHelper.getfdFromDescriptor(FD2);

            assertEquals(0, posix.flock(fd, 1)); // LOCK_SH
            assertEquals(0, posix.flock(fd, 8)); // LOCK_UN
            assertEquals(0, posix.flock(fd, 2)); // LOCK_EX
            assertEquals(-1, posix.flock(fd2, 2 | 4)); // LOCK_EX | LOCK_NB
            assertEquals(0, posix.flock(fd, 8)); // LOCK_UN
        }
    }

    @Test
    public void dupTest() throws Throwable {
        File tmp = File.createTempFile("dupTest", "tmp");
        RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
        FileChannel fileChannel = raf.getChannel();
        int fileDescriptor = getFdFromDescriptor(JavaLibCHelper.getDescriptorFromChannel(fileChannel));

        byte[] outContent = "foo".getBytes();

        FileDescriptor newFileDescriptor = toDescriptor(posix.dup(fileDescriptor));

        new FileOutputStream(toDescriptor(fileDescriptor)).write(outContent);
        raf.seek(0);

        byte[] inContent = new byte[outContent.length];
        new FileInputStream(newFileDescriptor).read(inContent, 0, 3);

        assertArrayEquals(inContent, outContent);
    }

    public static final int SEEK_SET = 0;

    @Test
    public void dup2Test() throws Throwable {
        File tmp = File.createTempFile("dupTest", "tmp");
        int oldFd = posix.open(tmp.getAbsolutePath(), OpenFlags.O_RDWR.intValue(), 0666);
        int newFd = 1000;

        byte[] outContent = "foo".getBytes();

        // NB: Windows differs a bit from POSIX's return value.  Both will return -1 on failure, but Windows will return
        // 0 upon success, while POSIX will return the new FD.  Since we already know what the FD will be if the call
        // is successful, it's easy to make code that works with both forms.  But it is something to watch out for.
        assertNotEquals(-1, posix.dup2(oldFd, newFd));
        FileDescriptor newFileDescriptor = toDescriptor(newFd);

        new FileOutputStream(toDescriptor(oldFd)).write(outContent);
        posix.lseek(newFd, SEEK_SET, 0);

        byte[] inContent = new byte[outContent.length];
        new FileInputStream(newFileDescriptor).read(inContent, 0, 3);

        assertArrayEquals(inContent, outContent);
    }

    @Test
    public void fcntlDupfdTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            File tmp = File.createTempFile("fcntlTest", "tmp");
            int fd = posix.open(tmp.getPath(), OpenFlags.O_RDWR.intValue(), 0444);

            byte[] outContent = "foo".getBytes();

            // NOTE: This test used to call without the third argument, but this leads to undefined behavior.
            // See https://github.com/jnr/jnr-posix/issues/144
            int newFd = posix.fcntl(fd, Fcntl.F_DUPFD, 0);

            new FileOutputStream(JavaLibCHelper.toFileDescriptor(fd)).write(outContent);
            posix.lseek(fd, SEEK_SET, 0);

            byte[] inContent = new byte[outContent.length];
            new FileInputStream(JavaLibCHelper.toFileDescriptor(newFd)).read(inContent, 0, 3);

            assertArrayEquals(inContent, outContent);
        }
    }

    @Test
    public void fcntlDupfdWithArgTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            File tmp = File.createTempFile("dupTest", "tmp");
            int oldFd = JavaLibCHelper.getfdFromDescriptor(JavaLibCHelper.getDescriptorFromChannel(
                    new RandomAccessFile(tmp, "rw").getChannel()));
            int newFd = JavaLibCHelper.getfdFromDescriptor(JavaLibCHelper.getDescriptorFromChannel(
                    new RandomAccessFile(tmp, "rw").getChannel()));

            byte[] outContent = "foo".getBytes();

            int expectedFd = 100;
            int dupFd = posix.fcntl(oldFd, Fcntl.F_DUPFD, expectedFd);

            new FileOutputStream(JavaLibCHelper.toFileDescriptor(newFd)).write(outContent);

            byte[] inContent = new byte[outContent.length];
            new FileInputStream(JavaLibCHelper.toFileDescriptor(dupFd)).read(inContent, 0, 3);

            assertTrue(dupFd >= expectedFd);
            assertArrayEquals(inContent, outContent);
        }
    }

    @Test
    public void closeTest() throws Throwable {
        File tmp = File.createTempFile("closeTest", "tmp");
        int fd = getFdFromDescriptor(JavaLibCHelper.getDescriptorFromChannel(new RandomAccessFile(tmp, "rw").getChannel()));

        int result;

        result = posix.close(fd);
        assertEquals(0, result);

        result = posix.close(fd);
        assertEquals(-1, result);

        // TODO (nirvdrum 06-May-15) We're not getting the correct errno value from Windows currently, so we need to skip this test.
        if (!Platform.IS_WINDOWS) {
            assertEquals(Errno.EBADF.intValue(), posix.errno());
        }
    }

    @Test
    public void unlinkTestNonWindows() throws Throwable {
        if (! Platform.IS_WINDOWS) {
            File tmp = File.createTempFile("unlinkTest", "tmp");
            RandomAccessFile raf = new RandomAccessFile(tmp, "rw");

            raf.write("hello".getBytes());

            int res = posix.unlink(tmp.getCanonicalPath());

            assertEquals(0, res);
            assertFalse(tmp.exists());

            raf.write("world".getBytes());
            raf.seek(0);

            byte[] actual = new byte[10];
            raf.read(actual);

            assertArrayEquals("helloworld".getBytes(), actual);
        }
    }

    @Test
    public void openTest() throws Throwable {
        int fd = posix.open("pom.xml", 0, 0666);

        assertNotEquals(-1, fd);

        int result = posix.close(fd);
        assertEquals(0, result);

        result = posix.close(fd);
        assertEquals(-1, result);

        fd = posix.open("jnr-posix-filetest.txt", OpenFlags.O_CREAT.intValue() | OpenFlags.O_RDWR.intValue(), 0600);

        assertEquals(0600, posix.stat("jnr-posix-filetest.txt").mode() & 0777);

        result = posix.close(fd);
        assertEquals(0, result);

        new File("jnr-posix-filetest.txt").delete();
    }

    @Test
    public void writeTest() throws Throwable {
        String str = "To thine own self be true";
        File tmp = File.createTempFile("writeTest", "tmp");
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());

        int fd = posix.open(tmp.getAbsolutePath(), 1, 066);
        posix.write(fd, buffer, str.length());
        posix.close(fd);

        RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
        assertEquals(raf.readLine(), new String(buffer.array()));
        posix.unlink(tmp.getAbsolutePath());
    }

    @Test
    public void pwriteTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            String str = "Now is the winter of our discontent";
            File tmp = File.createTempFile("pwriteTest", "tmp");
            RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
            raf.write(str.getBytes());
            raf.close();

            String str2 = "summer";
            ByteBuffer buffer = ByteBuffer.wrap(str2.getBytes());

            int fd = posix.open(tmp.getAbsolutePath(), 1, 066);
            posix.pwrite(fd, buffer, str2.length(), 11);
            posix.close(fd);

            raf = new RandomAccessFile(tmp, "r");
            assertEquals(raf.readLine(), "Now is the summer of our discontent");
            posix.unlink(tmp.getAbsolutePath());
        }
    }

    @Test
    public void truncateTest() throws Throwable {
        String str = "Beware the Jabberwock, my son!";
        File tmp = File.createTempFile("truncateTest", "tmp");
        RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
        raf.write(str.getBytes());
        raf.close();

        int shorterLength = str.length() - 10;
        int longerLength = shorterLength * 2;

        // Truncate should make a file shorter if the new length is less than the old length.
        posix.truncate(tmp.getAbsolutePath(), shorterLength);
        assertEquals(shorterLength, tmp.length());

        // Truncate should extend a file if the new length is greater than the old length.
        posix.truncate(tmp.getAbsolutePath(), longerLength);
        assertEquals(longerLength, tmp.length());
    }

    @Test
    public void ftruncateTest() throws Throwable {
        String str = "Beware the Jabberwock, my son!";
        File tmp = File.createTempFile("ftruncateTest", "tmp");
        RandomAccessFile raf = new RandomAccessFile(tmp, "rw");
        raf.write(str.getBytes());
        raf.close();

        int fd = posix.open(tmp.getAbsolutePath(), OpenFlags.O_RDWR.intValue(), 0666);
        posix.ftruncate(fd, 21);
        posix.lseekLong(fd, 0, 0);
        byte[] buf = new byte[21];
        int read = posix.read(fd, buf, 31);
        assertEquals(21, read);
        assertArrayEquals(buf, "Beware the Jabberwock".getBytes());
    }

    @Test
    public void fcntlIntTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            int[] fds = new int[2];
            int ret = posix.pipe(fds);
            assertEquals(0, ret);
            int flags = posix.fcntlInt(fds[0], Fcntl.F_GETFD, 0);
            posix.fcntlInt(fds[0], Fcntl.F_SETFD, flags | 1); // FD_CLOEXEC
            assertEquals(flags | 1, posix.fcntlInt(fds[0], Fcntl.F_GETFD, 0));
        }
    }

    @Test
    public void fchmodTest() throws IOException {
        if (!Platform.IS_WINDOWS) {
            File tmp = File.createTempFile("jnr-posix-chmod-test", "tmp");
            int fd = posix.open(tmp.getAbsolutePath(), OpenFlags.O_RDWR.intValue(), 0600);

            assertEquals("chmod: ", 0, posix.fchmod(fd, 0));
            assertEquals("chmod: ", 0, posix.fchmod(fd, 0777));
            tmp.delete();
        }
    }


    @Test
    public void renameTest() throws IOException {
        File oldFile = File.createTempFile("jnr-posix-rename-test", "tmp");
        File newFile = new File(oldFile.getParent() + File.separatorChar + "jnr-posix-rename-test-new");

        assertTrue(oldFile.exists());
        assertFalse(newFile.exists());

        posix.rename(oldFile.getCanonicalPath(), newFile.getCanonicalPath());

        assertFalse(oldFile.exists());
        assertTrue(newFile.exists());

        newFile.delete();
    }

    @Test
    public void accessTest() throws IOException {
        File tmp = File.createTempFile("jnr-posix-access-test", "tmp");

        jnr.constants.platform.Access.F_OK.intValue();

        // Set permissions to read-only and verify we don't have permissions to write.
        String canonicalPath = tmp.getCanonicalPath();
        assertEquals("chmod 0400: ", 0, posix.chmod(canonicalPath, 0400));
        assertEquals("access " + canonicalPath + " for write: ", -1, posix.access(canonicalPath, Access.W_OK.intValue()));
        assertEquals("access " + canonicalPath + " for read: ", 0, posix.access(canonicalPath, Access.R_OK.intValue()));

        // Reset the permissions to read-write and verify we now have permissions to write.
        assertEquals("chmod 0600: ", 0, posix.chmod(canonicalPath, 0600));
        assertEquals("access " + canonicalPath + " for write: ", 0, posix.access(canonicalPath, Access.W_OK.intValue()));
        assertEquals("access " + canonicalPath + " for read: ", 0, posix.access(canonicalPath, Access.R_OK.intValue()));

        // F_OK just checks the file exists and should pass.
        assertEquals("access " + canonicalPath + " exists: ", 0, posix.access(canonicalPath, Access.F_OK.intValue()));
    }

    @Test
    public void readlinkTest() throws IOException {
        if (!Platform.IS_WINDOWS) {
            File file = File.createTempFile("jnr-pøsix-réådlînk-tést", "tmp");
            File link = new File(file.getAbsolutePath() + "link");

            posix.symlink(file.getAbsolutePath(), link.getAbsolutePath());

            byte[] fileBytes = file.getAbsolutePath().getBytes();
            int fileLength = fileBytes.length;

            byte[] buffer = new byte[fileLength];

            assertEquals(fileLength, posix.readlink(link.getAbsolutePath(), buffer, buffer.length));

            assertArrayEquals(fileBytes, buffer);

            link.delete();
            file.delete();
        }
    }

    @Test
    public void readlinkByteBufferTest() throws IOException {
        if (!Platform.IS_WINDOWS) {
            File file = File.createTempFile("jnr-pøsix-réådlînk-tést", "tmp");
            File link = new File(file.getAbsolutePath() + "link");

            posix.symlink(file.getAbsolutePath(), link.getAbsolutePath());

            byte[] fileBytes = file.getAbsolutePath().getBytes();
            int fileLength = fileBytes.length;
            ByteBuffer buffer = ByteBuffer.allocate(fileLength);

            assertEquals(fileLength, posix.readlink(link.getAbsolutePath(), buffer, buffer.capacity()));

            assertArrayEquals(buffer.array(), file.getAbsolutePath().getBytes());

            link.delete();
            file.delete();
        }
    }

    @Test
    public void readlinkPointerTest() throws IOException {
        if (!Platform.IS_WINDOWS) {
            File file = File.createTempFile("jnr-pøsix-réådlînk-tést", "tmp");
            File link = new File(file.getAbsolutePath() + "link");

            int bufSize = 1024;

            Pointer buffer = jnr.ffi.Runtime.getSystemRuntime().getMemoryManager().allocateDirect(bufSize);

            posix.symlink(file.getAbsolutePath(), link.getAbsolutePath());
            
            byte[] fileBytes = file.getAbsolutePath().getBytes();
            int fileLength = fileBytes.length;

            assertEquals(fileLength, posix.readlink(link.getAbsolutePath(), buffer, bufSize));

            assertEquals(file.getAbsolutePath(), buffer.getString(0, fileLength, Charset.defaultCharset()));

            link.delete();
            file.delete();
        }
    }

    @Test
    public void mkfifoTest() throws Throwable {
        if (!Platform.IS_WINDOWS) {
            File tmp = File.createTempFile("mkfifoTest", "tmp");
            tmp.deleteOnExit();

            int ret = posix.mkfifo(tmp.getAbsolutePath(), 0666);

            FileInputStream fis = new FileInputStream(tmp);
            FileOutputStream fos = new FileOutputStream(tmp);

            byte[] content = "hello".getBytes();

            fos.write(content);
            fis.read(content);

            assertArrayEquals("hello".getBytes(), content);
        }
    }

    @Test
    public void lseekTest() throws Throwable {
        if (Platform.IS_MAC) {
            int fd = posix.open("/dev/zero", 0, 0);

            // use 2^33 to ensure we're out of int range
            long offset = (long) Math.pow(2, 33);
            long seek = posix.lseekLong(fd, offset, 0);

            assertEquals(seek, offset);
        }
    }

    private int getFdFromDescriptor(FileDescriptor descriptor) {
        if (Platform.IS_WINDOWS) {
            HANDLE handle = JavaLibCHelper.gethandle(descriptor);
            return ((WindowsLibC) posix.libc())._open_osfhandle(handle, 0);
        } else {
            return JavaLibCHelper.getfdFromDescriptor(descriptor);
        }
    }

    private FileDescriptor toDescriptor(int fd) {
        if (Platform.IS_WINDOWS) {
            HANDLE handle = ((WindowsLibC) posix.libc())._get_osfhandle(fd);
            return JavaLibCHelper.toFileDescriptor(handle);
        } else {
            return JavaLibCHelper.toFileDescriptor(fd);
        }
    }

    private boolean hasNanosecondPrecision(FileStat fileStat) {
        return fileStat instanceof NanosecondFileStat && !Platform.IS_MAC;
    }

    private void utimensat(File file, int parentFd) {
        String path = file.getAbsolutePath();
        FileStat fileStat = posix.stat(path);
        if (!hasNanosecondPrecision(fileStat)) {
            file.delete();
            return;
        }

        long atimeSeconds = fileStat.atime()+2;
        long mtimeSeconds = fileStat.mtime()-2;
        long atimeNanoSeconds = 123456789;
        long mtimeNanoSeconds = 135;
        posix.utimensat(parentFd,
                path,
                new long[] {atimeSeconds, atimeNanoSeconds},
                new long[] {mtimeSeconds, mtimeNanoSeconds},
                0);
        assertStatNanoSecond(file, atimeSeconds, atimeNanoSeconds, mtimeSeconds, mtimeNanoSeconds);
    }

    private void assertStatNanoSecond(File file, long atimeSeconds, long atimeNanoSeconds, long mtimeSeconds, long mtimeNanoSeconds) {
        NanosecondFileStat nanosecondFileStat = (NanosecondFileStat) posix.stat(file.getPath());
        assertEquals("Access timestamp should be updated", atimeSeconds, nanosecondFileStat.atime());
        assertEquals("Modification timestamp should be updated", mtimeSeconds, nanosecondFileStat.mtime());
        assertEquals("Access time precision should be in ns", atimeNanoSeconds, nanosecondFileStat.aTimeNanoSecs());
        assertEquals("Modification time precision should be in ns", mtimeNanoSeconds, nanosecondFileStat.mTimeNanoSecs());
        file.delete();
    }
}
