From c940f9a3108a9eb8ad648e7520fbc68e2a1e04de Mon Sep 17 00:00:00 2001 From: better0fdead Date: Fri, 5 Jul 2024 13:20:57 +0300 Subject: [PATCH] repository: prepare for development Added CI with test + linter. Added CI to publish a rock. Added dummy tests. Added dummy role file. Added Makefile. Added rockspec. Closes #4 --- .github/workflows/publish.yml | 77 ++++++ .github/workflows/testing.yaml | 106 ++++++++ .gitignore | 12 + .luacheckrc | 19 ++ Makefile | 24 ++ metrics-export-role-scm-1.rockspec | 22 ++ roles/metrics-export.lua | 17 ++ test/helper.lua | 286 ++++++++++++++++++++++ test/helper_server.lua | 194 +++++++++++++++ test/integration/metrics-export_test.lua | 66 +++++ test/integration/simple_app/config.yaml | 17 ++ test/integration/simple_app/instances.yml | 1 + test/unit/metrics-export_test.lua | 22 ++ 13 files changed, 863 insertions(+) create mode 100644 .github/workflows/publish.yml create mode 100644 .github/workflows/testing.yaml create mode 100644 .gitignore create mode 100644 .luacheckrc create mode 100644 Makefile create mode 100644 metrics-export-role-scm-1.rockspec create mode 100644 roles/metrics-export.lua create mode 100644 test/helper.lua create mode 100644 test/helper_server.lua create mode 100644 test/integration/metrics-export_test.lua create mode 100644 test/integration/simple_app/config.yaml create mode 100644 test/integration/simple_app/instances.yml create mode 100644 test/unit/metrics-export_test.lua diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..298ba5b --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,77 @@ +name: publish + +on: + push: + branches: [master] + tags: ['*'] + +jobs: + version-check: + # We need this job to run only on push with tag. + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }} + runs-on: ubuntu-22.04 + steps: + - name: Check module version + uses: tarantool/actions/check-module-version@master + with: + module-name: 'metrics-export-role' + + publish-rockspec-scm-1: + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - uses: tarantool/rocks.tarantool.org/github-action@master + with: + auth: ${{ secrets.ROCKS_AUTH }} + files: metrics-export-role-scm-1.rockspec + + publish-rockspec-tag: + if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }} + needs: version-check + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + # Create a rockspec for the release. + - run: printf '%s=%s\n' TAG "${GITHUB_REF##*/}" >> "${GITHUB_ENV}" + - run: sed -E + -e 's/branch = ".+"/tag = "${{ env.TAG }}"/g' + -e 's/version = ".+"/version = "${{ env.TAG }}-1"/g' + metrics-export-role-scm-1.rockspec > metrics-export-role-${{ env.TAG }}-1.rockspec + + - name: Setup tt + run: | + curl -L https://tarantool.io/XodVosJ/release/3/installer.sh | bash + sudo apt install -y tt + tt version + + # Create a rock for the release (.all.rock). + # + # `tt rocks pack ` creates + # .all.rock tarball. It speeds up + # `tt rocks install ` and + # frees it from dependency on git. + # + # Don't confuse this command with + # `tt rocks pack `, which creates a + # source tarball (.src.rock). + # + # Important: Don't upload binary rocks to + # rocks.tarantool.org. Lua/C modules should be packed into + # .src.rock instead. See [1] for description of rock types. + # + # [1]: https://github.com/luarocks/luarocks/wiki/Types-of-rocks + - uses: tarantool/setup-tarantool@v3 + with: + tarantool-version: '3.1' + - run: tt rocks install metrics-export-role-${{ env.TAG }}-1.rockspec + - run: tt rocks pack metrics-export-role ${{ env.TAG }} + + # Upload .rockspec and .all.rock. + - uses: tarantool/rocks.tarantool.org/github-action@master + with: + auth: ${{ secrets.ROCKS_AUTH }} + files: | + metrics-export-role-${{ env.TAG }}-1.rockspec + metrics-export-role-${{ env.TAG }}-1.all.rock diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml new file mode 100644 index 0000000..ce7aa07 --- /dev/null +++ b/.github/workflows/testing.yaml @@ -0,0 +1,106 @@ +name: testing + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + linux: + # We want to run on external PRs, but not on our own internal + # PRs as they'll be run by the push to the branch. + # + # The main trick is described here: + # https://github.com/Dart-Code/Dart-Code/pull/2375 + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository + + strategy: + fail-fast: false + matrix: + tarantool: + - 'debug-master' + - '3.1' + + env: + TNT_DEBUG_PATH: /home/runner/tnt-debug + + runs-on: ubuntu-22.04 + steps: + - name: Install tarantool ${{ matrix.tarantool }} + if: startsWith(matrix.tarantool, 'debug') != true + uses: tarantool/setup-tarantool@v3 + with: + tarantool-version: ${{ matrix.tarantool }} + + - name: Create variables for Tarantool ${{ matrix.tarantool }} + if: startsWith(matrix.tarantool, 'debug') + run: | + branch=$(echo ${{ matrix.tarantool }} | cut -d- -f2) + commit_hash=$(git ls-remote https://github.com/tarantool/tarantool.git --branch ${branch} | head -c 8) + echo "TNT_BRANCH=${branch}" >> $GITHUB_ENV + echo "VERSION_POSTFIX=-${commit_hash}" >> $GITHUB_ENV + shell: bash + + - name: Cache tarantool build + if: startsWith(matrix.tarantool, 'debug') + id: cache-tnt-debug + uses: actions/cache@v3 + with: + path: ${{ env.TNT_DEBUG_PATH }} + key: cache-tnt-${{ matrix.tarantool }}${{ env.VERSION_POSTFIX }} + + - name: Clone tarantool ${{ matrix.tarantool }} + if: startsWith(matrix.tarantool, 'debug') && steps.cache-tnt-debug.outputs.cache-hit != 'true' + uses: actions/checkout@v3 + with: + repository: tarantool/tarantool + ref: ${{ env.TNT_BRANCH }} + path: tarantool + fetch-depth: 0 + submodules: true + + - name: Build tarantool ${{ matrix.tarantool }} + if: startsWith(matrix.tarantool, 'debug') && steps.cache-tnt-debug.outputs.cache-hit != 'true' + run: | + sudo apt-get -y install git build-essential cmake make zlib1g-dev \ + libreadline-dev libncurses5-dev libssl-dev \ + libunwind-dev libicu-dev python3 python3-yaml \ + python3-six python3-gevent + cd ${GITHUB_WORKSPACE}/tarantool + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_DIST=ON + make + make DESTDIR=${TNT_DEBUG_PATH} install + + - name: Install tarantool ${{ matrix.tarantool }} + if: startsWith(matrix.tarantool, 'debug') + run: | + sudo cp -rvP ${TNT_DEBUG_PATH}/usr/local/* /usr/local/ + + - name: Clone the module + uses: actions/checkout@v3 + + - name: Cache rocks + uses: actions/cache@v3 + id: cache-rocks + with: + path: .rocks/ + key: "cache-rocks-${{ matrix.tarantool }}${{ env.VERSION_POSTFIX }}-\ + ${{ matrix.cartridge-version }}-\ + ${{ matrix.metrics-version }}" + + - name: Setup tt + run: | + curl -L https://tarantool.io/XodVosJ/release/3/installer.sh | bash + sudo apt install -y tt + tt version + + - name: Install requirements + run: make deps + if: steps.cache-rocks.outputs.cache-hit != 'true' + + - run: echo $PWD/.rocks/bin >> $GITHUB_PATH + + - run: make check + + - run: make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..390299b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*~ +*.snap +*.xlog +*.log +VERSION +luacov.stats.out +luacov.report.out +tags +.rocks +doc/apidoc/ +.idea +*.swp diff --git a/.luacheckrc b/.luacheckrc new file mode 100644 index 0000000..668d06c --- /dev/null +++ b/.luacheckrc @@ -0,0 +1,19 @@ +globals = { + "box", + "_TARANTOOL", +} + +ignore = { + -- Shadowing an upvalue argument. + "432", +} + +include_files = { + '.luacheckrc', + '*.rockspec', + '**/*.lua', +} + +exclude_files = { + '.rocks', +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..451c8a6 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +# This way everything works as expected ever for +# `make -C /path/to/project` or +# `make -f /path/to/project/Makefile`. +MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +PROJECT_DIR := $(patsubst %/,%,$(dir $(MAKEFILE_PATH))) + +SHELL := $(shell which bash) +SEED ?= $(shell /bin/bash -c "echo $$RANDOM") + +all: test + +check: luacheck + +luacheck: + luacheck --config .luacheckrc --codes . + +.PHONY: test +test: + luatest -v --shuffle all:${SEED} + +deps: + tt rocks install luatest 1.0.1 + tt rocks install luacheck 0.26.0 + tt rocks make diff --git a/metrics-export-role-scm-1.rockspec b/metrics-export-role-scm-1.rockspec new file mode 100644 index 0000000..d23c163 --- /dev/null +++ b/metrics-export-role-scm-1.rockspec @@ -0,0 +1,22 @@ +package = "metrics-export-role" +version = "scm-1" +source = { + url = "git+https://github.com/tarantool/metrics-export-role", + branch = "master", +} +description = { + summary = "The Tarantool 3 role for metrics export via HTTP", + homepage = "https://github.com/tarantool/metrics-export-role", + license = "BSD2", + maintainer = "Fedor Terekhin " +} +dependencies = { + "lua >= 5.1", +} +build = { + type = "builtin", + modules = { + ["roles.metrics-export"] = "roles/metrics-export.lua" + } +} +-- vim: syntax=lua diff --git a/roles/metrics-export.lua b/roles/metrics-export.lua new file mode 100644 index 0000000..2ec614c --- /dev/null +++ b/roles/metrics-export.lua @@ -0,0 +1,17 @@ +local function validate() + +end + +local function apply() + +end + +local function stop() + +end + +return { + validate = validate, + apply = apply, + stop = stop, +} diff --git a/test/helper.lua b/test/helper.lua new file mode 100644 index 0000000..0183be3 --- /dev/null +++ b/test/helper.lua @@ -0,0 +1,286 @@ +local t = require("luatest") +local fio = require("fio") + +local helpers = require("luatest.helpers") + +helpers.project_root = fio.dirname(debug.sourcedir()) + +function helpers.create_space(space_name, engine) + local space_format = { + { + name = "id", + type = "number" + }, + { + name = "first_name", + type = "string" + }, + { + name = "value", + type = "number", + is_nullable = true + }, + { + name = "count", + type = "number", + is_nullable = true + }, + { + name = "non_unique_id", + type = "number", + is_nullable = true, + }, + { + name = "json_path_field", + is_nullable = true, + }, + { + name = "multikey_field", + is_nullable = true + }, + { + name = "functional_field", + is_nullable = true + }, + } + + local space = box.schema.create_space(space_name, { + engine = engine + }) + space:format(space_format) + + return space +end + +function helpers.create_space_with_tree_index(engine) + local space = helpers.create_space("tree", engine) + + space:create_index("primary", { + type = "TREE", + parts = { + { + field = 1 + } + } + }) + space:create_index("index_for_first_name", { + type = "TREE", + parts = { + { + field = 2 + } + } + }) + space:create_index("multipart_index", { + type = "TREE", + parts = { + { + field = 3, + is_nullable = true + }, + { + field = 4, + is_nullable = true + } + } + }) + space:create_index("non_unique_index", { + type = "TREE", + parts = { + { + field = 5, + is_nullable = true + } + }, + unique = false + }) + + if _TARANTOOL >= "2" then + space:create_index("json_path_index", { + type = "TREE", + parts = { + { + field = 6, + type = "scalar", + path = "age", + is_nullable = true + } + } + }) + space:create_index("multikey_index", { + type = "TREE", + parts = { + { + field = 7, + type = "str", + path = "data[*].name" + } + } + }) + if engine ~= "vinyl" then + space:create_index("functional_index", { + type = "TREE", + parts = { + { + field = 1, + type = "string" + } + }, + func = "tree_func" + }) + end + end + + return space +end + +function helpers.create_space_with_hash_index(engine) + local space = helpers.create_space("hash", engine) + space:create_index("primary", { + type = "HASH", + parts = { + { + field = 1 + } + } + }) + space:create_index("index_for_first_name", { + type = "HASH", + parts = { + { + field = 2 + } + } + }) + space:create_index("multipart_index", { + type = "HASH", + parts = { + { + field = 1 + }, + { + field = 2 + } + } + }) + + return space +end + +function helpers.create_space_with_bitset_index(engine) + local space = helpers.create_space("bitset", engine) + space:create_index("primary", { + type = "TREE", + parts = { + { + field = 1 + } + } + }) + space:create_index("index_for_first_name", { + type = "BITSET", + parts = { + { + field = 2, + type = "string" + } + }, + unique = false + }) + + return space +end + +t.after_suite(function() + fio.rmtree(t.datadir) +end) + +t.before_suite(function() + t.datadir = fio.tempdir() + box.cfg{ + wal_dir = t.datadir, + memtx_dir = t.datadir, + vinyl_dir = t.datadir, + } + + local tree_code = [[function(tuple) + if tuple[8] then + return {string.sub(tuple[8],2,2)} + end + return {tuple[2]} + end]] + if _TARANTOOL >= "2" then + box.schema.func.create("tree_func", { + body = tree_code, + is_deterministic = true, + is_sandboxed = true + }) + end +end) + +function helpers.is_metrics_supported() + local is_package, metrics = pcall(require, "metrics") + if not is_package then + return false + end + -- metrics >= 0.11.0 is required + local counter = require('metrics.collectors.counter') + return metrics.unregister_callback and counter.remove +end + +function helpers.iterate_with_func(task) + return task.index:pairs(task.start_key(), { iterator = task.iterator_type }) + :take_while( + function() + return task:process_while() + end + ) +end + +helpers.iteration_result = {} +function helpers.is_expired_debug(_, tuple) + table.insert(helpers.iteration_result, tuple) + return true +end + +function helpers.tarantool_version() + local major_minor_patch = _G._TARANTOOL:split('-', 1)[1] + local major_minor_patch_parts = major_minor_patch:split('.', 2) + + local major = tonumber(major_minor_patch_parts[1]) + local minor = tonumber(major_minor_patch_parts[2]) + local patch = tonumber(major_minor_patch_parts[3]) + + return major, minor, patch +end + +function helpers.tarantool_role_is_supported() + local major, _, _ = helpers.tarantool_version() + return major >= 3 +end + +function helpers.error_function() + error("error function call") +end + +function helpers.get_error_function(error_msg) + return function() + error(error_msg) + end +end + +function helpers.create_persistent_function(name, body) + box.schema.func.create(name, { + body = body or "function(...) return true end", + if_not_exists = true + }) +end + +local root = fio.dirname(fio.dirname(fio.abspath(package.search('test.helper')))) + +helpers.lua_path = root .. '/?.lua;' .. + root .. '/?/init.lua;' .. + root .. '/.rocks/share/tarantool/?.lua;' .. + root .. '/.rocks/share/tarantool/?/init.lua' + +return helpers diff --git a/test/helper_server.lua b/test/helper_server.lua new file mode 100644 index 0000000..d42da4e --- /dev/null +++ b/test/helper_server.lua @@ -0,0 +1,194 @@ +-- https://github.com/tarantool/tarantool/blob/5040fba9cf1da942371721e36e81c7372699600c/test/luatest_helpers/server.lua +local fun = require('fun') +local yaml = require('yaml') +local urilib = require('uri') +local fio = require('fio') +local luatest = require('luatest') + +-- Join paths in an intuitive way. +-- +-- If a component is nil, it is skipped. +-- +-- If a component is an absolute path, it skips all the previous +-- components. +-- +-- The wrapper is written for two components for simplicity. +local function pathjoin(a, b) + -- No first path -- skip it. + if a == nil then + return b + end + -- No second path -- skip it. + if b == nil then + return a + end + -- The absolute path is checked explicitly due to gh-8816. + if b:startswith('/') then + return b + end + return fio.pathjoin(a, b) +end + +-- Determine advertise URI for given instance from a cluster +-- configuration. +local function find_advertise_uri(config, instance_name, dir) + if config == nil or next(config) == nil then + return nil + end + + -- Determine listen and advertise options that are in effect + -- for the given instance. + local advertise + local listen + + for _, group in pairs(config.groups or {}) do + for _, replicaset in pairs(group.replicasets or {}) do + local instance = (replicaset.instances or {})[instance_name] + if instance == nil then + break + end + if instance.iproto ~= nil then + if instance.iproto.advertise ~= nil then + advertise = advertise or instance.iproto.advertise.client + end + listen = listen or instance.iproto.listen + end + if replicaset.iproto ~= nil then + if replicaset.iproto.advertise ~= nil then + advertise = advertise or replicaset.iproto.advertise.client + end + listen = listen or replicaset.iproto.listen + end + if group.iproto ~= nil then + if group.iproto.advertise ~= nil then + advertise = advertise or group.iproto.advertise.client + end + listen = listen or group.iproto.listen + end + end + end + + if config.iproto ~= nil then + if config.iproto.advertise ~= nil then + advertise = advertise or config.iproto.advertise.client + end + listen = listen or config.iproto.listen + end + + local uris + if advertise ~= nil then + uris = {{uri = advertise}} + else + uris = listen + end + + for _, uri in ipairs(uris or {}) do + uri = table.copy(uri) + uri.uri = uri.uri:gsub('{{ *instance_name *}}', instance_name) + uri.uri = uri.uri:gsub('unix/:%./', ('unix/:%s/'):format(dir)) + local u = urilib.parse(uri) + if u.ipv4 ~= '0.0.0.0' and u.ipv6 ~= '::' and u.service ~= '0' then + return uri + end + end + error('No suitable URI to connect is found') +end + +local Server = luatest.Server:inherit({}) + +-- Adds the following options: +-- +-- * config_file (string) +-- +-- An argument of the `--config <...>` CLI option. +-- +-- Used to deduce advertise URI to connect net.box to the +-- instance. +-- +-- The special value '' means running without `--config <...>` +-- CLI option (but still pass `--name `). +-- * remote_config (table) +-- +-- If `config_file` is not passed, this config value is used to +-- deduce the advertise URI to connect net.box to the instance. +Server.constructor_checks = fun.chain(Server.constructor_checks, { + config_file = 'string', + remote_config = '?table', +}):tomap() + +function Server:initialize() + if self.config_file ~= nil then + self.command = arg[-1] + + self.args = fun.chain(self.args or {}, { + '--name', self.alias + }):totable() + + if self.config_file ~= '' then + table.insert(self.args, '--config') + table.insert(self.args, self.config_file) + + -- Take into account self.chdir to calculate a config + -- file path. + local config_file_path = pathjoin(self.chdir, self.config_file) + + -- Read the provided config file. + local fh, err = fio.open(config_file_path, {'O_RDONLY'}) + if fh == nil then + error(('Unable to open file %q: %s'):format(config_file_path, + err)) + end + self.config = yaml.decode(fh:read()) + fh:close() + end + + if self.net_box_uri == nil then + local config = self.config or self.remote_config + + -- NB: listen and advertise URIs are relative to + -- process.work_dir, which, in turn, is relative to + -- self.chdir. + local work_dir + if config.process ~= nil and config.process.work_dir ~= nil then + work_dir = config.process.work_dir + end + local dir = pathjoin(self.chdir, work_dir) + self.net_box_uri = find_advertise_uri(config, self.alias, dir) + end + end + getmetatable(getmetatable(self)).initialize(self) +end + +function Server:connect_net_box() + getmetatable(getmetatable(self)).connect_net_box(self) + + if self.config_file == nil then + return + end + + if not self.net_box then + return + end + + -- Replace the ready condition. + local saved_eval = self.net_box.eval + self.net_box.eval = function(self, expr, args, opts) + if expr == 'return _G.ready' then + expr = "return require('config'):info().status == 'ready' or " .. + "require('config'):info().status == 'check_warnings'" + end + return saved_eval(self, expr, args, opts) + end +end + +-- Enable the startup waiting if the advertise URI of the instance +-- is determined. +function Server:start(opts) + opts = opts or {} + if self.config_file and opts.wait_until_ready == nil then + opts.wait_until_ready = self.net_box_uri ~= nil + end + getmetatable(getmetatable(self)).start(self, opts) +end + +return Server diff --git a/test/integration/metrics-export_test.lua b/test/integration/metrics-export_test.lua new file mode 100644 index 0000000..039bb79 --- /dev/null +++ b/test/integration/metrics-export_test.lua @@ -0,0 +1,66 @@ +local t = require('luatest') +local fio = require('fio') + +local helpers = require('test.helper') +local Server = require('test.helper_server') + +local g = t.group('metrics_export_integration_test') + +g.before_all(function (cg) + t.skip_if(not helpers.tarantool_role_is_supported(), + 'Tarantool role is supported only for Tarantool starting from v3.0.0') + + local workdir = fio.tempdir() + cg.router = Server:new({ + config_file = fio.abspath(fio.pathjoin('test', 'integration', 'simple_app', 'config.yaml')), + env = {LUA_PATH = helpers.lua_path}, + chdir = workdir, + alias = 'master', + workdir = workdir, + }) +end) + +g.before_each(function(cg) + fio.mktree(cg.router.workdir) + + -- We start instance before each test because + -- we need to force reload of metrics-export role and also instance environment + -- from previous tests can influence test result. + -- (e.g function creation, when testing that role doesn't start w/o it) + -- Restarting instance is the easiest way to achieve it. + -- It takes around 1s to start an instance, which considering small amount + -- of integration tests is not a problem. + cg.router:start{wait_until_ready = true} +end) + +g.after_each(function(cg) + cg.router:stop() + fio.rmtree(cg.router.workdir) +end) + +g.test_dummy = function(cg) + cg.router:exec(function() + box.schema.create_space('users', {if_not_exists = true}) + + box.space.users:format({ + {name = 'id', type = 'unsigned'}, + {name = 'first name', type = 'string'}, + {name = 'second name', type = 'string', is_nullable = true}, + {name = 'age', type = 'number', is_nullable = false}, + }) + + box.space.users:create_index('primary', { + parts = { + {field = 1, type = 'unsigned'}, + }, + }) + + box.space.users:insert{1, 'Samantha', 'Carter', 30} + box.space.users:insert{2, 'Fay', 'Rivers', 41} + box.space.users:insert{3, 'Zachariah', 'Peters', 13} + box.space.users:insert{4, 'Milo', 'Walters', 74} + end) + t.assert_equals(cg.router:exec(function() + return #box.space.users:select({}, {limit = 10}) + end), 4) +end diff --git a/test/integration/simple_app/config.yaml b/test/integration/simple_app/config.yaml new file mode 100644 index 0000000..69fe25f --- /dev/null +++ b/test/integration/simple_app/config.yaml @@ -0,0 +1,17 @@ +credentials: + users: + guest: + roles: [super] + +groups: + group-001: + replicasets: + replicaset-001: + roles: [roles.metrics-export] + instances: + master: + iproto: + listen: + - uri: '127.0.0.1:3313' + database: + mode: rw diff --git a/test/integration/simple_app/instances.yml b/test/integration/simple_app/instances.yml new file mode 100644 index 0000000..b79b9b8 --- /dev/null +++ b/test/integration/simple_app/instances.yml @@ -0,0 +1 @@ +master: diff --git a/test/unit/metrics-export_test.lua b/test/unit/metrics-export_test.lua new file mode 100644 index 0000000..48a77d3 --- /dev/null +++ b/test/unit/metrics-export_test.lua @@ -0,0 +1,22 @@ +local t = require('luatest') +local helpers = require('test.helper') + +local g = t.group('metrics_export_unit_test') + +g.before_all(function() + t.skip_if(not helpers.tarantool_role_is_supported(), + 'Tarantool role is supported only for Tarantool starting from v3.0.0') + g.default_cfg = { } +end) + +g.before_each(function() + g.role = require('roles.metrics-export') +end) + +g.after_each(function() + g.role.stop() +end) + +function g.test_dummy() + t.assert_equals(g.role.validate(), nil) +end