# 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, "/."))