# Specl specifications for APIs in lposix.c
#
# Specifications are topographically sorted, with fundamental calls specified
# at the top of the file, and calls with dependencies on correct functioning
# of earlier specifications further down.
#
# If you get a series of failed expectations, fixing the earliest failures
# first will often clear up later failures automatically!

specify posix:
- describe euidaccess:
  - before: f = posix.euidaccess
  - context with bad arguments:
      badargs.diagnose (f, "euidaccess (string, string)")


- describe errno:
  - context with bad arguments:
      badargs.diagnose (posix.errno, "(?int)")


- describe set_errno:
  - context with bad arguments:
      badargs.diagnose (posix.set_errno, "(int)")


- describe getopt:
  - context with bad arguments:
      badargs.diagnose (posix.getopt, "(list, string, ?table, ?int, ?int)")


- describe openpty:
  - context with bad arguments:
      badargs.diagnose (posix.openpty, "openpty ()")


# spawn's tests also cover execx
- describe spawn:
  - before: f = posix.spawn

  - context with bad arguments:
      badargs.diagnose (f, "spawn (table|function, ?any*)")

  - it spawns a command and can detect success:
      expect (f ({"true"})).to_equal (0)
  - it can detect failure:
      expect (f ({"false"})).to_equal (1)
  - it can pass a table of arguments:
      script = [[
        local spawn = require "posix".spawn
        os.exit (spawn {"echo", "foo"} and 0 or 1)
      ]]
      expect (luaproc (script)).to_succeed_with "foo\n"
  - it can pass a function:
      # FIXME: If the function body contains:
      # io.stdout.write ("hello")
      # then the specl results are printed twice; "hello" is not printed
      expect (f (function () end)).to_equal(0)

- describe popen:
  - before:
      popen, pclose = posix.popen, posix.pclose
      read, write, BUFSIZE = posix.read, posix.write, posix.BUFSIZ
  - context with bad arguments:
      badargs.diagnose (popen, "popen (table|function, string, ?function)")

  - it spawns a command and reads its output:
      p = popen ({"echo", "foo"}, "r")
      s = posix.read (p.fd, posix.BUFSIZ)
      pclose (p)
      expect (s).to_equal "foo\n"

  - it spawns a command and writes its input: |
      script = [[
        local posix = require "posix"
        local p = posix.popen ({"wc"}, "w")
        local s = "foo bar baz\n"
        local r = posix.write (p.fd, s)
        posix.pclose (p)
        os.exit (r == #s and 0 or 1)
      ]]
      expect (luaproc (script)).to_succeed_while_matching "^%s*1%s+3%s+12\n$"

- describe popen_pipeline:
  - before:
      popen_pipeline, pclose = posix.popen_pipeline, posix.pclose
      read, write, BUFSIZE = posix.read, posix.write, posix.BUFSIZ
  - context with bad arguments:
      badargs.diagnose (popen_pipeline, "popen_pipeline (table, string, ?function)")

  - it spawns a pipeline and reads its output:
      p = popen_pipeline ({{"echo", "foo", "bar", "baz"}, {"wc"}, {"wc", "-l"}}, "r")
      expect (read (p.fd, posix.BUFSIZ)).to_match ("^%s*1\n")
      pclose (p)

  - it spawns a pipeline and writes its input: |
      script = [[
        local posix = require "posix"
        local s = "foo bar baz\n"
        local p = posix.popen_pipeline ({{"cat"}, {"wc"}}, "w")
        local r = posix.write (p.fd, s)
        posix.pclose (p)
        os.exit (r == #s and 0 or 1)
      ]]
      expect (luaproc (script)).to_succeed_while_matching "^%s*1%s+3%s+12\n$"


- describe timeradd:
  - before:
      tv1 = { tv_sec = 2, tv_usec = 123456 }
      tv2 = { tv_sec = 1, tv_usec = 876543 }

      f = posix.timeradd

  - context with bad arguments:
      badargs.diagnose (f, "timeradd (table, table)")

  - context with systime.gettimeofday timervals:
    - it adds both fields of a timerval:
        expect (f (tv1, tv2)).to_equal { sec = 3, usec = 999999 }
    - it adds both fields of a systime timerval:
        expect (f (tv1, tv2)).to_equal { sec = 3, usec = 999999 }
    - it carries overflow second:
        expect (f (tv2, tv2)).to_equal { sec = 3, usec = 753086 }
    - it does not require 'tv_sec' field:
        expect (f (tv1, {tv_usec = 876543})).
          to_equal { sec = 2, usec = 999999 }
        expect (f ({tv_usec = 123456}, {tv_usec = 876543})).
          to_equal { sec = 0, usec = 999999}
    - it does not require 'tv_usec' field:
        expect (f (tv1, {tv_sec = 1})).
          to_equal { sec = 3, usec = 123456 }
        expect (f ({tv_sec = 2}, {tv_sec = 1})).
          to_equal { sec = 3, usec = 0 }
  - context with legacy timervals:
    - before:
        stv1 = { sec = tv1.tv_sec, usec = tv1.tv_usec }
        stv2 = { sec = tv2.tv_sec, usec = tv2.tv_usec }
    - it adds both fields of a timerval:
        expect (f (stv1, stv2)).to_equal { sec = 3, usec = 999999 }
    - it carries overflow second:
        expect (f (stv2, stv2)).to_equal { sec = 3, usec = 753086 }
    - it does not require 'sec' field:
        expect (f (stv1, {usec = 876543})).to_equal { sec = 2, usec = 999999 }
        expect (f ({usec = 123456}, {usec = 876543})).to_equal { sec = 0, usec = 999999}
    - it does not require 'usec' field:
        expect (f (stv1, {sec = 1})).to_equal { sec = 3, usec = 123456 }
        expect (f ({sec = 2}, {sec = 1})).to_equal { sec = 3, usec = 0 }


- describe timercmp:
  - before:
      tv1 = { tv_sec = 2, tv_usec = 123456 }
      tv2 = { tv_sec = 1, tv_usec = 876543 }
      f = posix.timercmp

  - context with bad arguments:
      badargs.diagnose (f, "timercmp (table, table)")

  - context with systime.gettimeofday timervals:
    - it returns 0 if timers are equal:
        expect (f (tv1, tv1)).to_equal (0)
    - it returns positive integer if second timer is greater than first:
        expect (f (tv1, tv2) > 0).to_be (true)
    - it returns negative integer if first timer is greater than the second:
        expect (f (tv2, tv1) < 0).to_be (true)
  - context with legacy timervals:
    - before:
        stv1 = { sec = tv1.tv_sec, usec = tv1.tv_usec }
        stv2 = { sec = tv2.tv_sec, usec = tv2.tv_usec }
    - it returns 0 if timers are equal:
        expect (f (stv1, stv1)).to_equal (0)
    - it returns positive integer if second timer is greater than first:
        expect (f (stv1, stv2) > 0).to_be (true)
    - it returns negative integer if first timer is greater than the second:
        expect (f (stv2, stv1) < 0).to_be (true)


- describe timersub:
  - before:
      tv1 = { tv_sec = 2, tv_usec = 876543 }
      tv2 = { tv_sec = 1, tv_usec = 123456 }
      f = posix.timersub

  - context with bad arguments:
      badargs.diagnose (f, "timersub (table, table)")

  - context with systime.gettimeofday timervals:
    - it subtracts both fields of a timerval:
        expect (f (tv1, tv2)).to_equal { sec = 1, usec = 753087 }
    - it carries overflow second:
        expect (f (tv2, tv1)).to_equal { sec = -2, usec = 246913 }
    - it does not require 'tv_sec' field:
        expect (f (tv1, {tv_usec = 123456})).to_equal { sec = 2, usec = 753087 }
        expect (f ({tv_usec = 876543}, {tv_usec = 123456})).
          to_equal { sec = 0, usec = 753087}
    - it does not require 'tv_usec' field:
        expect (f (tv1, {tv_sec = 1})).to_equal { sec = 1, usec = 876543 }
        expect (f ({tv_sec = 2}, {tv_sec = 1})).to_equal { sec = 1, usec = 0 }
  - context with legacy timervals:
    - before:
        stv1 = { sec = tv1.tv_sec, usec = tv1.tv_usec }
        stv2 = { sec = tv2.tv_sec, usec = tv2.tv_usec }
    - it subtracts both fields of a timerval:
        expect (f (stv1, stv2)).to_equal { sec = 1, usec = 753087 }
    - it carries overflow second:
        expect (f (stv2, stv1)).to_equal { sec = -2, usec = 246913 }
    - it does not require 'sec' field:
        expect (f (stv1, {usec = 123456})).to_equal { sec = 2, usec = 753087 }
        expect (f ({usec = 876543}, {usec = 123456})).
          to_equal { sec = 0, usec = 753087}
    - it does not require 'usec' field:
        expect (f (stv1, {sec = 1})).to_equal { sec = 1, usec = 876543 }
        expect (f ({sec = 2}, {sec = 1})).to_equal { sec = 1, usec = 0 }


- specify file descriptors:
  - describe fileno:
    - context with bad arguments:
        badargs.diagnose (posix.fileno, "(file)")


  - describe rpoll:
    - context with bad arguments:
        badargs.diagnose (posix.rpoll, "(int, int)")


  - describe poll:
    - context with bad arguments:
        badargs.diagnose (posix.poll, "(table, ?int)")


  - describe close:
    - context with bad arguments:
        badargs.diagnose (posix.close, "(int)")


  - describe dup:
    - context with bad arguments:
        badargs.diagnose (posix.dup, "(int)")


  - describe dup2:
    - context with bad arguments:
        badargs.diagnose (posix.dup2, "(int, int)")


  - describe pipe:
    - before:
        pipe, read, write = posix.pipe, posix.read, posix.write

    - context with bad arguments:
        badargs.diagnose (pipe, "()")

    - it creates a pipe:
        pout, pin = pipe ()
        expect (pout > 0).to_be (true)
        expect (pin > 0).to_be (true)
    - it can buffer characters:
        pout, pin = pipe ()
        data = "test characters"
        write (pin, data)
        expect (read (pout, data:len ())).to_be (data)


  - describe read:
    - context with bad arguments:
        badargs.diagnose (posix.read, "(int, int)")


  - describe write:
    - context with bad arguments:
        badargs.diagnose (posix.write, "(int, string)")


  - describe fcntl:
    - before:
        F_GETLK, F_SETLK, F_SETLKW = posix.F_GETLK, posix.F_SETLK, posix.F_SETLKW
        F_RDLCK, F_WRLCK, F_UNLCK = posix.F_RDLCK, posix.F_WRLCK, posix.F_UNLCK
        SEEK_SET, SEEK_CUR, SEEK_END = posix.SEEK_SET, posix.SEEK_CUR, posix.SEEK_END

        fcntl, typeerrors = init (posix, "fcntl")

    # posix.fcntl diagnoses the third arg differently depending on
    # the value of `cmd`, which `diagnose.badargs` can't express; ergo
    # manual checks here...
    - context with bad arguments:
      - 'it diagnoses missing argument #1':
          expect (fcntl ()).to_raise.any_of (typeerrors (1, "int"))
      - 'it diagnoses argument #1 type not int':
          expect (fcntl (false)).to_raise.any_of (typeerrors (1, "int", "boolean"))
      - 'it diagnoses missing argument #2':
          expect (fcntl (-1)).to_raise.any_of (typeerrors (2, "int"))
      - 'it diagnoses argument #2 type not int':
          expect (fcntl (-1, false)).to_raise.any_of (typeerrors (2, "int", "boolean"))
      - 'it diagnoses missing argument #3 to F_GETLK':
          expect (fcntl (-1, F_GETLK)).to_raise.any_of (typeerrors (3, "table"))
      - 'it diagnoses argument #3 type to F_GETLK not table':
          expect (fcntl (-1, F_GETLK, false)).
            to_raise.any_of (typeerrors (3, "table", "boolean"))
      - 'it diagnoses argument #3 type to non-F_GETLK not int':
          expect (fcntl (-1, 0, false)).
            to_raise.any_of (typeerrors (3, "?int", "boolean"))
      - 'it diagnoses too many arguments':
          expect (fcntl (-1, F_GETLK, {}, false)).to_raise.any_of (typeerrors (4))
          expect (fcntl (-1, 0, -1, false)).to_raise.any_of (typeerrors (4))

    - it has all needed constants:
        expect (type (F_GETLK)).to_be "number"
        expect (type (F_SETLK)).to_be "number"
        expect (type (F_SETLKW)).to_be "number"
        expect (type (F_RDLCK)).to_be "number"
        expect (type (F_WRLCK)).to_be "number"
        expect (type (F_UNLCK)).to_be "number"
        expect (type (SEEK_SET)).to_be "number"
        expect (type (SEEK_CUR)).to_be "number"
        expect (type (SEEK_END)).to_be "number"

    - context when file locking:
      - before:
          mkstemp, open, close = posix.mkstemp, posix.open, posix.close
          fork, wait, errno = posix.fork, posix.wait, posix.errno
          O_RDWR, EAGAIN, EACCES = posix.O_RDWR, posix.EAGAIN, posix.EACCES
          P_CHILD = 0
          SUCCESS = 0
          fd, path = mkstemp "tmpXXXXXX"
          close (fd)
          parent_pid = posix.getpid "pid"

          query_lock = {
            l_type = F_RDLCK, l_whence = SEEK_SET, l_start = 0, l_len = 0,
          }
          write_lock = {
            l_type = F_WRLCK, l_whence = SEEK_SET, l_start = 0, l_len = 0,
          }
      - after:
          os.remove (path)

      - it checks whether lock is possible with F_GETLK:
          fd = open (path, O_RDWR)
          result = fcntl (fd, F_GETLK, query_lock)
          expect (result).to_be (SUCCESS)
          expect (query_lock.l_type).to_be (F_UNLCK)
          close (fd)
      - it can lock file with F_SETLK: |
          parent_fd = open (path, O_RDWR)
          result = fcntl (parent_fd, F_SETLK, write_lock)
          expect (result).to_be (SUCCESS)
          process = fork ()
          if process == P_CHILD then
            child_fd = open (path, O_RDWR)
            result = fcntl (child_fd, F_GETLK, query_lock)
            close (child_fd)
            -- (not sure how to expect () in subprocess)
            if result ~= SUCCESS then
              os.exit (10)
            elseif query_lock.l_pid ~= parent_pid then
              os.exit (11)
            else
              os.exit (12)
            end
          else
            _, _, exit_code = wait (process)
            expect (exit_code).to_be (12)
          end
          close (parent_fd)
      - it returns error if cannot lock file with F_SETLK: |
          parent_fd = open (path, O_RDWR)
          result = fcntl (parent_fd, F_SETLK, write_lock)
          expect (result).to_be (0)
          process = fork ()
          if process == P_CHILD then
            child_fd = open (path, O_RDWR)
            result = fcntl (child_fd, F_SETLK, write_lock)
            close (child_fd)
            -- (not sure how to expect () in subprocess)
            if result == SUCCESS then
              os.exit (10)
            elseif errno () ~= errno (EACCES) and errno () ~= errno (EAGAIN) then
              os.exit (11)
            else
              os.exit (12)
            end
          else
            _, _, exit_code = wait (process)
            expect (exit_code).to_be (12)
          end
          close (parent_fd)


  - describe lseek:
    - before:
        close, lseek, open, read, write =
          posix.close, posix.lseek, posix.open, posix.read, posix.write
        SEEK_SET, SEEK_CUR, SEEK_END =
          posix.SEEK_SET, posix.SEEK_CUR, posix.SEEK_END

    - context with bad arguments:
        badargs.diagnose (lseek, "(int, int, int)")

    - it changes the current position of a file descriptor:
        _, path = posix.mkstemp (template)
        fd = open (path, posix.O_RDWR)
        expect (fd).not_to_be (nil)
        write (fd, "0123456789")
        lseek (fd, 3, SEEK_SET)
        expect (read (fd, 3)).to_be "345"
        lseek (fd, -2, SEEK_CUR)
        expect (read (fd, 3)).to_be "456"
        lseek (fd, -5, SEEK_END)
        expect (read (fd, 3)).to_be "567"
        close (fd)
        os.remove (path)



- specify file system:
  - before:
      # Make and change into a temporary subdirectory where we can
      # control all the contents for self-contained examples.
      link, mkdir, mkdtemp = posix.link, posix.mkdir, posix.mkdtemp
      origwd = posix.getcwd ()
      dir, errmsg = mkdtemp (template)
      mkdir (dir .. "/subdir")
      link ("subdir", dir .. "/soft", true)
      touch (dir .. "/file")
      link (dir .. "/file", dir .. "/hard")
      link ("no such destination", dir .. "/dangling", true)
  - after:
      posix.chdir (origwd)
      rmtmp (dir)


  - describe sync:
    - context with bad arguments:
        badargs.diagnose (posix.sync, "()")


  - describe fsync:
    - context with bad arguments:
        badargs.diagnose (posix.fsync, "(int)")


  - describe fdatasync:
    - context with bad arguments:
        if posix.fdatasync then
          badargs.diagnose (posix.fdatasync, "(int)")
        end


  - describe basename:
    - before:
        basename = posix.basename

    - context with bad arguments:
        badargs.diagnose (basename, "(string)")

    - it returns a path without leading directories:
        expect (basename "/foo/bar").to_be "bar"


  - describe dirname:
    - before:
        dirname = posix.dirname

    - context with bad arguments:
        badargs.diagnose (dirname, "(string)")

    - it return a path without final element:
        expect (dirname "/foo/bar").to_be "/foo"


  - describe dir:
    - context with bad arguments:
        badargs.diagnose (posix.dir, "(?string)")


  - describe glob:
    - before:
        chdir, glob, mkdtemp = posix.chdir, posix.glob, posix.mkdtemp

    - context with bad arguments:
        badargs.diagnose (glob, "(?string)")

    - it matches files in the given directory:
        dir = mkdtemp (template)
        touch (dir .. "/test.1")
        touch (dir .. "/test.2")
        touch (dir .. "/extra_file")
        chdir (dir)
        globlist, errmsg = glob "test.*"
        expect (errmsg).to_be (nil)
        expect (type (globlist)).to_be "table"
        rmtmp (dir)


  - describe files:
    - before:
        files = posix.files

    - context with bad arguments:
        badargs.diagnose (files, "(?string)")

    - it returns a table of files in the given directory:
        t = {}
        for f in files (dir) do
          table.insert (t, f)
        end
        table.sort (t)
        expect (t).to_equal {".", "..", "dangling", "file", "hard", "soft", "subdir"}


  - describe getcwd:
    - context with bad arguments:
        badargs.diagnose (posix.getcwd, "()")


  - describe chdir:
    - before:
        chdir, chmod, getcwd, mkdir, rmdir =
          posix.chdir, posix.chmod, posix.getcwd, posix.mkdir, posix.rmdir
        cwd = getcwd ()
    - after:
        chdir (cwd)
        pcall (rmdir, "x")

    - context with bad arguments:
        badargs.diagnose (chdir, "(string)")

    - it changes to a relative directory:
        thisdir = posix.basename (getcwd ())
        expect (Emsg (chdir ("../" .. thisdir))).
          not_to_contain "No such file or directory"
        expect (Emsg (chdir "..")).not_to_contain "No such file or directory"
    - it changes to an absolute directory:
        expect (Emsg (chdir "/var/tmp/")).
          not_to_contain "No such file or directory"
    - it diagnoses missing directory:
        expect (Emsg (chdir "very_unlikely_to_exist")).
          to_contain "No such file or directory"
    - it diagnoses insufficient permissions:
        mkdir "x"
        chmod ("x", "a-rwx")
        expect (Emsg (chdir "x")).
          to_contain "Permission denied"
        rmdir "x"


  - describe rmdir:
    - before:
        mkdir, rmdir = posix.mkdir, posix.rmdir

    - context with bad arguments:
        badargs.diagnose (rmdir, "(string)")

    - it removes the named directory:
        mkdir "x"
        expect (Emsg (rmdir "x")).not_to_contain "No such file or directory"
    - it diagnoses missing directory:
        expect (Emsg (rmdir ".")).to_contain "Invalid argument"


  - describe unlink:
    - context with bad arguments:
        badargs.diagnose (posix.unlink, "(string)")


  - describe link:
    - before:
        link, stat = posix.link, posix.stat
        touch "xxx"
    - after:
        os.remove "xxx"

    - context with bad arguments:
        badargs.diagnose (link, "(string, string, ?boolean)")

    - it creates hard links:
        expect (Emsg (link ("xxx", "xxx-hard"))).to_be ""
        expect (stat ("xxx-hard", "ino")).to_be (stat ("xxx", "ino"))
        os.remove "xxx-hard"
    - it creates soft links:
        expect (Emsg (link ("xxx", "xxx-soft", true))).to_be ""
        expect (stat ("xxx-soft", "type")).to_be "link"
        os.remove "xxx-soft"

  - describe readlink:
    - before:
        readlink = posix.readlink

    - context with bad arguments:
        badargs.diagnose (readlink, "(string)")

    - it diagnoses missing file: |
        _, err, code = readlink "does not exist!"
        expect (err).to_match "^does not exist!: "
        expect (code).to_be (posix.ENOENT)
    - it diagnoses non-symbolic link: |
        _, err, code = readlink (dir .. "/file")
        expect (err).to_be (dir .. "/file: not a symbolic link")
        expect (code).to_be (posix.EINVAL)
    - it reads the contents of a symbolic link:
        expect (readlink (dir .. "/soft")).to_be "subdir"
    - it reads the contents of a dangling symbolic link:
        expect (readlink (dir .. "/dangling")).to_be "no such destination"


  - describe access:
    - before:
        access = posix.access
        touch "xxx"
    - after:
        os.remove "xxx"

    - context with bad arguments:
        badargs.diagnose (posix.access, "(string, ?string)")

    - it checks whether a file is visible to the real user:
        expect (Emsg (access ("xxx", "f"))).to_be ""
    - it checks whether a file is readable by the real user:
        expect (Emsg (access ("xxx", "r"))).to_be ""
    - it checks whether a file is writable by the real user:
        expect (Emsg (access ("xxx", "w"))).to_be ""
    - "it defaults to 'f' with no mode argument":
        expect (access ("xxx")).to_be (access ("xxx", "f"))
    - it diagnoses missing files:
        os.remove "xxx"
        expect (Emsg (access "xxx")).to_contain "No such file or directory"


  - describe chown:
    - context with bad arguments:
        badargs.diagnose (posix.chown, "(string, ?string|int, ?string|int)")


  - describe utime:
    - before:
        stat, utime = posix.stat, posix.utime
        touch "xxx"
    - after:
        os.remove "xxx"

    - context with bad arguments:
        badargs.diagnose (posix.utime, "(string, ?int, ?int)")

    - it sets the last file modification time:
        mtime = stat ("/etc", "mtime")
        expect (stat ("xxx", "mtime")).not_to_equal (mtime)
        expect (Emsg (utime ("xxx", mtime))).to_be ""
        expect (stat ("xxx", "mtime")).to_equal (mtime)
    - it sets the last file access time:
        atime = stat ("/etc", "atime")
        expect (stat ("xxx", "atime")).not_to_equal (atime)
        expect (Emsg (utime ("xxx", nil, atime))).to_be ""
        expect (stat ("xxx", "atime")).to_equal (atime)


- specify process management:
  - describe nice:
    - before:
        nice = posix.nice

    - context with bad arguments:
        badargs.diagnose (nice, "(int)")

    - it adjusts the process priority:
        old = nice (1)
        expect (old).not_to_be (nil)
        new = nice (2)
        expect (new).to_be (old + 2)


  - describe fork:
    # NOTE: Calling expect in a child process does not report results
    #       back to parent, so we send test data over a pipe.
    - before:
        nice, execp, fork, getpid, getppid, wait =
          posix.nice, posix.execp, posix.fork, posix.getpid, posix.getppid, posix.wait
        _exit, close, pipe, read, write =
          posix._exit, posix.close, posix.pipe, posix.read, posix.write
        P_CHILD = 0

    - context with bad arguments:
        badargs.diagnose (fork, "()")

    - it copies itself to a new child process: |
        r, w = pipe ()
        process, status = fork (), 67
        if process == P_CHILD then
          close (r) -- close unused read end
          -- write parent pid and child pid to shared pipe.
          write (w, tostring (getppid ()).."\n")
          write (w, tostring (getpid "pid").."\n")
          close (w)
          _exit (status)
        else
          posix.close (w) -- close unused write end
          p, msg, ret = wait (process)
          expect (p).to_be (process)
          expect (msg).to_be "exited"
          expect (ret).to_be (status)
          -- check pids from child process.
          buf = posix.read (r, 1024)
          cppid, cpid = string.match (buf, "(%d+)\n(%d+)\n")
          close (r)
          expect (cppid).to_be (tostring (getpid "pid"))
          expect (cpid).to_be (tostring (process))
        end


  - describe _exit:
    - context with bad arguments:
        badargs.diagnose (posix. _exit, "(int)")


  - describe wait:
    - context with bad arguments:
        badargs.diagnose (posix.wait, "(?int, ?int)")


  - describe setpid:
    - before:
        setpid, typeerrors = init (posix, "setpid")

    - context with bad arguments:
      - 'it diagnoses missing argument #1':
          expect (setpid ()).to_raise.any_of (typeerrors (1, "string"))
      - 'it diagnoses argument #1 type not string':
          expect (setpid (false)).to_raise.any_of (typeerrors (1, "string", "boolean"))
      - 'it diagnoses argument #1 invalid option': |
          expect (setpid "fubar").to_raise.any_of {
            "bad argument #1 to '?' (invalid id option 'f')",
            "bad argument #1 to 'setpid' (invalid id option 'f')",
          }
      - 'it diagnoses missing argument #2':
          expect (setpid "p").to_raise.any_of (typeerrors (2, "int"))
      - 'it diagnoses argument #2 type not int':
          expect (setpid ("p", false)).
            to_raise.any_of (typeerrors (2, "int", "boolean"))
      - 'it diagnoses missing argument #3':
          expect (setpid ("p", 0)).to_raise.any_of (typeerrors (3, "int"))
      - 'it diagnoses argument #3 type not int':
          expect (setpid ("p", 0, false)).
            to_raise.any_of (typeerrors (3, "int", "boolean"))
      - it diagnoses too many arguments:
          expect (setpid ("p", 0, 0, false)).to_raise.any_of (typeerrors (4))
          expect (setpid ("u", 0, false)).to_raise.any_of (typeerrors (3))


  - describe sleep:
    - context with bad arguments:
        badargs.diagnose (posix.sleep, "(int)")


  - describe sched_setscheduler:
    - context with bad arguments:
        if posix.sched_setscheduler then
          badargs.diagnose (posix.sched_setscheduler, "(?int, ?int, ?int)")
        end


  - describe sched_getscheduler:
    - context with bad arguments:
        if posix.sched_getscheduler then
          badargs.diagnose (posix.sched_getscheduler, "(?int)")
        end



- specify terminal handling:
  - describe ttyname:
    - before:
        ttyname = posix.ttyname

    - context with bad arguments:
        badargs.diagnose (posix.ttyname, "(?int)")

    - it takes a file descriptor argument:
        expect (ttyname (2)).to_contain.any_of {"console", "pts", "tty"}
    - it returns a string:
        expect (type (ttyname (1))).to_be "string"
    - it defaults the first argument to 0:
        expect (ttyname ()).to_be (ttyname (0))


  - describe ctermid:
    - before:
        ctermid = posix.ctermid

    - context with bad arguments:
        badargs.diagnose (posix.ctermid, "()")

    - it returns the pathname of the controlling terminal:
        expect (ctermid ()).to_match.any_of {"/.*pts.*", "/.*tty.*"}


  - describe isatty:
    - context with bad arguments:
        badargs.diagnose (posix.isatty, "(int)")


  - describe tcsetattr:
    - context with bad arguments:
        badargs.diagnose (posix.tcsetattr, "(int, int, table)")


  - describe tcgetattr:
    - context with bad arguments:
        badargs.diagnose (posix.tcgetattr, "(int)")


  - describe tcsendbreak:
    - context with bad arguments:
        badargs.diagnose (posix.tcsendbreak, "(int, int)")


  - describe tcdrain:
    - context with bad arguments:
        badargs.diagnose (posix.tcdrain, "(int)")


  - describe tcflush:
    - context with bad arguments:
        badargs.diagnose (posix.tcflush, "(int, int)")


  - describe tcflow:
    - context with bad arguments:
        badargs.diagnose (posix.tcflow, "(int, int)")



- specify user management:
  - describe getlogin:
    - context with bad arguments:
        badargs.diagnose (posix.getlogin, "()")


  - describe getgroups:
    - context with bad arguments:
        if posix.getgroups then
          badargs.diagnose (posix.getgroups, "()")
        end


  - describe crypt:
    - before:
        crypt = posix.crypt
        key, salt = "hello", "pl"

    - context with bad arguments:
        if posix.crypt then
          badargs.diagnose (posix.crypt, "(string, string)")
        end

    - it can perform repeatable one-way hashing:
        hash = crypt (key, salt)
        expect (crypt (key, salt)).to_be (hash)
    - it encrypts differently for a different salt:
        expect (crypt (key, salt)).not_to_equal (crypt (key, "/."))
