--- @diagnostic disable: lowercase-global, need-check-nil -- common testing environment posix = require( 'posix' ) string = require( 'string' ) local sys_stat = require "posix.sys.stat" -- escape codes to colorize output on terminal local c1='\027[47;34m' local c0='\027[0m' -- compatibility with 5.1 if table.unpack == nil then --- @diagnostic disable-next-line: deprecated table.unpack = unpack end -- -- Writes colorized. -- function cwriteln (...) io.write( c1, '++ ', ... ) io.write( c0, '\n' ) end local lver = string.gmatch(_VERSION, "Lua (%d).(%d)") _LUA_VERSION_MAJOR, _LUA_VERSION_MINOR = lver() _LUA_VERSION_MAJOR = tonumber(_LUA_VERSION_MAJOR) _LUA_VERSION_MINOR = tonumber(_LUA_VERSION_MINOR) -- Compatibility execute function function execute(...) if _LUA_VERSION_MAJOR <= 5 and _LUA_VERSION_MINOR < 2 then local rv = os.execute(...) return "exit", rv else local ok, why, code = os.execute(...) return why, code end end -- -- Initializes the pseudo random generator -- -- If environment variable 'SEED' is set, -- that one is used seed. -- local seed = tonumber(os.getenv( 'SEED')) or os.time( ) math.randomseed( seed ) cwriteln( 'random seed: ', seed ) -- -- Creates a tmp directory. -- -- Returns the name of the directory. -- function mktempd ( ) local f = io.popen( 'mktemp -td ltest.XXX', 'r' ) if f == nil then return error("can't create testing directory") end local s = f:read( '*a' ) f:close( ) s = s:gsub( '[\n\r]+', ' ' ) s = s:match( '^%s*(.-)%s*$' ) return s end -- -- Creates a tmp directory with the -- typical lsyncd test architecture. -- -- returns path of tmpdir -- path of srcdir -- path of trgdir -- function mktemps ( ) local tdir = mktempd() .. '/' cwriteln( 'using ', tdir, ' as test root' ) local srcdir = tdir..'src/' local trgdir = tdir..'trg/' posix.mkdir( srcdir ) posix.mkdir( trgdir ) return tdir, srcdir, trgdir end -- -- Writes a file with 'text' in it -- and adds a newline. -- function writefile ( filename, text, mode ) local f = io.open( filename, 'w' ) if not f then cwriteln( 'Cannot open "'..filename..'" for writing.' ) return false end f:write( text ) f:write( '\n' ) f:close( ) if mode ~= nil then posix.chmod(filename, mode) end return true end function splitpath(P) local i = #P local ch = P:sub(i,i) while i > 0 and ch ~= "/" do i = i - 1 ch = P:sub(i,i) end if i == 0 then return '',P else return P:sub(1,i-1), P:sub(i+1) end end function isabs(p) return string.sub(p, 1, 2) == "/" end function abspath(P,pwd) P = P:gsub('[\\/]$','') if not isabs(P) then local rv = posix.unistd.getcwd() .. "/" .. P return rv end return P end function script_path() -- local str = debug.getinfo(2, "S").source:sub(2) -- return str:match("(.*/)") local dir, file = splitpath(abspath(debug.getinfo(1).short_src)) return dir end function which(exec) local path = os.getenv("PATH") for match in (path..':'):gmatch("(.-)"..':') do local fname = match..'/'..exec local s = sys_stat.stat(fname) if s ~= nil then return fname end end end -- -- Starts test ssh server -- function startSshd() -- local f = io.open(script_path() .. "ssh/sshd.pid", 'r') -- if f -- then -- return false -- end cwriteln(arg[0]) cwriteln(script_path() .. "ssh/sshd_config") local sshdPath = script_path() .. "/ssh/" if posix.stat( sshdPath ) == nil then cwriteln("setup ssh server in " .. sshdPath) posix.mkdir(sshdPath) os.execute("ssh-keygen -t rsa -N '' -f" .. sshdPath .. "id_rsa") os.execute("cp ".. sshdPath .. "id_rsa.pub ".. sshdPath .. "authorized_keys") os.execute("ssh-keygen -t rsa -N '' -f ".. sshdPath .. "ssh_host_rsa_key") cwriteln("done") end local f = io.open( sshdPath .. "sshd_config", 'w') local cfg = [[ Port 2468 HostKey ]] .. sshdPath .. [[ssh_host_rsa_key AuthorizedKeysFile ]] .. sshdPath .. [[authorized_keys ChallengeResponseAuthentication no UsePAM no #Subsystem sftp /usr/lib/ssh/sftp-server PidFile ]] .. sshdPath .. [[sshd.pid ]] cwriteln("Use ssh config: "..cfg) f:write(cfg) f:close() --local which = io.popen("which sshd") exePath = which('sshd') cwriteln("Using sshd: "..exePath) local pid = spawn(exePath, "-D", "-e", "-f", sshdPath .. "sshd_config") cwriteln( 'spawned sshd server: ' .. pid) return true end function strip(s) return s:match "^%s*(.-)%s*$" end -- -- Stop test ssh server -- function stopSshd() local f = io.open(script_path() .. "/ssh/sshd.pid", 'r') if not f then return false end pid = strip(f:read("*a")) posix.kill(tonumber(pid)) end -- -- Spawns a subprocess. -- -- Returns the processes pid. -- function spawn(...) args = { ... } cwriteln( 'spawning: ', table.concat( args, ' ' ) ) local pid = posix.fork( ) if pid < 0 then cwriteln( 'Error, failed fork!' ) os.exit( -1 ) end if pid == 0 then posix.exec( ... ) -- should not return cwriteln( 'Error, failed to spawn: ', ... ) os.exit( -1 ) end return pid end -- -- Makes a lot of random data -- -- function churn ( rootdir, -- the directory to make data in n, -- roughly how much data action will be done init -- if true init random data only, no sleeps or moves ) -- all dirs created, indexed by integer and path root = { name = '' } alldirs = { root } dirsWithFileI = { } dirsWithFileD = { } -- -- returns the name of a directory -- -- name is internal recursive paramter, keep it nil. -- local function dirname ( dir, name ) name = name or '' if not dir then return name end return dirname( dir.parent, dir.name .. '/' .. name ) end -- -- Picks a random dir. -- local function pickDir ( notRoot ) if notRoot then if #alldirs <= 2 then return nil end return alldirs[ math.random( 2, #alldirs ) ] end return alldirs[ math.random( #alldirs ) ] end -- -- Picks a random file. -- -- Returns 3 values: -- * the directory -- * the filename -- * number of files in directory -- local function pickFile ( ) -- picks the random directory if #dirsWithFileI < 1 then return end local rdir = dirsWithFileI[ math.random( 1, #dirsWithFileI ) ] if not rdir then return end -- counts the files in there local c = 0 for name, _ in pairs(rdir) do if #name == 2 then c = c + 1 end end -- picks one file at random local cr = math.random( 1, c ) local fn for name, _ in pairs( rdir ) do if #name == 2 then -- filenames are 2 chars wide. cr = cr - 1 if cr == 0 then fn = name break end end end return rdir, fn, c end -- -- Removes a reference to a file -- -- @param dir -- directory reference -- @param fn -- filename -- @param c -- number of files in dir -- local function rmFileReference ( dir, fn, c ) dir[fn] = nil if c == 1 then -- if last file from origin dir, it has no files anymore for i, v in ipairs( dirsWithFileI ) do if v == dir then table.remove( dirsWithFileI, i ) break end end dirsWithFileD[ dir ] = nil end end -- -- possible randomized behaviour. -- just gives it a pause -- local function sleep ( ) cwriteln( '..zzz..' ) posix.sleep( 1 ) end -- -- possible randomized behaviour. -- creates a directory -- local function mkdir ( ) -- chooses a random directory to create it into local rdir = pickDir( ) -- creates a new random one letter name local nn = string.char( 96 + math.random( 26 ) ) if not rdir[nn] then local ndir = { name = nn, parent = rdir, } local dn = dirname( ndir ) rdir[ nn ] = dn table.insert( alldirs, ndir ) cwriteln( 'mkdir '..rootdir..dn ) posix.mkdir( rootdir..dn ) end end -- -- Possible randomized behaviour: -- Creates a file. -- local function mkfile ( ) -- chooses a random directory to create it into local rdir = pickDir() -- creates a new random one letter name local nn = 'f'..string.char( 96 + math.random( 26 ) ) local fn = dirname( rdir ) .. nn cwriteln( 'mkfile ' .. rootdir .. fn ) local f = io.open(rootdir..fn, 'w') if f then for i = 1, 10 do f:write( string.char( 96 + math.random( 26 ) ) ) end f:write( '\n' ) f:close( ) rdir[ nn ]=true if not dirsWithFileD[ rdir ] then table.insert( dirsWithFileI, rdir ) dirsWithFileD[ rdir ]=true end end end -- -- Possible randomized behaviour: -- Moves a directory. -- local function mvdir ( ) if #alldirs <= 2 then return end -- chooses a random directory to move local odir = pickDir( true ) -- chooses a random directory to move to local tdir = pickDir( ) -- makes sure tdir is not a subdir of odir local dd = tdir while dd do if odir == dd then return end dd = dd.parent end -- origin name in the target dir already if tdir[odir.name] ~= nil then return end local on = dirname( odir ) local tn = dirname( tdir ) cwriteln( 'mvdir ', rootdir,on, ' -> ', rootdir, tn, odir.name ) os.rename( rootdir..on, rootdir..tn..odir.name ) odir.parent[ odir.name ] = nil odir.parent = tdir tdir[ odir.name ] = odir end -- -- possible randomized behaviour, -- moves a file. -- local function mvfile ( ) local odir, fn, c = pickFile( ) if not odir then return end -- picks a directory with a file at random -- picks a target directory at random local tdir = pickDir( ) local on = dirname( odir ) local tn = dirname( tdir ) cwriteln( 'mvfile ', rootdir, on, fn, ' -> ', rootdir, tn, fn ) os.rename( rootdir..on..fn, rootdir..tn..fn ) rmFileReference( odir, fn, c ) tdir[ fn ] = true if not dirsWithFileD[ tdir ] then dirsWithFileD[ tdir ] = true table.insert( dirsWithFileI, tdir ) end end -- -- Possible randomized behaviour: -- Removes a file. -- local function rmfile ( ) local dir, fn, c = pickFile( ) if dir then local dn = dirname( dir ) cwriteln( 'rmfile ', rootdir, dn, fn ) posix.unlink( rootdir..dn..fn ) rmFileReference( dir, fn, c ) end end local dice if init then dice = { { 10, mkfile }, { 10, mkdir }, } else dice = { { 50, sleep }, { 20, mkfile }, { 20, mkdir }, { 20, mvdir }, { 20, rmfile }, } end cwriteln( 'making random data' ) local ndice = 0 for i, d in ipairs( dice ) do ndice = ndice + d[ 1 ] d[ 1 ] = ndice end for ai = 1, n do -- throws a die what to do local acn = math.random( ndice ) for i, d in ipairs( dice ) do if acn <= d[ 1 ] then d[ 2 ]( ) break end end end end -- check if tables are equal function isTableEqual(o1, o2, ignore_mt) if o1 == o2 then return true end local o1Type = type(o1) local o2Type = type(o2) if o1Type ~= o2Type then return false end if o1Type ~= 'table' then return false end if not ignore_mt then local mt1 = getmetatable(o1) if mt1 and mt1.__eq then --compare using built in method return o1 == o2 end end local keySet = {} for key1, value1 in pairs(o1) do local value2 = o2[key1] if value2 == nil or isTableEqual(value1, value2, ignore_mt) == false then return false end keySet[key1] = true end for key2, _ in pairs(o2) do if not keySet[key2] then return false end end return true end -- -- Tests if the filename exists -- fails if this is different to expect. -- function testfile ( filename, expect ) local stat, err = posix.stat( filename ) if stat and not expect then cwriteln( 'failure: ', filename, ' should be filtered') os.exit( 1 ) end if not stat and expect then cwriteln( 'failure: ', filename, ' should not be filtered' ) os.exit( 1 ) end end