# 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:
expect (f ({"echo", "foo"})).to_equal(0)
- 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:
p = popen ({"wc"}, "w")
s = "foo bar baz"
expect (write (p.fd, s)).to_equal (#s)
pclose (p)
- 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:
s = "foo bar baz"
p = popen_pipeline ({{"cat"}, {"wc"}}, "w")
expect (write (p.fd, s)).to_equal (#s)
pclose (p)
- describe timeradd:
- before:
tv1 = { sec = 2, usec = 123456 }
tv2 = { sec = 1, usec = 876543 }
f = posix.timeradd
- context with bad arguments:
badargs.diagnose (f, "timeradd (table, table)")
- it adds both fields of a 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 'sec' field:
expect (f (tv1, {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 (tv1, {sec = 1})).to_equal { sec = 3, usec = 123456 }
expect (f ({sec = 2}, {sec = 1})).to_equal { sec = 3, usec = 0 }
- describe timercmp:
- before:
tv1 = { sec = 2, usec = 123456 }
tv2 = { sec = 1, usec = 876543 }
f = posix.timercmp
- context with bad arguments:
badargs.diagnose (f, "timercmp (table, table)")
- 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)
- describe timersub:
- before:
tv1 = { sec = 2, usec = 876543 }
tv2 = { sec = 1, usec = 123456 }
f = posix.timersub
- context with bad arguments:
badargs.diagnose (f, "timersub (table, table)")
- 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 'sec' field:
expect (f (tv1, {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 (tv1, {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, "/."))