Python 3 Fixes (Pt. 2) (#39397)

* Typo in comment

* First convert to string if not already a string. Then to bytes under Py3.

The reason being that jids from the CLI, at least the one fed in
integration.runners.jobs.ManageTest.test_loopup_jid is loaded as an
integer, and, while the Py2 code converts JIDs to strings, under Py3, we
assume JID's are already strings.

* Mark tests which require root permissions to run

* Allow declaring that the function IS a class method.

```
Python 3.5.3 (default, Jan 21 2017, 00:29:12)
[GCC 6.3.1 20170109] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Foo:
...     def bar(self):
...         print('bar')
...
>>> import inspect
>>> inspect.ismethod(Foo.bar)
False
>>> inspect.ismethod(Foo().bar)
True
```

On Python 2, `inspect.ismethod` returns `True` for bound and unbound
methods while on Python 3 it only returns `True` for bound methods.
The explicit `is_class_method` is to avoid instantiating the class just
to get the function signature.

* Always decode responses to the Python version native string implementation

* Just compare the objects as matching list.

Asserting same item count doesn't make that much sense.

* Py3 compatibility

* Fix saltnado tests under Py3

* Python 3 compatibility

* Show me the full traceback

* Revert "Convert fileserver data from bytes to strings"

This reverts commit e53972f8c6.

* Revert "Under Py3, we get `bytes` when using the roots backend directly"

This reverts commit 9f73b240c1.

* Convert from bytes to str if not a binary file

* Py3 compatibility fixes.

Convert file contents from bytes to string if not a binary file
This commit is contained in:
Pedro Algarvio 2017-02-14 23:20:56 +00:00 committed by Nicole Thomas
parent b5d56890ce
commit fc59d5e832
16 changed files with 135 additions and 119 deletions

View file

@ -350,7 +350,7 @@ class SyncClientMixin(object):
for global_key, value in six.iteritems(func_globals):
self.functions[mod_name].__globals__[global_key] = value
# There are some descrepencies of what a "low" structure is in the
# There are some discrepancies of what a "low" structure is in the
# publisher world it is a dict including stuff such as jid, fun,
# arg (a list of args, with kwargs packed in). Historically this
# particular one has had no "arg" and just has had all the kwargs

View file

@ -594,9 +594,7 @@ class Fileserver(object):
return ret
fstr = '{0}.serve_file'.format(fnd['back'])
if fstr in self.servers:
ret = self.servers[fstr](load, fnd)
if six.PY3 and ret.get('data'):
ret['data'] = ret['data'].decode(__salt_system_encoding__)
return self.servers[fstr](load, fnd)
return ret
def __file_hash_and_stat(self, load):

View file

@ -129,9 +129,12 @@ def serve_file(load, fnd):
return ret
ret['dest'] = fnd['rel']
gzip = load.get('gzip', None)
with salt.utils.fopen(os.path.normpath(fnd['path']), 'rb') as fp_:
fpath = os.path.normpath(fnd['path'])
with salt.utils.fopen(fpath, 'rb') as fp_:
fp_.seek(load['loc'])
data = fp_.read(__opts__['file_buffer_size'])
if data and six.PY3 and not salt.utils.is_bin_file(fpath):
data = data.decode(__salt_system_encoding__)
if gzip and data:
data = salt.utils.gzip_util.compress(data, gzip)
ret['gzip'] = gzip

View file

@ -199,6 +199,7 @@ from collections import defaultdict
# pylint: disable=import-error
import cgi
import yaml
import tornado.escape
import tornado.httpserver
import tornado.ioloop
import tornado.web
@ -523,7 +524,7 @@ class BaseSaltAPIHandler(tornado.web.RequestHandler, SaltClientsMixIn): # pylin
# Use cgi.parse_header to correctly separate parameters from value
header = cgi.parse_header(self.request.headers['Content-Type'])
value, parameters = header
return ct_in_map[value](data)
return ct_in_map[value](tornado.escape.native_str(data))
except KeyError:
self.send_error(406)
except ValueError:
@ -1059,7 +1060,7 @@ class SaltAPIHandler(BaseSaltAPIHandler, SaltClientsMixIn): # pylint: disable=W
# salt.utils.format_call doesn't work for functions having the annotation tornado.gen.coroutine
def _format_call_run_job_async(self, chunk):
f_call = salt.utils.format_call(salt.client.LocalClient.run_job, chunk)
f_call = salt.utils.format_call(salt.client.LocalClient.run_job, chunk, is_class_method=True)
f_call.get('kwargs', {})['io_loop'] = tornado.ioloop.IOLoop.current()
return f_call

View file

@ -257,12 +257,14 @@ def _decrypt_ciphertext(cipher, translate_newlines=False):
the cipher and return the decrypted string. If the cipher cannot be
decrypted, log the error, and return the ciphertext back out.
'''
if translate_newlines:
cipher = cipher.replace(r'\n', '\n')
if six.PY3:
cipher = cipher.encode(__salt_system_encoding__)
cmd = [_get_gpg_exec(), '--homedir', _get_key_dir(), '--status-fd', '2',
'--no-tty', '-d']
proc = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=False)
decrypted_data, decrypt_error = proc.communicate(
input=cipher.replace(r'\n', '\n') if translate_newlines else cipher
)
decrypted_data, decrypt_error = proc.communicate(input=cipher)
if not decrypted_data:
log.warning(
'Could not decrypt cipher %s, received: %s',
@ -271,6 +273,8 @@ def _decrypt_ciphertext(cipher, translate_newlines=False):
)
return cipher
else:
if six.PY3:
decrypted_data = decrypted_data.decode(__salt_system_encoding__)
return str(decrypted_data)

View file

@ -1016,7 +1016,8 @@ def build_whitespace_split_regex(text):
def format_call(fun,
data,
initial_ret=None,
expected_extra_kws=()):
expected_extra_kws=(),
is_class_method=None):
'''
Build the required arguments and keyword arguments required for the passed
function.
@ -1028,6 +1029,13 @@ def format_call(fun,
None
:param expected_extra_kws: Any expected extra keyword argument names which
should not trigger a :ref:`SaltInvocationError`
:param is_class_method: Pass True if you are sure that the function being passed
is a class method. The reason for this is that on Python 3
``inspect.ismethod`` only returns ``True`` for bound methods,
while on Python 2, it returns ``True`` for bound and unbound
methods. So, on Python 3, in case of a class method, you'd
need the class to which the function belongs to be instantiated
and this is not always wanted.
:returns: A dictionary with the function required arguments and keyword
arguments.
'''
@ -1036,7 +1044,7 @@ def format_call(fun,
ret['args'] = []
ret['kwargs'] = {}
aspec = salt.utils.args.get_function_argspec(fun)
aspec = salt.utils.args.get_function_argspec(fun, is_class_method=is_class_method)
arg_data = arg_lookup(fun, aspec)
args = arg_data['args']

View file

@ -170,15 +170,25 @@ if six.PY3:
return _ArgSpec(args, varargs, varkw, defaults)
def get_function_argspec(func):
def get_function_argspec(func, is_class_method=None):
'''
A small wrapper around getargspec that also supports callable classes
:param is_class_method: Pass True if you are sure that the function being passed
is a class method. The reason for this is that on Python 3
``inspect.ismethod`` only returns ``True`` for bound methods,
while on Python 2, it returns ``True`` for bound and unbound
methods. So, on Python 3, in case of a class method, you'd
need the class to which the function belongs to be instantiated
and this is not always wanted.
'''
if not callable(func):
raise TypeError('{0} is not a callable'.format(func))
if six.PY2:
if inspect.isfunction(func):
if is_class_method is True:
aspec = inspect.getargspec(func)
del aspec.args[0] # self
elif inspect.isfunction(func):
aspec = inspect.getargspec(func)
elif inspect.ismethod(func):
aspec = inspect.getargspec(func)
@ -191,7 +201,10 @@ def get_function_argspec(func):
'Cannot inspect argument list for \'{0}\''.format(func)
)
else:
if inspect.isfunction(func):
if is_class_method is True:
aspec = _getargspec(func)
del aspec.args[0] # self
elif inspect.isfunction(func):
aspec = _getargspec(func) # pylint: disable=redefined-variable-type
elif inspect.ismethod(func):
aspec = _getargspec(func)

View file

@ -454,6 +454,8 @@ class GitProvider(object):
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
output = cmd.communicate()[0]
if six.PY3:
output = output.decode(__salt_system_encoding__)
if cmd.returncode != 0:
log.warning(
'Failed to prune stale branches for %s remote \'%s\'. '
@ -601,7 +603,7 @@ class GitProvider(object):
os.O_CREAT | os.O_EXCL | os.O_WRONLY)
with os.fdopen(fh_, 'w'):
# Write the lock file and close the filehandle
os.write(fh_, str(os.getpid()))
os.write(fh_, six.b(str(os.getpid())))
except (OSError, IOError) as exc:
if exc.errno == errno.EEXIST:
with salt.utils.fopen(self._get_lock_file(lock_type), 'r') as fd_:
@ -2078,7 +2080,8 @@ class GitBase(object):
os.makedirs(env_cachedir)
new_envs = self.envs(ignore_cache=True)
serial = salt.payload.Serial(self.opts)
with salt.utils.fopen(self.env_cache, 'w+') as fp_:
mode = 'wb+' if six.PY3 else 'w+'
with salt.utils.fopen(self.env_cache, mode) as fp_:
fp_.write(serial.dumps(new_envs))
log.trace('Wrote env cache data to {0}'.format(self.env_cache))
@ -2480,9 +2483,12 @@ class GitFS(GitBase):
return ret
ret['dest'] = fnd['rel']
gzip = load.get('gzip', None)
with salt.utils.fopen(fnd['path'], 'rb') as fp_:
fpath = os.path.normpath(fnd['path'])
with salt.utils.fopen(fpath, 'rb') as fp_:
fp_.seek(load['loc'])
data = fp_.read(self.opts['file_buffer_size'])
if data and six.PY3 and not salt.utils.is_bin_file(fpath):
data = data.decode(__salt_system_encoding__)
if gzip and data:
data = salt.utils.gzip_util.compress(data, gzip)
ret['gzip'] = gzip

View file

@ -101,10 +101,11 @@ def jid_dir(jid, job_dir=None, hash_type='sha256'):
'''
Return the jid_dir for the given job id
'''
if not isinstance(jid, six.string_types):
jid = str(jid)
if six.PY3:
jhash = getattr(hashlib, hash_type)(jid.encode('utf-8')).hexdigest()
else:
jhash = getattr(hashlib, hash_type)(str(jid)).hexdigest()
jid = jid.encode('utf-8')
jhash = getattr(hashlib, hash_type)(jid).hexdigest()
parts = []
if job_dir is not None:

View file

@ -139,7 +139,10 @@ class VirtualboxProviderTest(VirtualboxCloudTestCase):
names = machines.keys()
self.assertGreaterEqual(len(names), 1, "No machines found")
for name, machine in six.iteritems(machines):
self.assertItemsEqual(expected_attributes, machine.keys())
if six.PY3:
self.assertCountEqual(expected_attributes, machine.keys())
else:
self.assertItemsEqual(expected_attributes, machine.keys())
self.assertIn(BASE_BOX_NAME, names)
@ -168,7 +171,10 @@ class VirtualboxProviderTest(VirtualboxCloudTestCase):
names = machines.keys()
self.assertGreaterEqual(len(names), 1, "No machines found")
for name, machine in six.iteritems(machines):
self.assertItemsEqual(expected_attributes, machine.keys())
if six.PY3:
self.assertCountEqual(expected_attributes, machine.keys())
else:
self.assertItemsEqual(expected_attributes, machine.keys())
self.assertIn(BASE_BOX_NAME, names)

View file

@ -18,9 +18,6 @@ import integration
from salt.fileserver import roots
from salt import fileclient
# Import third party libs
import salt.ext.six as six
roots.__opts__ = {}
@ -71,19 +68,19 @@ class RootsTest(integration.ModuleCase):
ret = roots.serve_file(load, fnd)
self.assertDictEqual(
ret,
{'data': six.b('Scene 24\n\n \n OLD MAN: Ah, hee he he ha!\n '
'ARTHUR: And this enchanter of whom you speak, he '
'has seen the grail?\n OLD MAN: Ha ha he he he '
'he!\n ARTHUR: Where does he live? Old man, where '
'does he live?\n OLD MAN: He knows of a cave, a '
'cave which no man has entered.\n ARTHUR: And the '
'Grail... The Grail is there?\n OLD MAN: Very much '
'danger, for beyond the cave lies the Gorge\n '
'of Eternal Peril, which no man has ever crossed.\n '
'ARTHUR: But the Grail! Where is the Grail!?\n '
'OLD MAN: Seek you the Bridge of Death.\n ARTHUR: '
'The Bridge of Death, which leads to the Grail?\n '
'OLD MAN: Hee hee ha ha!\n\n'),
{'data': 'Scene 24\n\n \n OLD MAN: Ah, hee he he ha!\n '
'ARTHUR: And this enchanter of whom you speak, he '
'has seen the grail?\n OLD MAN: Ha ha he he he '
'he!\n ARTHUR: Where does he live? Old man, where '
'does he live?\n OLD MAN: He knows of a cave, a '
'cave which no man has entered.\n ARTHUR: And the '
'Grail... The Grail is there?\n OLD MAN: Very much '
'danger, for beyond the cave lies the Gorge\n '
'of Eternal Peril, which no man has ever crossed.\n '
'ARTHUR: But the Grail! Where is the Grail!?\n '
'OLD MAN: Seek you the Bridge of Death.\n ARTHUR: '
'The Bridge of Death, which leads to the Grail?\n '
'OLD MAN: Hee hee ha ha!\n\n',
'dest': 'testfile'})
@skipIf(True, "Update test not yet implemented")

View file

@ -23,12 +23,15 @@ from subprocess import Popen, PIPE, STDOUT
log = logging.getLogger(__name__)
# Import 3rd-party libs
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('..')
import salt.ext.six as six
# Import salt libs
import integration
import salt.utils
from salt import pillar
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('..')
GPG_HOMEDIR = os.path.join(integration.TMP_CONF_DIR, 'gpgkeys')
@ -224,7 +227,7 @@ class DecryptGPGPillarTest(integration.ModuleCase):
stdin=PIPE,
stdout=PIPE,
stderr=STDOUT,
shell=False).communicate(input=TEST_KEY)[0]
shell=False).communicate(input=six.b(TEST_KEY))[0]
log.debug('Result:\n%s', output)
os.makedirs(PILLAR_BASE)

View file

@ -17,7 +17,6 @@ from salttesting.mock import patch, MagicMock
ensure_in_syspath('../../')
# Import salt libs
import salt.ext.six as six
import integration
import salt.utils
from salt.modules import file as filemod
@ -167,10 +166,7 @@ class FileModuleTest(integration.ModuleCase):
def test_source_list_for_single_file_returns_unchanged(self):
ret = self.run_function('file.source_list', ['salt://http/httpd.conf',
'filehash', 'base'])
if six.PY3:
self.assertCountEqual(ret, ['salt://http/httpd.conf', 'filehash'])
else:
self.assertItemsEqual(ret, ['salt://http/httpd.conf', 'filehash'])
self.assertEqual(list(ret), ['salt://http/httpd.conf', 'filehash'])
def test_source_list_for_list_returns_existing_file(self):
filemod.__salt__ = {
@ -183,12 +179,7 @@ class FileModuleTest(integration.ModuleCase):
ret = filemod.source_list(['salt://http/httpd.conf',
'salt://http/httpd.conf.fallback'],
'filehash', 'base')
if six.PY3:
self.assertCountEqual(ret, ['salt://http/httpd.conf.fallback',
'filehash'])
else:
self.assertItemsEqual(ret, ['salt://http/httpd.conf.fallback',
'filehash'])
self.assertEqual(list(ret), ['salt://http/httpd.conf.fallback', 'filehash'])
def test_source_list_for_list_returns_file_from_other_env(self):
def list_master(env):
@ -203,12 +194,7 @@ class FileModuleTest(integration.ModuleCase):
ret = filemod.source_list(['salt://http/httpd.conf?saltenv=dev',
'salt://http/httpd.conf.fallback'],
'filehash', 'base')
if six.PY3:
self.assertCountEqual(ret, ['salt://http/httpd.conf?saltenv=dev',
'filehash'])
else:
self.assertItemsEqual(ret, ['salt://http/httpd.conf?saltenv=dev',
'filehash'])
self.assertEqual(list(ret), ['salt://http/httpd.conf?saltenv=dev', 'filehash'])
def test_source_list_for_list_returns_file_from_dict(self):
filemod.__salt__ = {
@ -219,10 +205,7 @@ class FileModuleTest(integration.ModuleCase):
ret = filemod.source_list(
[{'salt://http/httpd.conf': ''}], 'filehash', 'base')
if six.PY3:
self.assertCountEqual(ret, ['salt://http/httpd.conf', 'filehash'])
else:
self.assertItemsEqual(ret, ['salt://http/httpd.conf', 'filehash'])
self.assertEqual(list(ret), ['salt://http/httpd.conf', 'filehash'])
@patch('salt.modules.file.os.remove')
def test_source_list_for_list_returns_file_from_dict_via_http(self, remove):
@ -236,62 +219,39 @@ class FileModuleTest(integration.ModuleCase):
ret = filemod.source_list(
[{'http://t.est.com/http/httpd.conf': 'filehash'}], '', 'base')
if six.PY3:
self.assertCountEqual(ret, ['http://t.est.com/http/httpd.conf',
'filehash'])
else:
self.assertItemsEqual(ret, ['http://t.est.com/http/httpd.conf',
'filehash'])
self.assertEqual(list(ret), ['http://t.est.com/http/httpd.conf', 'filehash'])
def test_source_list_for_single_local_file_slash_returns_unchanged(self):
ret = self.run_function('file.source_list', [self.myfile,
'filehash', 'base'])
if six.PY3:
self.assertCountEqual(ret, [self.myfile, 'filehash'])
else:
self.assertItemsEqual(ret, [self.myfile, 'filehash'])
self.assertEqual(list(ret), [self.myfile, 'filehash'])
def test_source_list_for_single_local_file_proto_returns_unchanged(self):
ret = self.run_function('file.source_list', ['file://' + self.myfile,
'filehash', 'base'])
if six.PY3:
self.assertCountEqual(ret, ['file://' + self.myfile, 'filehash'])
else:
self.assertItemsEqual(ret, ['file://' + self.myfile, 'filehash'])
self.assertEqual(list(ret), ['file://' + self.myfile, 'filehash'])
def test_source_list_for_list_returns_existing_local_file_slash(self):
ret = filemod.source_list([self.myfile + '-foo',
self.myfile],
'filehash', 'base')
if six.PY3:
self.assertCountEqual(ret, [self.myfile, 'filehash'])
else:
self.assertItemsEqual(ret, [self.myfile, 'filehash'])
self.assertEqual(list(ret), [self.myfile, 'filehash'])
def test_source_list_for_list_returns_existing_local_file_proto(self):
ret = filemod.source_list(['file://' + self.myfile + '-foo',
'file://' + self.myfile],
'filehash', 'base')
if six.PY3:
self.assertCountEqual(ret, ['file://' + self.myfile, 'filehash'])
else:
self.assertItemsEqual(ret, ['file://' + self.myfile, 'filehash'])
self.assertEqual(list(ret), ['file://' + self.myfile, 'filehash'])
def test_source_list_for_list_returns_local_file_slash_from_dict(self):
ret = filemod.source_list(
[{self.myfile: ''}], 'filehash', 'base')
if six.PY3:
self.assertCountEqual(ret, [self.myfile, 'filehash'])
else:
self.assertItemsEqual(ret, [self.myfile, 'filehash'])
self.assertEqual(list(ret), [self.myfile, 'filehash'])
def test_source_list_for_list_returns_local_file_proto_from_dict(self):
ret = filemod.source_list(
[{'file://' + self.myfile: ''}], 'filehash', 'base')
if six.PY3:
self.assertCountEqual(ret, ['file://' + self.myfile, 'filehash'])
else:
self.assertItemsEqual(ret, ['file://' + self.myfile, 'filehash'])
self.assertEqual(list(ret), ['file://' + self.myfile, 'filehash'])
if __name__ == '__main__':
from integration import run_tests

View file

@ -27,6 +27,12 @@ except ImportError:
HAS_ZMQ_IOLOOP = False
def json_loads(data):
if six.PY3:
data = data.decode('utf-8')
return json.loads(data)
@skipIf(HAS_ZMQ_IOLOOP is False, 'PyZMQ version must be >= 14.0.1 to run these tests.')
@skipIf(StrictVersion(zmq.__version__) < StrictVersion('14.0.1'), 'PyZMQ must be >= 14.0.1 to run these tests.')
class TestSaltAPIHandler(SaltnadoTestCase):
@ -47,13 +53,9 @@ class TestSaltAPIHandler(SaltnadoTestCase):
request_timeout=30,
)
self.assertEqual(response.code, 200)
response_obj = json.loads(response.body)
self.assertItemsEqual(response_obj['clients'],
['runner',
'runner_async',
'local_async',
'local']
)
response_obj = json_loads(response.body)
self.assertEqual(sorted(response_obj['clients']),
['local', 'local_async', 'runner', 'runner_async'])
self.assertEqual(response_obj['return'], 'Welcome')
def test_post_no_auth(self):
@ -94,7 +96,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
connect_timeout=30,
request_timeout=30,
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(response_obj['return'], [{'minion': True, 'sub_minion': True}])
def test_simple_local_post_no_tgt(self):
@ -113,7 +115,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
connect_timeout=30,
request_timeout=30,
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(response_obj['return'], ["No minions matched the target. No command was sent, no jid was assigned."])
# local client request body test
@ -133,7 +135,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
connect_timeout=30,
request_timeout=30,
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(response_obj['return'], [{'minion': True, 'sub_minion': True}])
def test_simple_local_post_invalid_request(self):
@ -164,7 +166,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
@ -189,7 +191,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
ret[1]['minions'] = sorted(ret[1]['minions'])
@ -223,7 +225,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
ret[1]['minions'] = sorted(ret[1]['minions'])
@ -246,7 +248,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
headers={'Content-Type': self.content_type_map['json'],
saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(response_obj['return'], [{}])
# runner tests
@ -262,7 +264,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
connect_timeout=30,
request_timeout=30,
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(len(response_obj['return']), 1)
self.assertEqual(set(response_obj['return'][0]), set(['minion', 'sub_minion']))
@ -279,7 +281,7 @@ class TestSaltAPIHandler(SaltnadoTestCase):
connect_timeout=10,
request_timeout=10,
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertIn('return', response_obj)
self.assertEqual(1, len(response_obj['return']))
self.assertIn('jid', response_obj['return'][0])
@ -303,7 +305,7 @@ class TestMinionSaltAPIHandler(SaltnadoTestCase):
headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
follow_redirects=False,
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(len(response_obj['return']), 1)
# one per minion
self.assertEqual(len(response_obj['return'][0]), 2)
@ -318,7 +320,7 @@ class TestMinionSaltAPIHandler(SaltnadoTestCase):
headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
follow_redirects=False,
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(len(response_obj['return']), 1)
self.assertEqual(len(response_obj['return'][0]), 1)
# check a single grain
@ -335,7 +337,7 @@ class TestMinionSaltAPIHandler(SaltnadoTestCase):
saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
@ -357,7 +359,7 @@ class TestMinionSaltAPIHandler(SaltnadoTestCase):
saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
ret = response_obj['return']
ret[0]['minions'] = sorted(ret[0]['minions'])
@ -405,7 +407,7 @@ class TestJobsSaltAPIHandler(SaltnadoTestCase):
follow_redirects=False,
)
response = self.wait(timeout=30)
response_obj = json.loads(response.body)['return'][0]
response_obj = json_loads(response.body)['return'][0]
try:
for jid, ret in six.iteritems(response_obj):
self.assertIn('Function', ret)
@ -415,7 +417,7 @@ class TestJobsSaltAPIHandler(SaltnadoTestCase):
self.assertIn('StartTime', ret)
self.assertIn('Arguments', ret)
except AttributeError as attribute_error:
print(json.loads(response.body))
print(json_loads(response.body))
raise
# test with a specific JID passed in
@ -427,7 +429,7 @@ class TestJobsSaltAPIHandler(SaltnadoTestCase):
follow_redirects=False,
)
response = self.wait(timeout=30)
response_obj = json.loads(response.body)['return'][0]
response_obj = json_loads(response.body)['return'][0]
self.assertIn('Function', response_obj)
self.assertIn('Target', response_obj)
self.assertIn('Target-type', response_obj)
@ -460,7 +462,7 @@ class TestRunSaltAPIHandler(SaltnadoTestCase):
headers={'Content-Type': self.content_type_map['json'],
saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertEqual(response_obj['return'], [{'minion': True, 'sub_minion': True}])
@ -488,6 +490,8 @@ class TestEventsSaltAPIHandler(SaltnadoTestCase):
self.stop()
def on_event(self, event):
if six.PY3:
event = event.decode('utf-8')
if self.events_to_fire > 0:
self.application.event_listener.event.fire_event({
'foo': 'bar',
@ -544,7 +548,7 @@ class TestWebhookSaltAPIHandler(SaltnadoTestCase):
body='foo=bar',
headers={saltnado.AUTH_TOKEN_HEADER: self.token['token']},
)
response_obj = json.loads(response.body)
response_obj = json_loads(response.body)
self.assertTrue(response_obj['success'])

View file

@ -105,7 +105,16 @@ class OutputReturnTest(integration.ShellCase):
except Exception:
# display trace in error message for debugging on jenkins
trace = traceback.format_exc()
self.assertEqual(trace, '')
sentinel = object()
old_max_diff = getattr(self, 'maxDiff', sentinel)
try:
self.maxDiff = None
self.assertEqual(trace, '')
finally:
if old_max_diff is sentinel:
delattr(self, 'maxDiff')
else:
self.maxDiff = old_max_diff
if __name__ == '__main__':

View file

@ -11,6 +11,7 @@ import yaml
import shutil
# Import Salt Testing libs
from salttesting import skipIf
from salttesting.helpers import ensure_in_syspath
ensure_in_syspath('../../')
@ -175,6 +176,7 @@ class RunTest(integration.ShellCase, testprogram.TestProgramCase, integration.Sh
stdout=stdout, stderr=stderr
)
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
def test_salt_run_with_eauth_all_args(self):
'''
test salt-run with eauth
@ -189,6 +191,7 @@ class RunTest(integration.ShellCase, testprogram.TestProgramCase, integration.Sh
self.assertEqual(expect, run_cmd)
self._remove_user()
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
def test_salt_run_with_eauth_bad_passwd(self):
'''
test salt-run with eauth and bad password