1-- $Id: testes/files.lua $
  2-- See Copyright Notice in file all.lua
  3
  4local debug = require "debug"
  5
  6local maxint = math.maxinteger
  7
  8assert(type(os.getenv"PATH") == "string")
  9
 10assert(io.input(io.stdin) == io.stdin)
 11assert(not pcall(io.input, "non-existent-file"))
 12assert(io.output(io.stdout) == io.stdout)
 13
 14
 15local function testerr (msg, f, ...)
 16  local stat, err = pcall(f, ...)
 17  return (not stat and string.find(err, msg, 1, true))
 18end
 19
 20
 21local function checkerr (msg, f, ...)
 22  assert(testerr(msg, f, ...))
 23end
 24
 25
 26-- cannot close standard files
 27assert(not io.close(io.stdin) and
 28       not io.stdout:close() and
 29       not io.stderr:close())
 30
 31-- cannot call close method without an argument (new in 5.3.5)
 32checkerr("got no value", io.stdin.close)
 33
 34
 35assert(type(io.input()) == "userdata" and io.type(io.output()) == "file")
 36assert(type(io.stdin) == "userdata" and io.type(io.stderr) == "file")
 37assert(not io.type(8))
 38local a = {}; setmetatable(a, {})
 39assert(not io.type(a))
 40
 41assert(getmetatable(io.input()).__name == "FILE*")
 42
 43local a,b,c = io.open('xuxu_nao_existe')
 44assert(not a and type(b) == "string" and type(c) == "number")
 45
 46a,b,c = io.open('/a/b/c/d', 'w')
 47assert(not a and type(b) == "string" and type(c) == "number")
 48
 49local file = os.tmpname()
 50local f, msg = io.open(file, "w")
 51if not f then
 52  (Message or print)("'os.tmpname' file cannot be open; skipping file tests")
 53
 54else  --{  most tests here need tmpname
 55f:close()
 56
 57print('testing i/o')
 58
 59local otherfile = os.tmpname()
 60
 61checkerr("invalid mode", io.open, file, "rw")
 62checkerr("invalid mode", io.open, file, "rb+")
 63checkerr("invalid mode", io.open, file, "r+bk")
 64checkerr("invalid mode", io.open, file, "")
 65checkerr("invalid mode", io.open, file, "+")
 66checkerr("invalid mode", io.open, file, "b")
 67assert(io.open(file, "r+b")):close()
 68assert(io.open(file, "r+")):close()
 69assert(io.open(file, "rb")):close()
 70
 71assert(os.setlocale('C', 'all'))
 72
 73io.input(io.stdin); io.output(io.stdout);
 74
 75os.remove(file)
 76assert(not loadfile(file))
 77checkerr("", dofile, file)
 78assert(not io.open(file))
 79io.output(file)
 80assert(io.output() ~= io.stdout)
 81
 82if not _port then   -- invalid seek
 83  local status, msg, code = io.stdin:seek("set", 1000)
 84  assert(not status and type(msg) == "string" and type(code) == "number")
 85end
 86
 87assert(io.output():seek() == 0)
 88assert(io.write("alo alo"):seek() == string.len("alo alo"))
 89assert(io.output():seek("cur", -3) == string.len("alo alo")-3)
 90assert(io.write("joao"))
 91assert(io.output():seek("end") == string.len("alo joao"))
 92
 93assert(io.output():seek("set") == 0)
 94
 95assert(io.write('"alo"', "{a}\n", "second line\n", "third line \n"))
 96assert(io.write('Xfourth_line'))
 97io.output(io.stdout)
 98collectgarbage()  -- file should be closed by GC
 99assert(io.input() == io.stdin and rawequal(io.output(), io.stdout))
100print('+')
101
102-- test GC for files
103collectgarbage()
104for i=1,120 do
105  for i=1,5 do
106    io.input(file)
107    assert(io.open(file, 'r'))
108    io.lines(file)
109  end
110  collectgarbage()
111end
112
113io.input():close()
114io.close()
115
116assert(os.rename(file, otherfile))
117assert(not os.rename(file, otherfile))
118
119io.output(io.open(otherfile, "ab"))
120assert(io.write("\n\n\t\t  ", 3450, "\n"));
121io.close()
122
123
124do
125  -- closing file by scope
126  local F = nil
127  do
128    local f <close> = assert(io.open(file, "w"))
129    F = f
130  end
131  assert(tostring(F) == "file (closed)")
132end
133assert(os.remove(file))
134
135
136do
137  -- test writing/reading numbers
138  local f <close> = assert(io.open(file, "w"))
139  f:write(maxint, '\n')
140  f:write(string.format("0X%x\n", maxint))
141  f:write("0xABCp-3", '\n')
142  f:write(0, '\n')
143  f:write(-maxint, '\n')
144  f:write(string.format("0x%X\n", -maxint))
145  f:write("-0xABCp-3", '\n')
146  assert(f:close())
147  local f <close> = assert(io.open(file, "r"))
148  assert(f:read("n") == maxint)
149  assert(f:read("n") == maxint)
150  assert(f:read("n") == 0xABCp-3)
151  assert(f:read("n") == 0)
152  assert(f:read("*n") == -maxint)            -- test old format (with '*')
153  assert(f:read("n") == -maxint)
154  assert(f:read("*n") == -0xABCp-3)            -- test old format (with '*')
155end
156assert(os.remove(file))
157
158
159-- testing multiple arguments to io.read
160do
161  local f <close> = assert(io.open(file, "w"))
162  f:write[[
163a line
164another line
1651234
1663.45
167one
168two
169three
170]]
171  local l1, l2, l3, l4, n1, n2, c, dummy
172  assert(f:close())
173  local f <close> = assert(io.open(file, "r"))
174  l1, l2, n1, n2, dummy = f:read("l", "L", "n", "n")
175  assert(l1 == "a line" and l2 == "another line\n" and
176         n1 == 1234 and n2 == 3.45 and dummy == nil)
177  assert(f:close())
178  local f <close> = assert(io.open(file, "r"))
179  l1, l2, n1, n2, c, l3, l4, dummy = f:read(7, "l", "n", "n", 1, "l", "l")
180  assert(l1 == "a line\n" and l2 == "another line" and c == '\n' and
181         n1 == 1234 and n2 == 3.45 and l3 == "one" and l4 == "two"
182         and dummy == nil)
183  assert(f:close())
184  local f <close> = assert(io.open(file, "r"))
185  -- second item failing
186  l1, n1, n2, dummy = f:read("l", "n", "n", "l")
187  assert(l1 == "a line" and not n1)
188end
189assert(os.remove(file))
190
191
192
193-- test yielding during 'dofile'
194f = assert(io.open(file, "w"))
195f:write[[
196local x, z = coroutine.yield(10)
197local y = coroutine.yield(20)
198return x + y * z
199]]
200assert(f:close())
201f = coroutine.wrap(dofile)
202assert(f(file) == 10)
203assert(f(100, 101) == 20)
204assert(f(200) == 100 + 200 * 101)
205assert(os.remove(file))
206
207
208f = assert(io.open(file, "w"))
209-- test number termination
210f:write[[
211-12.3-	-0xffff+  .3|5.E-3X  +234e+13E 0xDEADBEEFDEADBEEFx
2120x1.13Ap+3e
213]]
214-- very long number
215f:write("1234"); for i = 1, 1000 do f:write("0") end;  f:write("\n")
216-- invalid sequences (must read and discard valid prefixes)
217f:write[[
218.e+	0.e;	--;  0xX;
219]]
220assert(f:close())
221f = assert(io.open(file, "r"))
222assert(f:read("n") == -12.3); assert(f:read(1) == "-")
223assert(f:read("n") == -0xffff); assert(f:read(2) == "+ ")
224assert(f:read("n") == 0.3); assert(f:read(1) == "|")
225assert(f:read("n") == 5e-3); assert(f:read(1) == "X")
226assert(f:read("n") == 234e13); assert(f:read(1) == "E")
227assert(f:read("n") == 0Xdeadbeefdeadbeef); assert(f:read(2) == "x\n")
228assert(f:read("n") == 0x1.13aP3); assert(f:read(1) == "e")
229
230do   -- attempt to read too long number
231  assert(not f:read("n"))  -- fails
232  local s = f:read("L")   -- read rest of line
233  assert(string.find(s, "^00*\n$"))  -- lots of 0's left
234end
235
236assert(not f:read("n")); assert(f:read(2) == "e+")
237assert(not f:read("n")); assert(f:read(1) == ";")
238assert(not f:read("n")); assert(f:read(2) == "-;")
239assert(not f:read("n")); assert(f:read(1) == "X")
240assert(not f:read("n")); assert(f:read(1) == ";")
241assert(not f:read("n")); assert(not f:read(0))   -- end of file
242assert(f:close())
243assert(os.remove(file))
244
245
246-- test line generators
247assert(not pcall(io.lines, "non-existent-file"))
248assert(os.rename(otherfile, file))
249io.output(otherfile)
250local n = 0
251local f = io.lines(file)
252while f() do n = n + 1 end;
253assert(n == 6)   -- number of lines in the file
254checkerr("file is already closed", f)
255checkerr("file is already closed", f)
256-- copy from file to otherfile
257n = 0
258for l in io.lines(file) do io.write(l, "\n"); n = n + 1 end
259io.close()
260assert(n == 6)
261-- copy from otherfile back to file
262local f = assert(io.open(otherfile))
263assert(io.type(f) == "file")
264io.output(file)
265assert(not io.output():read())
266n = 0
267for l in f:lines() do io.write(l, "\n"); n = n + 1 end
268assert(tostring(f):sub(1, 5) == "file ")
269assert(f:close()); io.close()
270assert(n == 6)
271checkerr("closed file", io.close, f)
272assert(tostring(f) == "file (closed)")
273assert(io.type(f) == "closed file")
274io.input(file)
275f = io.open(otherfile):lines()
276n = 0
277for l in io.lines() do assert(l == f()); n = n + 1 end
278f = nil; collectgarbage()
279assert(n == 6)
280assert(os.remove(otherfile))
281
282do  -- bug in 5.3.1
283  io.output(otherfile)
284  io.write(string.rep("a", 300), "\n")
285  io.close()
286  local t ={}; for i = 1, 250 do t[i] = 1 end
287  t = {io.lines(otherfile, table.unpack(t))()}
288  -- everything ok here
289  assert(#t == 250 and t[1] == 'a' and t[#t] == 'a')
290  t[#t + 1] = 1    -- one too many
291  checkerr("too many arguments", io.lines, otherfile, table.unpack(t))
292  collectgarbage()   -- ensure 'otherfile' is closed
293  assert(os.remove(otherfile))
294end
295
296io.input(file)
297do  -- test error returns
298  local a,b,c = io.input():write("xuxu")
299  assert(not a and type(b) == "string" and type(c) == "number")
300end
301checkerr("invalid format", io.read, "x")
302assert(io.read(0) == "")   -- not eof
303assert(io.read(5, 'l') == '"alo"')
304assert(io.read(0) == "")
305assert(io.read() == "second line")
306local x = io.input():seek()
307assert(io.read() == "third line ")
308assert(io.input():seek("set", x))
309assert(io.read('L') == "third line \n")
310assert(io.read(1) == "X")
311assert(io.read(string.len"fourth_line") == "fourth_line")
312assert(io.input():seek("cur", -string.len"fourth_line"))
313assert(io.read() == "fourth_line")
314assert(io.read() == "")  -- empty line
315assert(io.read('n') == 3450)
316assert(io.read(1) == '\n')
317assert(not io.read(0))  -- end of file
318assert(not io.read(1))  -- end of file
319assert(not io.read(30000))  -- end of file
320assert(({io.read(1)})[2] == undef)
321assert(not io.read())  -- end of file
322assert(({io.read()})[2] == undef)
323assert(not io.read('n'))  -- end of file
324assert(({io.read('n')})[2] == undef)
325assert(io.read('a') == '')  -- end of file (OK for 'a')
326assert(io.read('a') == '')  -- end of file (OK for 'a')
327collectgarbage()
328print('+')
329io.close(io.input())
330checkerr(" input file is closed", io.read)
331
332assert(os.remove(file))
333
334local t = '0123456789'
335for i=1,10 do t = t..t; end
336assert(string.len(t) == 10*2^10)
337
338io.output(file)
339io.write("alo"):write("\n")
340io.close()
341checkerr(" output file is closed", io.write)
342local f = io.open(file, "a+b")
343io.output(f)
344collectgarbage()
345
346assert(io.write(' ' .. t .. ' '))
347assert(io.write(';', 'end of file\n'))
348f:flush(); io.flush()
349f:close()
350print('+')
351
352io.input(file)
353assert(io.read() == "alo")
354assert(io.read(1) == ' ')
355assert(io.read(string.len(t)) == t)
356assert(io.read(1) == ' ')
357assert(io.read(0))
358assert(io.read('a') == ';end of file\n')
359assert(not io.read(0))
360assert(io.close(io.input()))
361
362
363-- test errors in read/write
364do
365  local function ismsg (m)
366    -- error message is not a code number
367    return (type(m) == "string" and not tonumber(m))
368  end
369
370  -- read
371  local f = io.open(file, "w")
372  local r, m, c = f:read()
373  assert(not r and ismsg(m) and type(c) == "number")
374  assert(f:close())
375  -- write
376  f = io.open(file, "r")
377  r, m, c = f:write("whatever")
378  assert(not r and ismsg(m) and type(c) == "number")
379  assert(f:close())
380  -- lines
381  f = io.open(file, "w")
382  r, m = pcall(f:lines())
383  assert(r == false and ismsg(m))
384  assert(f:close())
385end
386
387assert(os.remove(file))
388
389-- test for L format
390io.output(file); io.write"\n\nline\nother":close()
391io.input(file)
392assert(io.read"L" == "\n")
393assert(io.read"L" == "\n")
394assert(io.read"L" == "line\n")
395assert(io.read"L" == "other")
396assert(not io.read"L")
397io.input():close()
398
399local f = assert(io.open(file))
400local s = ""
401for l in f:lines("L") do s = s .. l end
402assert(s == "\n\nline\nother")
403f:close()
404
405io.input(file)
406s = ""
407for l in io.lines(nil, "L") do s = s .. l end
408assert(s == "\n\nline\nother")
409io.input():close()
410
411s = ""
412for l in io.lines(file, "L") do s = s .. l end
413assert(s == "\n\nline\nother")
414
415s = ""
416for l in io.lines(file, "l") do s = s .. l end
417assert(s == "lineother")
418
419io.output(file); io.write"a = 10 + 34\na = 2*a\na = -a\n":close()
420local t = {}
421assert(load(io.lines(file, "L"), nil, nil, t))()
422assert(t.a == -((10 + 34) * 2))
423
424
425do   -- testing closing file in line iteration
426
427  -- get the to-be-closed variable from a loop
428  local function gettoclose (lv)
429    lv = lv + 1
430    local stvar = 0   -- to-be-closed is 4th state variable in the loop
431    for i = 1, 1000 do
432      local n, v = debug.getlocal(lv, i)
433      if n == "(for state)" then
434        stvar = stvar + 1
435        if stvar == 4 then return v end
436      end
437    end
438  end
439
440  local f
441  for l in io.lines(file) do
442    f = gettoclose(1)
443    assert(io.type(f) == "file")
444    break
445  end
446  assert(io.type(f) == "closed file")
447
448  f = nil
449  local function foo (name)
450    for l in io.lines(name) do
451      f = gettoclose(1)
452      assert(io.type(f) == "file")
453      error(f)   -- exit loop with an error
454    end
455  end
456  local st, msg = pcall(foo, file)
457  assert(st == false and io.type(msg) == "closed file")
458
459end
460
461
462-- test for multipe arguments in 'lines'
463io.output(file); io.write"0123456789\n":close()
464for a,b in io.lines(file, 1, 1) do
465  if a == "\n" then assert(not b)
466  else assert(tonumber(a) == tonumber(b) - 1)
467  end
468end
469
470for a,b,c in io.lines(file, 1, 2, "a") do
471  assert(a == "0" and b == "12" and c == "3456789\n")
472end
473
474for a,b,c in io.lines(file, "a", 0, 1) do
475  if a == "" then break end
476  assert(a == "0123456789\n" and not b and not c)
477end
478collectgarbage()   -- to close file in previous iteration
479
480io.output(file); io.write"00\n10\n20\n30\n40\n":close()
481for a, b in io.lines(file, "n", "n") do
482  if a == 40 then assert(not b)
483  else assert(a == b - 10)
484  end
485end
486
487
488-- test load x lines
489io.output(file);
490io.write[[
491local y
492= X
493X =
494X *
4952 +
496X;
497X =
498X
499-                                   y;
500]]:close()
501_G.X = 1
502assert(not load((io.lines(file))))
503collectgarbage()   -- to close file in previous iteration
504load((io.lines(file, "L")))()
505assert(_G.X == 2)
506load((io.lines(file, 1)))()
507assert(_G.X == 4)
508load((io.lines(file, 3)))()
509assert(_G.X == 8)
510_G.X = nil
511
512print('+')
513
514local x1 = "string\n\n\\com \"\"''coisas [[estranhas]] ]]'"
515io.output(file)
516assert(io.write(string.format("X2 = %q\n-- comment without ending EOS", x1)))
517io.close()
518assert(loadfile(file))()
519assert(x1 == _G.X2)
520_G.X2 = nil
521print('+')
522assert(os.remove(file))
523assert(not os.remove(file))
524assert(not os.remove(otherfile))
525
526-- testing loadfile
527local function testloadfile (s, expres)
528  io.output(file)
529  if s then io.write(s) end
530  io.close()
531  local res = assert(loadfile(file))()
532  assert(os.remove(file))
533  assert(res == expres)
534end
535
536-- loading empty file
537testloadfile(nil, nil)
538
539-- loading file with initial comment without end of line
540testloadfile("# a non-ending comment", nil)
541
542
543-- checking Unicode BOM in files
544testloadfile("\xEF\xBB\xBF# some comment\nreturn 234", 234)
545testloadfile("\xEF\xBB\xBFreturn 239", 239)
546testloadfile("\xEF\xBB\xBF", nil)   -- empty file with a BOM
547
548
549-- checking line numbers in files with initial comments
550testloadfile("# a comment\nreturn require'debug'.getinfo(1).currentline", 2)
551
552
553-- loading binary file
554io.output(io.open(file, "wb"))
555assert(io.write(string.dump(function () return 10, '\0alo\255', 'hi' end)))
556io.close()
557a, b, c = assert(loadfile(file))()
558assert(a == 10 and b == "\0alo\255" and c == "hi")
559assert(os.remove(file))
560
561-- bug in 5.2.1
562do
563  io.output(io.open(file, "wb"))
564  -- save function with no upvalues
565  assert(io.write(string.dump(function () return 1 end)))
566  io.close()
567  f = assert(loadfile(file, "b", {}))
568  assert(type(f) == "function" and f() == 1)
569  assert(os.remove(file))
570end
571
572-- loading binary file with initial comment
573io.output(io.open(file, "wb"))
574assert(io.write("#this is a comment for a binary file\0\n",
575                string.dump(function () return 20, '\0\0\0' end)))
576io.close()
577a, b, c = assert(loadfile(file))()
578assert(a == 20 and b == "\0\0\0" and c == nil)
579assert(os.remove(file))
580
581
582-- 'loadfile' with 'env'
583do
584  local f = io.open(file, 'w')
585  f:write[[
586    if (...) then a = 15; return b, c, d
587    else return _ENV
588    end
589  ]]
590  f:close()
591  local t = {b = 12, c = "xuxu", d = print}
592  local f = assert(loadfile(file, 't', t))
593  local b, c, d = f(1)
594  assert(t.a == 15 and b == 12 and c == t.c and d == print)
595  assert(f() == t)
596  f = assert(loadfile(file, 't', nil))
597  assert(f() == nil)
598  f = assert(loadfile(file))
599  assert(f() == _G)
600  assert(os.remove(file))
601end
602
603
604-- 'loadfile' x modes
605do
606  io.open(file, 'w'):write("return 10"):close()
607  local s, m = loadfile(file, 'b')
608  assert(not s and string.find(m, "a text chunk"))
609  io.open(file, 'w'):write("\27 return 10"):close()
610  local s, m = loadfile(file, 't')
611  assert(not s and string.find(m, "a binary chunk"))
612  assert(os.remove(file))
613end
614
615
616io.output(file)
617assert(io.write("qualquer coisa\n"))
618assert(io.write("mais qualquer coisa"))
619io.close()
620assert(io.output(assert(io.open(otherfile, 'wb')))
621       :write("outra coisa\0\1\3\0\0\0\0\255\0")
622       :close())
623
624local filehandle = assert(io.open(file, 'r+'))
625local otherfilehandle = assert(io.open(otherfile, 'rb'))
626assert(filehandle ~= otherfilehandle)
627assert(type(filehandle) == "userdata")
628assert(filehandle:read('l') == "qualquer coisa")
629io.input(otherfilehandle)
630assert(io.read(string.len"outra coisa") == "outra coisa")
631assert(filehandle:read('l') == "mais qualquer coisa")
632filehandle:close();
633assert(type(filehandle) == "userdata")
634io.input(otherfilehandle)
635assert(io.read(4) == "\0\1\3\0")
636assert(io.read(3) == "\0\0\0")
637assert(io.read(0) == "")        -- 255 is not eof
638assert(io.read(1) == "\255")
639assert(io.read('a') == "\0")
640assert(not io.read(0))
641assert(otherfilehandle == io.input())
642otherfilehandle:close()
643assert(os.remove(file))
644assert(os.remove(otherfile))
645collectgarbage()
646
647io.output(file)
648  :write[[
649 123.4	-56e-2  not a number
650second line
651third line
652
653and the rest of the file
654]]
655  :close()
656io.input(file)
657local _,a,b,c,d,e,h,__ = io.read(1, 'n', 'n', 'l', 'l', 'l', 'a', 10)
658assert(io.close(io.input()))
659assert(_ == ' ' and not __)
660assert(type(a) == 'number' and a==123.4 and b==-56e-2)
661assert(d=='second line' and e=='third line')
662assert(h==[[
663
664and the rest of the file
665]])
666assert(os.remove(file))
667collectgarbage()
668
669-- testing buffers
670do
671  local f = assert(io.open(file, "w"))
672  local fr = assert(io.open(file, "r"))
673  assert(f:setvbuf("full", 2000))
674  f:write("x")
675  assert(fr:read("all") == "")  -- full buffer; output not written yet
676  f:close()
677  fr:seek("set")
678  assert(fr:read("all") == "x")   -- `close' flushes it
679  f = assert(io.open(file), "w")
680  assert(f:setvbuf("no"))
681  f:write("x")
682  fr:seek("set")
683  assert(fr:read("all") == "x")  -- no buffer; output is ready
684  f:close()
685  f = assert(io.open(file, "a"))
686  assert(f:setvbuf("line"))
687  f:write("x")
688  fr:seek("set", 1)
689  assert(fr:read("all") == "")   -- line buffer; no output without `\n'
690  f:write("a\n"):seek("set", 1)
691  assert(fr:read("all") == "xa\n")  -- now we have a whole line
692  f:close(); fr:close()
693  assert(os.remove(file))
694end
695
696
697if not _soft then
698  print("testing large files (> BUFSIZ)")
699  io.output(file)
700  for i=1,5001 do io.write('0123456789123') end
701  io.write('\n12346'):close()
702  io.input(file)
703  local x = io.read('a')
704  io.input():seek('set', 0)
705  local y = io.read(30001)..io.read(1005)..io.read(0)..
706            io.read(1)..io.read(100003)
707  assert(x == y and string.len(x) == 5001*13 + 6)
708  io.input():seek('set', 0)
709  y = io.read()  -- huge line
710  assert(x == y..'\n'..io.read())
711  assert(not io.read())
712  io.close(io.input())
713  assert(os.remove(file))
714  x = nil; y = nil
715end
716
717if not _port then
718  local progname
719  do  -- get name of running executable
720    local arg = arg or ARG
721    local i = 0
722    while arg[i] do i = i - 1 end
723    progname = '"' .. arg[i + 1] .. '"'
724  end
725  print("testing popen/pclose and execute")
726  -- invalid mode for popen
727  checkerr("invalid mode", io.popen, "cat", "")
728  checkerr("invalid mode", io.popen, "cat", "r+")
729  checkerr("invalid mode", io.popen, "cat", "rw")
730  do  -- basic tests for popen
731    local file = os.tmpname()
732    local f = assert(io.popen("cat - > " .. file, "w"))
733    f:write("a line")
734    assert(f:close())
735    local f = assert(io.popen("cat - < " .. file, "r"))
736    assert(f:read("a") == "a line")
737    assert(f:close())
738    assert(os.remove(file))
739  end
740
741  local tests = {
742    -- command,   what,  code
743    {"ls > /dev/null", "ok"},
744    {"not-to-be-found-command", "exit"},
745    {"exit 3", "exit", 3},
746    {"exit 129", "exit", 129},
747    {"kill -s HUP $$", "signal", 1},
748    {"kill -s KILL $$", "signal", 9},
749    {"sh -c 'kill -s HUP $$'", "exit"},
750    {progname .. ' -e " "', "ok"},
751    {progname .. ' -e "os.exit(0, true)"', "ok"},
752    {progname .. ' -e "os.exit(20, true)"', "exit", 20},
753  }
754  print("\n(some error messages are expected now)")
755  for _, v in ipairs(tests) do
756    local x, y, z = io.popen(v[1]):close()
757    local x1, y1, z1 = os.execute(v[1])
758    assert(x == x1 and y == y1 and z == z1)
759    if v[2] == "ok" then
760      assert(x and y == 'exit' and z == 0)
761    else
762      assert(not x and y == v[2])   -- correct status and 'what'
763      -- correct code if known (but always different from 0)
764      assert((v[3] == nil and z > 0) or v[3] == z)
765    end
766  end
767end
768
769
770-- testing tmpfile
771f = io.tmpfile()
772assert(io.type(f) == "file")
773f:write("alo")
774f:seek("set")
775assert(f:read"a" == "alo")
776
777end --}
778
779print'+'
780
781print("testing date/time")
782
783assert(os.date("") == "")
784assert(os.date("!") == "")
785assert(os.date("\0\0") == "\0\0")
786assert(os.date("!\0\0") == "\0\0")
787local x = string.rep("a", 10000)
788assert(os.date(x) == x)
789local t = os.time()
790D = os.date("*t", t)
791assert(os.date(string.rep("%d", 1000), t) ==
792       string.rep(os.date("%d", t), 1000))
793assert(os.date(string.rep("%", 200)) == string.rep("%", 100))
794
795local function checkDateTable (t)
796  _G.D = os.date("*t", t)
797  assert(os.time(D) == t)
798  load(os.date([[assert(D.year==%Y and D.month==%m and D.day==%d and
799    D.hour==%H and D.min==%M and D.sec==%S and
800    D.wday==%w+1 and D.yday==%j)]], t))()
801  _G.D = nil
802end
803
804checkDateTable(os.time())
805if not _port then
806  -- assume that time_t can represent these values
807  checkDateTable(0)
808  checkDateTable(1)
809  checkDateTable(1000)
810  checkDateTable(0x7fffffff)
811  checkDateTable(0x80000000)
812end
813
814checkerr("invalid conversion specifier", os.date, "%")
815checkerr("invalid conversion specifier", os.date, "%9")
816checkerr("invalid conversion specifier", os.date, "%")
817checkerr("invalid conversion specifier", os.date, "%O")
818checkerr("invalid conversion specifier", os.date, "%E")
819checkerr("invalid conversion specifier", os.date, "%Ea")
820
821checkerr("not an integer", os.time, {year=1000, month=1, day=1, hour='x'})
822checkerr("not an integer", os.time, {year=1000, month=1, day=1, hour=1.5})
823
824checkerr("missing", os.time, {hour = 12})   -- missing date
825
826
827if string.packsize("i") == 4 then   -- 4-byte ints
828  checkerr("field 'year' is out-of-bound", os.time,
829              {year = -(1 << 31) + 1899, month = 1, day = 1})
830
831  checkerr("field 'year' is out-of-bound", os.time,
832              {year = -(1 << 31), month = 1, day = 1})
833
834  if math.maxinteger > 2^31 then   -- larger lua_integer?
835    checkerr("field 'year' is out-of-bound", os.time,
836                {year = (1 << 31) + 1900, month = 1, day = 1})
837  end
838end
839
840
841if not _port then
842  -- test Posix-specific modifiers
843  assert(type(os.date("%Ex")) == 'string')
844  assert(type(os.date("%Oy")) == 'string')
845
846  -- test large dates (assume at least 4-byte ints and time_t)
847  local t0 = os.time{year = 1970, month = 1, day = 0}
848  local t1 = os.time{year = 1970, month = 1, day = 0, sec = (1 << 31) - 1}
849  assert(t1 - t0 == (1 << 31) - 1)
850  t0 = os.time{year = 1970, month = 1, day = 1}
851  t1 = os.time{year = 1970, month = 1, day = 1, sec = -(1 << 31)}
852  assert(t1 - t0 == -(1 << 31))
853
854  -- test out-of-range dates (at least for Unix)
855  if maxint >= 2^62 then  -- cannot do these tests in Small Lua
856    -- no arith overflows
857    checkerr("out-of-bound", os.time, {year = -maxint, month = 1, day = 1})
858    if string.packsize("i") == 4 then   -- 4-byte ints
859      if testerr("out-of-bound", os.date, "%Y", 2^40) then
860        -- time_t has 4 bytes and therefore cannot represent year 4000
861        print("  4-byte time_t")
862        checkerr("cannot be represented", os.time, {year=4000, month=1, day=1})
863      else
864        -- time_t has 8 bytes; an int year cannot represent a huge time
865        print("  8-byte time_t")
866        checkerr("cannot be represented", os.date, "%Y", 2^60)
867
868        -- this is the maximum year
869        assert(tonumber(os.time
870          {year=(1 << 31) + 1899, month=12, day=31, hour=23, min=59, sec=59}))
871
872        -- this is too much
873        checkerr("represented", os.time,
874          {year=(1 << 31) + 1899, month=12, day=31, hour=23, min=59, sec=60})
875      end
876
877      -- internal 'int' fields cannot hold these values
878      checkerr("field 'day' is out-of-bound", os.time,
879                  {year = 0, month = 1, day = 2^32})
880
881      checkerr("field 'month' is out-of-bound", os.time,
882                  {year = 0, month = -((1 << 31) + 1), day = 1})
883
884      checkerr("field 'year' is out-of-bound", os.time,
885                  {year = (1 << 31) + 1900, month = 1, day = 1})
886
887    else    -- 8-byte ints
888      -- assume time_t has 8 bytes too
889      print("  8-byte time_t")
890      assert(tonumber(os.date("%Y", 2^60)))
891
892      -- but still cannot represent a huge year
893      checkerr("cannot be represented", os.time, {year=2^60, month=1, day=1})
894    end
895  end
896end
897
898do
899  local D = os.date("*t")
900  local t = os.time(D)
901  if D.isdst == nil then
902    print("no daylight saving information")
903  else
904    assert(type(D.isdst) == 'boolean')
905  end
906  D.isdst = nil
907  local t1 = os.time(D)
908  assert(t == t1)   -- if isdst is absent uses correct default
909end
910
911local D = os.date("*t")
912t = os.time(D)
913D.year = D.year-1;
914local t1 = os.time(D)
915-- allow for leap years
916assert(math.abs(os.difftime(t,t1)/(24*3600) - 365) < 2)
917
918-- should not take more than 1 second to execute these two lines
919t = os.time()
920t1 = os.time(os.date("*t"))
921local diff = os.difftime(t1,t)
922assert(0 <= diff and diff <= 1)
923diff = os.difftime(t,t1)
924assert(-1 <= diff and diff <= 0)
925
926local t1 = os.time{year=2000, month=10, day=1, hour=23, min=12}
927local t2 = os.time{year=2000, month=10, day=1, hour=23, min=10, sec=19}
928assert(os.difftime(t1,t2) == 60*2-19)
929
930-- since 5.3.3, 'os.time' normalizes table fields
931t1 = {year = 2005, month = 1, day = 1, hour = 1, min = 0, sec = -3602}
932os.time(t1)
933assert(t1.day == 31 and t1.month == 12 and t1.year == 2004 and
934       t1.hour == 23 and t1.min == 59 and t1.sec == 58 and
935       t1.yday == 366)
936
937io.output(io.stdout)
938local t = os.date('%d %m %Y %H %M %S')
939local d, m, a, h, min, s = string.match(t,
940                             "(%d+) (%d+) (%d+) (%d+) (%d+) (%d+)")
941d = tonumber(d)
942m = tonumber(m)
943a = tonumber(a)
944h = tonumber(h)
945min = tonumber(min)
946s = tonumber(s)
947io.write(string.format('test done on %2.2d/%2.2d/%d', d, m, a))
948io.write(string.format(', at %2.2d:%2.2d:%2.2d\n', h, min, s))
949io.write(string.format('%s\n', _VERSION))
950
951