Skip to content

Commit

Permalink
Migrate treegen module and adapt it
Browse files Browse the repository at this point in the history
The original `treegen` module (path: tarantool/test/treegen.lua) has
been moved to the current project with the following changes:
    - refactoring;
    - updated documentation.

Closes #364
  • Loading branch information
Oleg Chaplashkin committed Jun 13, 2024
1 parent defcd6a commit 8086a58
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- Make `assert_error_*` additionally check error trace if required.
- Add `--list-test-cases` and `--run-test-case` CLI options.
- Introduce preloaded hooks (gh-380).
- Add `treegen` helper as a tree generator (gh-364).

## 1.0.1

Expand Down
3 changes: 2 additions & 1 deletion config.ld
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ file = {
'luatest/server.lua',
'luatest/replica_set.lua',
'luatest/justrun.lua',
'luatest/cbuilder.lua'
'luatest/cbuilder.lua',
'luatest/treegen.lua'
}
topics = {
'CHANGELOG.md',
Expand Down
215 changes: 215 additions & 0 deletions luatest/treegen.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
--- Working tree generator.
--
-- Generates a tree of Lua files using provided templates and
-- filenames.
--
-- @usage
--
-- local t = require('luatest')
-- local treegen = require('test.treegen')
--
-- local g = t.group()
--
-- local SCRIPT_TEMPLATE = [[
-- <...>
-- ]]
--
-- g.foobar_test = function(g)
-- local dir = treegen.prepare_directory(g,
-- {'foo/bar.lua', 'main.lua'})
-- <..test case..>
-- end
--
-- @module luatest.treegen

local hooks = require('luatest.hooks')

local fio = require('fio')
local fun = require('fun')
local checks = require('checks')

local log = require('luatest.log')

local treegen = {}

local function find_template(group, script)
for position, template_def in ipairs(group._treegen.templates) do
if script:match(template_def.pattern) then
return position, template_def.template
end
end
error(("treegen: can't find a template for script %q"):format(script))
end

--- Write provided content into the given directory.
--
-- @string directory Directory where the content will be created.
-- @string filename File to write (possible nested path: /foo/bar/main.lua).
-- @string content The body to write.
-- @return string
function treegen.write_file(directory, filename, content)
checks('string', 'string', 'string')
local content_abspath = fio.pathjoin(directory, filename)
local flags = {'O_CREAT', 'O_WRONLY', 'O_TRUNC'}
local mode = tonumber('644', 8)

local contentdir_abspath = fio.dirname(content_abspath)
log.info('Creating a directory: %s', contentdir_abspath)
fio.mktree(contentdir_abspath)

log.info('Writing a content: %s', content_abspath)
local fh = fio.open(content_abspath, flags, mode)
fh:write(content)
fh:close()
return content_abspath
end

-- Generate a content that follows a template and write it at the
-- given path in the given directory.
--
-- @table group Group of tests.
-- @string directory Directory where the content will be created.
-- @string filename File to write (possible nested path: /foo/bar/main.lua).
-- @table replacements List of replacement templates.
-- @return string
local function gen_content(group, directory, filename, replacements)
checks('table', 'string', 'string', 'table')
local _, template = find_template(group, filename)
replacements = fun.chain({filename = filename}, replacements):tomap()
local body = template:gsub('<(.-)>', replacements)
return treegen.write_file(directory, filename, body)
end

--- Initialize treegen module in the given group of tests.
--
-- @tab group Group of tests.
local function init(group)
checks('table')
group._treegen = {
tempdirs = {},
templates = {}
}
end

--- Remove all temporary directories created by the test
-- unless KEEP_DATA environment variable is set to a
-- non-empty value.
--
-- @tab group Group of tests.
local function clean(group)
checks('table')
local dirs = table.copy(group._treegen.tempdirs) or {}
group._treegen.tempdirs = nil

local keep_data = (os.getenv('KEEP_DATA') or '') ~= ''

for _, dir in ipairs(dirs) do
if keep_data then
log.info('Left intact due to KEEP_DATA env var: %s', dir)
else
log.info('Recursively removing: %s', dir)
fio.rmtree(dir)
end
end

group._treegen.templates = nil
end

--- Save the template with the given pattern.
--
-- @tab group Group of tests.
-- @string pattern File name template
-- @string template A content template for creating a file.
function treegen.add_template(group, pattern, template)
checks('table', 'string', 'string')
table.insert(group._treegen.templates, {
pattern = pattern,
template = template,
})
end

--- Remove the template by pattern.
--
-- @tab group Group of tests.
-- @string pattern File name template
function treegen.remove_template(group, pattern)
checks('table', 'string')
local is_found, position, _ = pcall(find_template, group, pattern)
if is_found then
table.remove(group._treegen.templates, position)
end
end

--- Create a temporary directory with given contents.
--
-- The contents are generated using templates added by
-- treegen.add_template().
--
-- @usage
--
-- Example for {'foo/bar.lua', 'baz.lua'}:
--
-- /
-- + tmp/
-- + rfbWOJ/
-- + foo/
-- | + bar.lua
-- + baz.lua
--
-- The return value is '/tmp/rfbWOJ' for this example.
--
-- @tab group Group of tests.
-- @tab contents List of bodies of the content to write.
-- @tab[opt] replacements List of replacement templates.
-- @return string
function treegen.prepare_directory(group, contents, replacements)
checks('table', '?table', '?table')
replacements = replacements or {}

local dir = fio.tempdir()

-- fio.tempdir() follows the TMPDIR environment variable.
-- If it ends with a slash, the return value contains a double
-- slash in the middle: for example, if TMPDIR=/tmp/, the
-- result is like `/tmp//rfbWOJ`.
--
-- It looks harmless on the first glance, but this directory
-- path may be used later to form an URI for a Unix domain
-- socket. As result the URI looks like
-- `unix/:/tmp//rfbWOJ/instance-001.iproto`.
--
-- It confuses net_box.connect(): it reports EAI_NONAME error
-- from getaddrinfo().
--
-- It seems, the reason is a peculiar of the URI parsing:
--
-- tarantool> uri.parse('unix/:/foo/bar.iproto')
-- ---
-- - host: unix/
-- service: /foo/bar.iproto
-- unix: /foo/bar.iproto
-- ...
--
-- tarantool> uri.parse('unix/:/foo//bar.iproto')
-- ---
-- - host: unix
-- path: /foo//bar.iproto
-- ...
--
-- Let's normalize the path using fio.abspath(), which
-- eliminates the double slashes.
dir = fio.abspath(dir)

table.insert(group._treegen.tempdirs, dir)

for _, content in ipairs(contents) do
gen_content(group, dir, content, replacements)
end

return dir
end

hooks.before_all_preloaded(init)
hooks.after_all_preloaded(clean)

return treegen
48 changes: 48 additions & 0 deletions test/treegen_test.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local t = require('luatest')
local fio = require('fio')

local treegen = require('luatest.treegen')

local g = t.group()


local function assert_file_content_equals(file, expected)
local fh = fio.open(file)
t.assert_equals(fh:read(), expected)
end

g.test_prepare_directory = function()
treegen.add_template(g, '^.*$', 'test_script')
local dir = treegen.prepare_directory(g, {'foo/bar.lua', 'baz.lua'})

t.assert(fio.path.is_dir(dir))
t.assert(fio.path.exists(dir))

t.assert(fio.path.exists(fio.pathjoin(dir, 'foo', 'bar.lua')))
t.assert(fio.path.exists(fio.pathjoin(dir, 'baz.lua')))

assert_file_content_equals(fio.pathjoin(dir, 'foo', 'bar.lua'), 'test_script')
assert_file_content_equals(fio.pathjoin(dir, 'baz.lua'), 'test_script')
end

g.before_test('test_clean_keep_data', function()
treegen.add_template(g, '^.*$', 'test_script')

os.setenv('KEEP_DATA', 'true')

g.dir = treegen.prepare_directory(g, {'foo.lua'})

t.assert(fio.path.is_dir(g.dir))
t.assert(fio.path.exists(g.dir))
end)

g.test_clean_keep_data = function()
t.assert(fio.path.is_dir(g.dir))
t.assert(fio.path.exists(g.dir))
end

g.after_test('test_clean_keep_data', function()
os.setenv('KEEP_DATA', '')
t.assert(fio.path.is_dir(g.dir))
t.assert(fio.path.exists(g.dir))
end)

0 comments on commit 8086a58

Please sign in to comment.