mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Rewrite the reactor unit tests
These have been skipped for a while now because they didn't work correctly. The old tests have been scrapped in favor of new ones that test both the old and new config schema.
This commit is contained in:
parent
2a35ab7f39
commit
531cac610e
1 changed files with 542 additions and 60 deletions
|
@ -1,74 +1,556 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import
|
||||
import time
|
||||
import shutil
|
||||
import tempfile
|
||||
import codecs
|
||||
import glob
|
||||
import logging
|
||||
import os
|
||||
|
||||
from contextlib import contextmanager
|
||||
import textwrap
|
||||
import yaml
|
||||
|
||||
import salt.utils
|
||||
from salt.utils.process import clean_proc
|
||||
import salt.loader
|
||||
import salt.utils.reactor as reactor
|
||||
|
||||
from tests.integration import AdaptedConfigurationTestCaseMixin
|
||||
from tests.support.paths import TMP
|
||||
from tests.support.unit import TestCase, skipIf
|
||||
from tests.support.mock import patch, MagicMock
|
||||
from tests.support.mixins import AdaptedConfigurationTestCaseMixin
|
||||
from tests.support.mock import (
|
||||
NO_MOCK,
|
||||
NO_MOCK_REASON,
|
||||
patch,
|
||||
MagicMock,
|
||||
Mock,
|
||||
mock_open,
|
||||
)
|
||||
|
||||
REACTOR_CONFIG = '''\
|
||||
reactor:
|
||||
- old_runner:
|
||||
- /srv/reactor/old_runner.sls
|
||||
- old_wheel:
|
||||
- /srv/reactor/old_wheel.sls
|
||||
- old_local:
|
||||
- /srv/reactor/old_local.sls
|
||||
- old_cmd:
|
||||
- /srv/reactor/old_cmd.sls
|
||||
- old_caller:
|
||||
- /srv/reactor/old_caller.sls
|
||||
- new_runner:
|
||||
- /srv/reactor/new_runner.sls
|
||||
- new_wheel:
|
||||
- /srv/reactor/new_wheel.sls
|
||||
- new_local:
|
||||
- /srv/reactor/new_local.sls
|
||||
- new_cmd:
|
||||
- /srv/reactor/new_cmd.sls
|
||||
- new_caller:
|
||||
- /srv/reactor/new_caller.sls
|
||||
'''
|
||||
|
||||
REACTOR_DATA = {
|
||||
'runner': {'data': {'message': 'This is an error'}},
|
||||
'wheel': {'data': {'id': 'foo'}},
|
||||
'local': {'data': {'pkg': 'zsh', 'repo': 'updates'}},
|
||||
'cmd': {'data': {'pkg': 'zsh', 'repo': 'updates'}},
|
||||
'caller': {'data': {'path': '/tmp/foo'}},
|
||||
}
|
||||
|
||||
SLS = {
|
||||
'/srv/reactor/old_runner.sls': textwrap.dedent('''\
|
||||
raise_error:
|
||||
runner.error.error:
|
||||
- name: Exception
|
||||
- message: {{ data['data']['message'] }}
|
||||
'''),
|
||||
'/srv/reactor/old_wheel.sls': textwrap.dedent('''\
|
||||
remove_key:
|
||||
wheel.key.delete:
|
||||
- match: {{ data['data']['id'] }}
|
||||
'''),
|
||||
'/srv/reactor/old_local.sls': textwrap.dedent('''\
|
||||
install_zsh:
|
||||
local.state.single:
|
||||
- tgt: test
|
||||
- arg:
|
||||
- pkg.installed
|
||||
- {{ data['data']['pkg'] }}
|
||||
- kwarg:
|
||||
fromrepo: {{ data['data']['repo'] }}
|
||||
'''),
|
||||
'/srv/reactor/old_cmd.sls': textwrap.dedent('''\
|
||||
install_zsh:
|
||||
cmd.state.single:
|
||||
- tgt: test
|
||||
- arg:
|
||||
- pkg.installed
|
||||
- {{ data['data']['pkg'] }}
|
||||
- kwarg:
|
||||
fromrepo: {{ data['data']['repo'] }}
|
||||
'''),
|
||||
'/srv/reactor/old_caller.sls': textwrap.dedent('''\
|
||||
touch_file:
|
||||
caller.file.touch:
|
||||
- args:
|
||||
- {{ data['data']['path'] }}
|
||||
'''),
|
||||
'/srv/reactor/new_runner.sls': textwrap.dedent('''\
|
||||
raise_error:
|
||||
runner.error.error:
|
||||
- args:
|
||||
- name: Exception
|
||||
- message: {{ data['data']['message'] }}
|
||||
'''),
|
||||
'/srv/reactor/new_wheel.sls': textwrap.dedent('''\
|
||||
remove_key:
|
||||
wheel.key.delete:
|
||||
- args:
|
||||
- match: {{ data['data']['id'] }}
|
||||
'''),
|
||||
'/srv/reactor/new_local.sls': textwrap.dedent('''\
|
||||
install_zsh:
|
||||
local.state.single:
|
||||
- tgt: test
|
||||
- args:
|
||||
- fun: pkg.installed
|
||||
- name: {{ data['data']['pkg'] }}
|
||||
- fromrepo: {{ data['data']['repo'] }}
|
||||
'''),
|
||||
'/srv/reactor/new_cmd.sls': textwrap.dedent('''\
|
||||
install_zsh:
|
||||
cmd.state.single:
|
||||
- tgt: test
|
||||
- args:
|
||||
- fun: pkg.installed
|
||||
- name: {{ data['data']['pkg'] }}
|
||||
- fromrepo: {{ data['data']['repo'] }}
|
||||
'''),
|
||||
'/srv/reactor/new_caller.sls': textwrap.dedent('''\
|
||||
touch_file:
|
||||
caller.file.touch:
|
||||
- args:
|
||||
- name: {{ data['data']['path'] }}
|
||||
'''),
|
||||
}
|
||||
|
||||
LOW_CHUNKS = {
|
||||
# Note that the "name" value in the chunk has been overwritten by the
|
||||
# "name" argument in the SLS. This is one reason why the new schema was
|
||||
# needed.
|
||||
'old_runner': [{
|
||||
'state': 'runner',
|
||||
'__id__': 'raise_error',
|
||||
'__sls__': '/srv/reactor/old_runner.sls',
|
||||
'order': 1,
|
||||
'fun': 'error.error',
|
||||
'name': 'Exception',
|
||||
'message': 'This is an error',
|
||||
}],
|
||||
'old_wheel': [{
|
||||
'state': 'wheel',
|
||||
'__id__': 'remove_key',
|
||||
'name': 'remove_key',
|
||||
'__sls__': '/srv/reactor/old_wheel.sls',
|
||||
'order': 1,
|
||||
'fun': 'key.delete',
|
||||
'match': 'foo',
|
||||
}],
|
||||
'old_local': [{
|
||||
'state': 'local',
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/old_local.sls',
|
||||
'order': 1,
|
||||
'tgt': 'test',
|
||||
'fun': 'state.single',
|
||||
'arg': ['pkg.installed', 'zsh'],
|
||||
'kwarg': {'fromrepo': 'updates'},
|
||||
}],
|
||||
'old_cmd': [{
|
||||
'state': 'local', # 'cmd' should be aliased to 'local'
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/old_cmd.sls',
|
||||
'order': 1,
|
||||
'tgt': 'test',
|
||||
'fun': 'state.single',
|
||||
'arg': ['pkg.installed', 'zsh'],
|
||||
'kwarg': {'fromrepo': 'updates'},
|
||||
}],
|
||||
'old_caller': [{
|
||||
'state': 'caller',
|
||||
'__id__': 'touch_file',
|
||||
'name': 'touch_file',
|
||||
'__sls__': '/srv/reactor/old_caller.sls',
|
||||
'order': 1,
|
||||
'fun': 'file.touch',
|
||||
'args': ['/tmp/foo'],
|
||||
}],
|
||||
'new_runner': [{
|
||||
'state': 'runner',
|
||||
'__id__': 'raise_error',
|
||||
'name': 'raise_error',
|
||||
'__sls__': '/srv/reactor/new_runner.sls',
|
||||
'order': 1,
|
||||
'fun': 'error.error',
|
||||
'args': [
|
||||
{'name': 'Exception'},
|
||||
{'message': 'This is an error'},
|
||||
],
|
||||
}],
|
||||
'new_wheel': [{
|
||||
'state': 'wheel',
|
||||
'__id__': 'remove_key',
|
||||
'name': 'remove_key',
|
||||
'__sls__': '/srv/reactor/new_wheel.sls',
|
||||
'order': 1,
|
||||
'fun': 'key.delete',
|
||||
'args': [
|
||||
{'match': 'foo'},
|
||||
],
|
||||
}],
|
||||
'new_local': [{
|
||||
'state': 'local',
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/new_local.sls',
|
||||
'order': 1,
|
||||
'tgt': 'test',
|
||||
'fun': 'state.single',
|
||||
'args': [
|
||||
{'fun': 'pkg.installed'},
|
||||
{'name': 'zsh'},
|
||||
{'fromrepo': 'updates'},
|
||||
],
|
||||
}],
|
||||
'new_cmd': [{
|
||||
'state': 'local',
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/new_cmd.sls',
|
||||
'order': 1,
|
||||
'tgt': 'test',
|
||||
'fun': 'state.single',
|
||||
'args': [
|
||||
{'fun': 'pkg.installed'},
|
||||
{'name': 'zsh'},
|
||||
{'fromrepo': 'updates'},
|
||||
],
|
||||
}],
|
||||
'new_caller': [{
|
||||
'state': 'caller',
|
||||
'__id__': 'touch_file',
|
||||
'name': 'touch_file',
|
||||
'__sls__': '/srv/reactor/new_caller.sls',
|
||||
'order': 1,
|
||||
'fun': 'file.touch',
|
||||
'args': [
|
||||
{'name': '/tmp/foo'},
|
||||
],
|
||||
}],
|
||||
}
|
||||
|
||||
WRAPPER_CALLS = {
|
||||
'old_runner': (
|
||||
'error.error',
|
||||
{
|
||||
'__state__': 'runner',
|
||||
'__id__': 'raise_error',
|
||||
'__sls__': '/srv/reactor/old_runner.sls',
|
||||
'__user__': 'Reactor',
|
||||
'order': 1,
|
||||
'arg': [],
|
||||
'kwarg': {
|
||||
'name': 'Exception',
|
||||
'message': 'This is an error',
|
||||
},
|
||||
'name': 'Exception',
|
||||
'message': 'This is an error',
|
||||
},
|
||||
),
|
||||
'old_wheel': (
|
||||
'key.delete',
|
||||
{
|
||||
'__state__': 'wheel',
|
||||
'__id__': 'remove_key',
|
||||
'name': 'remove_key',
|
||||
'__sls__': '/srv/reactor/old_wheel.sls',
|
||||
'order': 1,
|
||||
'__user__': 'Reactor',
|
||||
'arg': ['foo'],
|
||||
'kwarg': {},
|
||||
'match': 'foo',
|
||||
},
|
||||
),
|
||||
'old_local': {
|
||||
'args': ('test', 'state.single'),
|
||||
'kwargs': {
|
||||
'state': 'local',
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/old_local.sls',
|
||||
'order': 1,
|
||||
'arg': ['pkg.installed', 'zsh'],
|
||||
'kwarg': {'fromrepo': 'updates'},
|
||||
},
|
||||
},
|
||||
'old_cmd': {
|
||||
'args': ('test', 'state.single'),
|
||||
'kwargs': {
|
||||
'state': 'local',
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/old_cmd.sls',
|
||||
'order': 1,
|
||||
'arg': ['pkg.installed', 'zsh'],
|
||||
'kwarg': {'fromrepo': 'updates'},
|
||||
},
|
||||
},
|
||||
'old_caller': {
|
||||
'args': ('file.touch', '/tmp/foo'),
|
||||
'kwargs': {},
|
||||
},
|
||||
'new_runner': (
|
||||
'error.error',
|
||||
{
|
||||
'__state__': 'runner',
|
||||
'__id__': 'raise_error',
|
||||
'name': 'raise_error',
|
||||
'__sls__': '/srv/reactor/new_runner.sls',
|
||||
'__user__': 'Reactor',
|
||||
'order': 1,
|
||||
'arg': (),
|
||||
'kwarg': {
|
||||
'name': 'Exception',
|
||||
'message': 'This is an error',
|
||||
},
|
||||
},
|
||||
),
|
||||
'new_wheel': (
|
||||
'key.delete',
|
||||
{
|
||||
'__state__': 'wheel',
|
||||
'__id__': 'remove_key',
|
||||
'name': 'remove_key',
|
||||
'__sls__': '/srv/reactor/new_wheel.sls',
|
||||
'order': 1,
|
||||
'__user__': 'Reactor',
|
||||
'arg': (),
|
||||
'kwarg': {'match': 'foo'},
|
||||
},
|
||||
),
|
||||
'new_local': {
|
||||
'args': ('test', 'state.single'),
|
||||
'kwargs': {
|
||||
'state': 'local',
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/new_local.sls',
|
||||
'order': 1,
|
||||
'arg': (),
|
||||
'kwarg': {
|
||||
'fun': 'pkg.installed',
|
||||
'name': 'zsh',
|
||||
'fromrepo': 'updates',
|
||||
},
|
||||
},
|
||||
},
|
||||
'new_cmd': {
|
||||
'args': ('test', 'state.single'),
|
||||
'kwargs': {
|
||||
'state': 'local',
|
||||
'__id__': 'install_zsh',
|
||||
'name': 'install_zsh',
|
||||
'__sls__': '/srv/reactor/new_cmd.sls',
|
||||
'order': 1,
|
||||
'arg': (),
|
||||
'kwarg': {
|
||||
'fun': 'pkg.installed',
|
||||
'name': 'zsh',
|
||||
'fromrepo': 'updates',
|
||||
},
|
||||
},
|
||||
},
|
||||
'new_caller': {
|
||||
'args': ('file.touch',),
|
||||
'kwargs': {'name': '/tmp/foo'},
|
||||
},
|
||||
}
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def reactor_process(opts, reactor):
|
||||
opts = dict(opts)
|
||||
opts['reactor'] = reactor
|
||||
proc = reactor.Reactor(opts)
|
||||
proc.start()
|
||||
try:
|
||||
if os.environ.get('TRAVIS_PYTHON_VERSION', None) is not None:
|
||||
# Travis is slow
|
||||
time.sleep(10)
|
||||
else:
|
||||
time.sleep(2)
|
||||
yield
|
||||
finally:
|
||||
clean_proc(proc)
|
||||
|
||||
|
||||
def _args_sideffect(*args, **kwargs):
|
||||
return args, kwargs
|
||||
|
||||
|
||||
@skipIf(True, 'Skipping until its clear what and how is this supposed to be testing')
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
class TestReactor(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
def setUp(self):
|
||||
self.opts = self.get_temp_config('master')
|
||||
self.tempdir = tempfile.mkdtemp(dir=TMP)
|
||||
self.sls_name = os.path.join(self.tempdir, 'test.sls')
|
||||
with salt.utils.fopen(self.sls_name, 'w') as fh:
|
||||
fh.write('''
|
||||
update_fileserver:
|
||||
runner.fileserver.update
|
||||
''')
|
||||
'''
|
||||
Tests for constructing the low chunks to be executed via the Reactor
|
||||
'''
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
'''
|
||||
Load the reactor config for mocking
|
||||
'''
|
||||
cls.opts = cls.get_temp_config('master')
|
||||
reactor_config = yaml.safe_load(REACTOR_CONFIG)
|
||||
cls.opts.update(reactor_config)
|
||||
cls.reactor = reactor.Reactor(cls.opts)
|
||||
cls.reaction_map = salt.utils.repack_dictlist(reactor_config['reactor'])
|
||||
renderers = salt.loader.render(cls.opts, {})
|
||||
cls.render_pipe = [(renderers[x], '') for x in ('jinja', 'yaml')]
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.isdir(self.tempdir):
|
||||
shutil.rmtree(self.tempdir)
|
||||
del self.opts
|
||||
del self.tempdir
|
||||
del self.sls_name
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.opts
|
||||
del cls.reactor
|
||||
del cls.render_pipe
|
||||
|
||||
def test_basic(self):
|
||||
reactor_config = [
|
||||
{'salt/tagA': ['/srv/reactor/A.sls']},
|
||||
{'salt/tagB': ['/srv/reactor/B.sls']},
|
||||
{'*': ['/srv/reactor/all.sls']},
|
||||
]
|
||||
wrap = reactor.ReactWrap(self.opts)
|
||||
with patch.object(reactor.ReactWrap, 'local', MagicMock(side_effect=_args_sideffect)):
|
||||
ret = wrap.run({'fun': 'test.ping',
|
||||
'state': 'local',
|
||||
'order': 1,
|
||||
'name': 'foo_action',
|
||||
'__id__': 'foo_action'})
|
||||
raise Exception(ret)
|
||||
def test_list_reactors(self):
|
||||
'''
|
||||
Ensure that list_reactors() returns the correct list of reactor SLS
|
||||
files for each tag.
|
||||
'''
|
||||
for schema in ('old', 'new'):
|
||||
for rtype in REACTOR_DATA:
|
||||
tag = '_'.join((schema, rtype))
|
||||
self.assertEqual(
|
||||
self.reactor.list_reactors(tag),
|
||||
self.reaction_map[tag]
|
||||
)
|
||||
|
||||
def test_reactions(self):
|
||||
'''
|
||||
Ensure that the correct reactions are built from the configured SLS
|
||||
files and tag data.
|
||||
'''
|
||||
for schema in ('old', 'new'):
|
||||
for rtype in REACTOR_DATA:
|
||||
tag = '_'.join((schema, rtype))
|
||||
log.debug('test_reactions: processing %s', tag)
|
||||
reactors = self.reactor.list_reactors(tag)
|
||||
log.debug('test_reactions: %s reactors: %s', tag, reactors)
|
||||
# No globbing in our example SLS, and the files don't actually
|
||||
# exist, so mock glob.glob to just return back the path passed
|
||||
# to it.
|
||||
with patch.object(
|
||||
glob,
|
||||
'glob',
|
||||
MagicMock(side_effect=lambda x: [x])):
|
||||
# The below four mocks are all so that
|
||||
# salt.template.compile_template() will read the templates
|
||||
# we've mocked up in the SLS global variable above.
|
||||
with patch.object(
|
||||
os.path, 'isfile',
|
||||
MagicMock(return_value=True)):
|
||||
with patch.object(
|
||||
salt.utils, 'is_empty',
|
||||
MagicMock(return_value=False)):
|
||||
with patch.object(
|
||||
codecs, 'open',
|
||||
mock_open(read_data=SLS[reactors[0]])):
|
||||
with patch.object(
|
||||
salt.template, 'template_shebang',
|
||||
MagicMock(return_value=self.render_pipe)):
|
||||
reactions = self.reactor.reactions(
|
||||
tag,
|
||||
REACTOR_DATA[rtype],
|
||||
reactors,
|
||||
)
|
||||
log.debug(
|
||||
'test_reactions: %s reactions: %s',
|
||||
tag, reactions
|
||||
)
|
||||
self.assertEqual(reactions, LOW_CHUNKS[tag])
|
||||
|
||||
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
class TestReactWrap(TestCase, AdaptedConfigurationTestCaseMixin):
|
||||
'''
|
||||
Tests that we are formulating the wrapper calls properly
|
||||
'''
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.wrap = reactor.ReactWrap(cls.get_temp_config('master'))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.wrap
|
||||
|
||||
def test_runner(self):
|
||||
'''
|
||||
Test runner reactions using both the old and new config schema
|
||||
'''
|
||||
for schema in ('old', 'new'):
|
||||
tag = '_'.join((schema, 'runner'))
|
||||
chunk = LOW_CHUNKS[tag][0]
|
||||
thread_pool = Mock()
|
||||
thread_pool.fire_async = Mock()
|
||||
with patch.object(self.wrap, 'pool', thread_pool):
|
||||
self.wrap.run(chunk)
|
||||
thread_pool.fire_async.assert_called_with(
|
||||
self.wrap.client_cache['runner'].low,
|
||||
args=WRAPPER_CALLS[tag]
|
||||
)
|
||||
|
||||
def test_wheel(self):
|
||||
'''
|
||||
Test wheel reactions using both the old and new config schema
|
||||
'''
|
||||
for schema in ('old', 'new'):
|
||||
tag = '_'.join((schema, 'wheel'))
|
||||
chunk = LOW_CHUNKS[tag][0]
|
||||
thread_pool = Mock()
|
||||
thread_pool.fire_async = Mock()
|
||||
with patch.object(self.wrap, 'pool', thread_pool):
|
||||
self.wrap.run(chunk)
|
||||
thread_pool.fire_async.assert_called_with(
|
||||
self.wrap.client_cache['wheel'].low,
|
||||
args=WRAPPER_CALLS[tag]
|
||||
)
|
||||
|
||||
def test_local(self):
|
||||
'''
|
||||
Test local reactions using both the old and new config schema
|
||||
'''
|
||||
for schema in ('old', 'new'):
|
||||
tag = '_'.join((schema, 'local'))
|
||||
chunk = LOW_CHUNKS[tag][0]
|
||||
client_cache = {'local': Mock()}
|
||||
client_cache['local'].cmd_async = Mock()
|
||||
with patch.object(self.wrap, 'client_cache', client_cache):
|
||||
self.wrap.run(chunk)
|
||||
client_cache['local'].cmd_async.assert_called_with(
|
||||
*WRAPPER_CALLS[tag]['args'],
|
||||
**WRAPPER_CALLS[tag]['kwargs']
|
||||
)
|
||||
|
||||
def test_cmd(self):
|
||||
'''
|
||||
Test cmd reactions (alias for 'local') using both the old and new
|
||||
config schema
|
||||
'''
|
||||
for schema in ('old', 'new'):
|
||||
tag = '_'.join((schema, 'cmd'))
|
||||
chunk = LOW_CHUNKS[tag][0]
|
||||
client_cache = {'local': Mock()}
|
||||
client_cache['local'].cmd_async = Mock()
|
||||
with patch.object(self.wrap, 'client_cache', client_cache):
|
||||
self.wrap.run(chunk)
|
||||
client_cache['local'].cmd_async.assert_called_with(
|
||||
*WRAPPER_CALLS[tag]['args'],
|
||||
**WRAPPER_CALLS[tag]['kwargs']
|
||||
)
|
||||
|
||||
def test_caller(self):
|
||||
'''
|
||||
Test caller reactions using both the old and new config schema
|
||||
'''
|
||||
for schema in ('old', 'new'):
|
||||
tag = '_'.join((schema, 'caller'))
|
||||
chunk = LOW_CHUNKS[tag][0]
|
||||
client_cache = {'caller': Mock()}
|
||||
client_cache['caller'].cmd = Mock()
|
||||
with patch.object(self.wrap, 'client_cache', client_cache):
|
||||
self.wrap.run(chunk)
|
||||
client_cache['caller'].cmd.assert_called_with(
|
||||
*WRAPPER_CALLS[tag]['args'],
|
||||
**WRAPPER_CALLS[tag]['kwargs']
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue