|
| 1 | +--- Working tree generator. |
| 2 | +-- |
| 3 | +-- Generates a tree of Lua files using provided templates and |
| 4 | +-- filenames. |
| 5 | +-- |
| 6 | +-- @usage |
| 7 | +-- |
| 8 | +-- local t = require('luatest') |
| 9 | +-- local treegen = require('luatest.treegen') |
| 10 | +-- |
| 11 | +-- local g = t.group() |
| 12 | +-- |
| 13 | +-- g.test_foo = function(g) |
| 14 | +-- treegen.add_template('^.*$', 'test_script') |
| 15 | +-- local dir = treegen.prepare_directory({'foo/bar.lua', 'main.lua'}) |
| 16 | +-- ... |
| 17 | +-- end |
| 18 | +-- |
| 19 | +-- @module luatest.treegen |
| 20 | + |
| 21 | +local hooks = require('luatest.hooks') |
| 22 | + |
| 23 | +local fio = require('fio') |
| 24 | +local fun = require('fun') |
| 25 | +local checks = require('checks') |
| 26 | + |
| 27 | +local log = require('luatest.log') |
| 28 | + |
| 29 | +local treegen = { |
| 30 | + _group = {} |
| 31 | +} |
| 32 | + |
| 33 | +local function find_template(group, script) |
| 34 | + for position, template_def in ipairs(group._treegen.templates) do |
| 35 | + if script:match(template_def.pattern) then |
| 36 | + return position, template_def.template |
| 37 | + end |
| 38 | + end |
| 39 | + error(("treegen: can't find a template for script %q"):format(script)) |
| 40 | +end |
| 41 | + |
| 42 | +--- Write provided content into the given directory. |
| 43 | +-- |
| 44 | +-- @string directory Directory where the content will be created. |
| 45 | +-- @string filename File to write (possible nested path: /foo/bar/main.lua). |
| 46 | +-- @string content The body to write. |
| 47 | +-- @return string |
| 48 | +function treegen.write_file(directory, filename, content) |
| 49 | + checks('string', 'string', 'string') |
| 50 | + local content_abspath = fio.pathjoin(directory, filename) |
| 51 | + local flags = {'O_CREAT', 'O_WRONLY', 'O_TRUNC'} |
| 52 | + local mode = tonumber('644', 8) |
| 53 | + |
| 54 | + local contentdir_abspath = fio.dirname(content_abspath) |
| 55 | + log.info('Creating a directory: %s', contentdir_abspath) |
| 56 | + fio.mktree(contentdir_abspath) |
| 57 | + |
| 58 | + log.info('Writing a content: %s', content_abspath) |
| 59 | + local fh = fio.open(content_abspath, flags, mode) |
| 60 | + fh:write(content) |
| 61 | + fh:close() |
| 62 | + return content_abspath |
| 63 | +end |
| 64 | + |
| 65 | +-- Generate a content that follows a template and write it at the |
| 66 | +-- given path in the given directory. |
| 67 | +-- |
| 68 | +-- @table group Group of tests. |
| 69 | +-- @string directory Directory where the content will be created. |
| 70 | +-- @string filename File to write (possible nested path: /foo/bar/main.lua). |
| 71 | +-- @table replacements List of replacement templates. |
| 72 | +-- @return string |
| 73 | +local function gen_content(group, directory, filename, replacements) |
| 74 | + checks('table', 'string', 'string', 'table') |
| 75 | + local _, template = find_template(group, filename) |
| 76 | + replacements = fun.chain({filename = filename}, replacements):tomap() |
| 77 | + local body = template:gsub('<(.-)>', replacements) |
| 78 | + return treegen.write_file(directory, filename, body) |
| 79 | +end |
| 80 | + |
| 81 | +--- Initialize treegen module in the given group of tests. |
| 82 | +-- |
| 83 | +-- @tab group Group of tests. |
| 84 | +local function init(group) |
| 85 | + checks('table') |
| 86 | + group._treegen = { |
| 87 | + tempdirs = {}, |
| 88 | + templates = {} |
| 89 | + } |
| 90 | + treegen._group = group |
| 91 | +end |
| 92 | + |
| 93 | +--- Remove all temporary directories created by the test |
| 94 | +-- unless KEEP_DATA environment variable is set to a |
| 95 | +-- non-empty value. |
| 96 | +local function clean() |
| 97 | + if treegen._group._treegen == nil then |
| 98 | + return |
| 99 | + end |
| 100 | + |
| 101 | + local dirs = table.copy(treegen._group._treegen.tempdirs) or {} |
| 102 | + treegen._group._treegen.tempdirs = nil |
| 103 | + |
| 104 | + local keep_data = (os.getenv('KEEP_DATA') or '') ~= '' |
| 105 | + |
| 106 | + for _, dir in ipairs(dirs) do |
| 107 | + if keep_data then |
| 108 | + log.info('Left intact due to KEEP_DATA env var: %s', dir) |
| 109 | + else |
| 110 | + log.info('Recursively removing: %s', dir) |
| 111 | + fio.rmtree(dir) |
| 112 | + end |
| 113 | + end |
| 114 | + |
| 115 | + treegen._group._treegen.templates = nil |
| 116 | +end |
| 117 | + |
| 118 | +--- Save the template with the given pattern. |
| 119 | +-- |
| 120 | +-- @string pattern File name template |
| 121 | +-- @string template A content template for creating a file. |
| 122 | +function treegen.add_template(pattern, template) |
| 123 | + checks('string', 'string') |
| 124 | + table.insert(treegen._group._treegen.templates, { |
| 125 | + pattern = pattern, |
| 126 | + template = template, |
| 127 | + }) |
| 128 | +end |
| 129 | + |
| 130 | +--- Remove the template by pattern. |
| 131 | +-- |
| 132 | +-- @string pattern File name template |
| 133 | +function treegen.remove_template(pattern) |
| 134 | + checks('string') |
| 135 | + local is_found, position, _ = pcall(find_template, treegen._group, pattern) |
| 136 | + if is_found then |
| 137 | + table.remove(treegen._group._treegen.templates, position) |
| 138 | + end |
| 139 | +end |
| 140 | + |
| 141 | +--- Create a temporary directory with given contents. |
| 142 | +-- |
| 143 | +-- The contents are generated using templates added by |
| 144 | +-- treegen.add_template(). |
| 145 | +-- |
| 146 | +-- @usage |
| 147 | +-- |
| 148 | +-- Example for {'foo/bar.lua', 'baz.lua'}: |
| 149 | +-- |
| 150 | +-- / |
| 151 | +-- + tmp/ |
| 152 | +-- + rfbWOJ/ |
| 153 | +-- + foo/ |
| 154 | +-- | + bar.lua |
| 155 | +-- + baz.lua |
| 156 | +-- |
| 157 | +-- The return value is '/tmp/rfbWOJ' for this example. |
| 158 | +-- |
| 159 | +-- @tab contents List of bodies of the content to write. |
| 160 | +-- @tab[opt] replacements List of replacement templates. |
| 161 | +-- @return string |
| 162 | +function treegen.prepare_directory(contents, replacements) |
| 163 | + checks('?table', '?table') |
| 164 | + replacements = replacements or {} |
| 165 | + |
| 166 | + local dir = fio.tempdir() |
| 167 | + |
| 168 | + -- fio.tempdir() follows the TMPDIR environment variable. |
| 169 | + -- If it ends with a slash, the return value contains a double |
| 170 | + -- slash in the middle: for example, if TMPDIR=/tmp/, the |
| 171 | + -- result is like `/tmp//rfbWOJ`. |
| 172 | + -- |
| 173 | + -- It looks harmless on the first glance, but this directory |
| 174 | + -- path may be used later to form an URI for a Unix domain |
| 175 | + -- socket. As result the URI looks like |
| 176 | + -- `unix/:/tmp//rfbWOJ/instance-001.iproto`. |
| 177 | + -- |
| 178 | + -- It confuses net_box.connect(): it reports EAI_NONAME error |
| 179 | + -- from getaddrinfo(). |
| 180 | + -- |
| 181 | + -- It seems, the reason is a peculiar of the URI parsing: |
| 182 | + -- |
| 183 | + -- tarantool> uri.parse('unix/:/foo/bar.iproto') |
| 184 | + -- --- |
| 185 | + -- - host: unix/ |
| 186 | + -- service: /foo/bar.iproto |
| 187 | + -- unix: /foo/bar.iproto |
| 188 | + -- ... |
| 189 | + -- |
| 190 | + -- tarantool> uri.parse('unix/:/foo//bar.iproto') |
| 191 | + -- --- |
| 192 | + -- - host: unix |
| 193 | + -- path: /foo//bar.iproto |
| 194 | + -- ... |
| 195 | + -- |
| 196 | + -- Let's normalize the path using fio.abspath(), which |
| 197 | + -- eliminates the double slashes. |
| 198 | + dir = fio.abspath(dir) |
| 199 | + |
| 200 | + table.insert(treegen._group._treegen.tempdirs, dir) |
| 201 | + |
| 202 | + for _, content in ipairs(contents) do |
| 203 | + gen_content(treegen._group, dir, content, replacements) |
| 204 | + end |
| 205 | + |
| 206 | + return dir |
| 207 | +end |
| 208 | + |
| 209 | +hooks.before_all_preloaded(init) |
| 210 | +hooks.after_all_preloaded(clean) |
| 211 | + |
| 212 | +return treegen |
0 commit comments