Files
storlets/tests/unit/tools/extensions/test_ipython.py
Kota Tsuyuzaki d0ceeb55ec Fix apt fails due to linux image missing on AWS EC2
`uname -r` doesn't show the kernel number with 'genric' suffix (it will
be as 'aws' suffix) on AWS EC2 so that we may need a more general way to
get the correct package name.

Change-Id: Ieaa6891cb441124f13b5dc822f6511bc3091e188
Closes-Bug: #1686256
2017-05-18 22:08:52 -07:00

501 lines
19 KiB
Python

# Copyright (c) 2010-2016 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from IPython.core.error import UsageError
from cStringIO import StringIO
from storlets.tools.extensions.ipython import StorletMagics
import unittest
import mock
import os
import itertools
class FakeConnection(object):
def __init__(self, fake_status=200, fake_headers=None, fake_iter=None):
self._fake_status = fake_status
self._fake_headers = fake_headers or {}
self._fake_iter = fake_iter or iter([])
def _return_fake_response(self, **kwargs):
if 'response_dict' in kwargs:
kwargs['response_dict']['status'] = self._fake_status
kwargs['response_dict']['headers'] = self._fake_headers
kwargs['response_dict']['content_iter'] = self._fake_iter
if 'resp_chunk_size' in kwargs:
resp_body = self._fake_iter
else:
resp_body = ''.join([chunk for chunk in self._fake_iter])
return (self._fake_headers, resp_body)
# Those 3 methods are just for entry point difference from the caller
# but all methods returns same response format with updateing response_dict
def get_object(self, *args, **kwargs):
return self._return_fake_response(**kwargs)
def copy_object(self, *args, **kwargs):
return self._return_fake_response(**kwargs)
def put_object(self, *args, **kwargs):
return self._return_fake_response(**kwargs)
class MockShell(object):
def __init__(self):
self.user_ns = {}
def register(self, var_name, value):
self.user_ns[var_name] = value
class BaseTestIpythonExtension(object):
def setUp(self):
self.magics = StorletMagics()
# set auth info for keystone
self.os_original_env = os.environ.copy()
self._set_auth_environ()
self.magics.shell = MockShell()
def tearDown(self):
os.environ = self.os_original_env.copy()
def _set_auth_environ(self):
# helper method to set auth information for keystone v3 (default)
os.environ['OS_AUTH_VERSION'] = '3'
os.environ['OS_AUTH_URL'] = 'http://127.0.0.1/identity/v3'
os.environ['OS_USERNAME'] = 'tester'
os.environ['OS_PASSWORD'] = 'testing'
os.environ['OS_USER_DOMAIN_NAME'] = 'default'
os.environ['OS_PROJECT_DOMAIN_NAME'] = 'default'
os.environ['OS_PROJECT_NAME'] = 'test'
@mock.patch('storlets.tools.extensions.ipython.Connection')
def _call_cell(self, func, line, cell, fake_conn):
fake_conn.return_value = self.fake_connection
# cell magic
func(line, cell)
@mock.patch('storlets.tools.extensions.ipython.Connection')
def _call_line(self, func, line, fake_conn):
fake_conn.return_value = self.fake_connection
# line magic
func(line)
class TestStorletMagicStorletApp(BaseTestIpythonExtension, unittest.TestCase):
def setUp(self):
super(TestStorletMagicStorletApp, self).setUp()
self.fake_connection = FakeConnection()
def _call_storletapp(self, line, cell):
self._call_cell(self.magics.storletapp, line, cell)
def test_storlet_magics(self):
line = 'test.TestStorlet'
cell = ''
self._call_storletapp(line, cell)
def test_storlet_magics_usage_error(self):
# no line result in UsageError (from ipython default behavior)
line = ''
cell = ''
self.assertRaises(
UsageError, self._call_storletapp, line, cell)
def test_storlet_magics_with_invoke(self):
line = 'test.TestStorlet --with-invoke --input path:/foo/bar'
cell = ''
self._call_storletapp(line, cell)
def test_storlet_magics_with_invoke_no_input_fail(self):
line = 'test.TestStorlet --with-invoke'
cell = ''
with self.assertRaises(UsageError) as cm:
self._call_storletapp(line, cell)
self.assertEqual(
'--with-invoke option requires --input to run the app',
cm.exception.message)
def test_storlet_magics_invalid_input_fail(self):
invalid_input_patterns = (
'invalid', # no "path:" prefix
'path://', # no container object in the slash
'path:/container', # only container
'path:container', # only container w/o slash prefix
)
for invalid_input in invalid_input_patterns:
with self.assertRaises(UsageError) as cm:
self.magics._parse_input_path(invalid_input)
self.assertEqual(
'swift object path must have the format: '
'"path:/<container>/<object>"',
cm.exception.message)
def test_storlet_magics_stdout(self):
line = 'test.TestStorlet --with-invoke --input path:/foo/bar' \
'--print-result'
cell = ''
fake_stdout = StringIO()
with mock.patch('sys.stdout', fake_stdout):
self._call_storletapp(line, cell)
stdout_string = fake_stdout.getvalue()
expected_outputs = (
'Upload storlets succeeded',
'Example command `swift download <container> <object> '
'-H X-Run-Storlet:',
'Invocation Complete',
)
for expected in expected_outputs:
self.assertIn(expected, stdout_string)
def test_storlet_auth_v3_no_enough_auth_info(self):
line = 'test.TestStorlet'
cell = ''
# OS_AUTH_URL, OS_USERNAME, OS_PASSWORD, OS_PROJECT_NAME are required
required = ('OS_AUTH_URL', 'OS_USERNAME',
'OS_PASSWORD', 'OS_PROJECT_NAME')
for combi_length in range(1, len(required) + 1):
for combination in itertools.combinations(required, combi_length):
# set full environ for auth
self._set_auth_environ()
# and delete specific required keys
for key in combination:
del os.environ[key]
with self.assertRaises(UsageError) as e:
self._call_storletapp(line, cell)
self.assertEqual(
"You need to set OS_AUTH_URL, OS_USERNAME, OS_PASSWORD "
"and OS_PROJECT_NAME for Swift authentication",
e.exception.message)
def test_storlet_auth_v2_not_supported(self):
line = 'test.TestStorlet'
cell = ''
os.environ['OS_AUTH_VERSION'] = '2'
with self.assertRaises(NotImplementedError) as e:
self._call_storletapp(line, cell)
self.assertEqual(
'keystone v2 is not supported',
e.exception.message)
def test_storlet_auth_v1_no_enough_auth_info(self):
# NOTE: Now, storlets should work on keystone v3 so that this test
# may be deprecated in the future if we don't have to support pure
# swift upstream v1 auth (e.g. tempauth).
line = 'test.TestStorlet'
cell = ''
# ST_AUTH, ST_USER, ST_KEY are required
required = ('ST_AUTH', 'ST_USER', 'ST_KEY')
# v1 doesn't require OS_AUTH_VERSION
del os.environ['OS_AUTH_VERSION']
try:
del os.environ['OS_IDENTITY_API_VERSION']
except Exception:
pass
def _set_v1_auth():
os.environ['ST_AUTH'] = 'http://localhost/v1/auth'
os.environ['ST_USER'] = 'test:tester'
os.environ['ST_KEY'] = 'testing'
for combi_length in range(1, len(required) + 1):
for combination in itertools.combinations(required, combi_length):
# set full environ for auth
_set_v1_auth()
# and delete specific required keys
for key in combination:
del os.environ[key]
with self.assertRaises(UsageError) as e:
self._call_storletapp(line, cell)
self.assertEqual(
"You need to set ST_AUTH, ST_USER, ST_KEY "
"for Swift authentication",
e.exception.message)
class TestStorletMagicGet(BaseTestIpythonExtension, unittest.TestCase):
def setUp(self):
super(TestStorletMagicGet, self).setUp()
self.fake_connection = FakeConnection()
def _call_get(self, line):
self._call_line(self.magics.get, line)
def test_get_invalid_args(self):
scenarios = [
{
'line': '--input path:/c/o --storlet a.b',
'exception': UsageError,
'msg': '-o option is mandatory for the invocation'
}, {
'line': '--input path:/c/o -o a1234',
'exception': UsageError,
'msg': '--storlet option is mandatory for the invocation'
}, {
'line': '--storlet a.b -o a1234',
'exception': UsageError,
'msg': '--input option is mandatory for the invocation'
}, {
'line': '--input path/c/o --storlet a.b -o a1234',
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}, {
'line': '--input path:/c/ --storlet a.b -o a1234',
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}, {
'line': '--input path:/c --storlet a.b -o a1234',
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}, {
'line': '--input path:/c/o --storlet a.b -o 1234',
'exception': UsageError,
'msg': ('The output variable name must be a valid prefix '
'of a python variable, that is, start with a '
'letter')
}]
for scenario in scenarios:
with self.assertRaises(UsageError) as e:
self._call_get(scenario['line'])
self.assertEqual(scenario['msg'], e.exception.message)
def _test_get(self, line, outvar_name):
self._call_get(line)
self.assertTrue(outvar_name in self.magics.shell.user_ns)
resp = self.magics.shell.user_ns[outvar_name]
self.assertEqual({}, resp.headers)
self.assertEqual(200, resp.status)
self.assertEqual('', ''.join([chunk for chunk in iter(resp)]))
self.assertEqual('', resp.content)
def test_get(self):
outvar_name = 'a1234'
line = '--input path:/c/o --storlet a.b -o %s' % outvar_name
self._test_get(line, outvar_name)
def test_get_with_input(self):
params_name = 'params'
outvar_name = 'a1234'
line = '--input path:/c/o --storlet a.b -o %s -i %s' % (outvar_name,
params_name)
# register the variable to user_ns
self.magics.shell.register(params_name, {'a': 'b'})
self._test_get(line, outvar_name)
def test_get_with_input_error(self):
params_name = 'params'
outvar_name = 'a1234'
line = '--input path:/c/o --storlet a.b -o %s -i %s' % (outvar_name,
params_name)
with self.assertRaises(KeyError):
self._test_get(line, outvar_name)
class TestStorletMagicCopy(BaseTestIpythonExtension, unittest.TestCase):
def setUp(self):
super(TestStorletMagicCopy, self).setUp()
self.fake_connection = FakeConnection()
def _call_copy(self, line):
self._call_line(self.magics.copy, line)
def test_copy_invalid_args(self):
scenarios = [
{
'line': '--input path:/c/o --storlet a.b --output path:/c/o',
'exception': UsageError,
'msg': '-o option is mandatory for the invocation'
}, {
'line': ('--input path:/c/o --storlet a.b -o 1234 '
'--output path:/c/o'),
'exception': UsageError,
'msg': ('The output variable name must be a valid prefix '
'of a python variable, that is, start with a '
'letter')
}, {
'line': '--input path:/c/o -o a1234 --output path:/c/o',
'exception': UsageError,
'msg': '--storlet option is mandatory for the invocation'
}, {
'line': '--storlet a.b -o a1234 --output path:/c/o',
'exception': UsageError,
'msg': '--input option is mandatory for the invocation'
}, {
'line': ('--input path/c/o --storlet a.b -o a1234 '
'--output path:/c/o'),
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}, {
'line': ('--input path:/c/ --storlet a.b -o a1234 '
'--output path:/c/o'),
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}, {
'line': ('--input path:/c --storlet a.b -o a1234 '
'--output path:/c/o'),
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}, {
'line': '--input path:/c --storlet a.b -o a1234 ',
'exception': UsageError,
'msg': ('--output option is mandatory for the invocation')
}, {
'line': ('--input path:/c --storlet a.b -o a1234 '
'--output path/c/o'),
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}]
for scenario in scenarios:
with self.assertRaises(UsageError) as e:
self._call_copy(scenario['line'])
self.assertEqual(scenario['msg'], e.exception.message)
def _test_copy(self, line, outvar_name):
self._call_copy(line)
self.assertTrue(outvar_name in self.magics.shell.user_ns)
resp = self.magics.shell.user_ns[outvar_name]
self.assertEqual({}, resp.headers)
self.assertEqual(200, resp.status)
# sanity, no body
self.assertEqual('', resp.content)
def test_copy(self):
outvar_name = 'a1234'
line = ('--input path:/c/o --output path:/c/o '
'--storlet a.b -o %s' % outvar_name)
self._test_copy(line, outvar_name)
def test_copy_stdout_with_input(self):
params_name = 'params'
outvar_name = 'a1234'
line = ('--input path:/c/o --output path:/c/o '
'--storlet a.b -o %s -i %s' % (outvar_name, params_name))
self.magics.shell.register(params_name, {'a': 'b'})
self._test_copy(line, outvar_name)
def test_copy_stdout_with_input_error(self):
params_name = 'params'
outvar_name = 'a1234'
line = ('--input path:/c/o --output path:/c/o '
'--storlet a.b -o %s -i %s' % (outvar_name, params_name))
with self.assertRaises(KeyError):
self._test_copy(line, outvar_name)
class TestStorletMagicPut(BaseTestIpythonExtension, unittest.TestCase):
def setUp(self):
super(TestStorletMagicPut, self).setUp()
self.fake_connection = FakeConnection(201)
def _call_put(self, line):
self._call_line(self.magics.put, line)
def test_put_invalid_args(self):
scenarios = [
{
'line': '--input /c/o --storlet a.b --output path:/c/o',
'exception': UsageError,
'msg': '-o option is mandatory for the invocation'
}, {
'line': ('--input /c/o --storlet a.b -o 1234 '
'--output path:/c/o'),
'exception': UsageError,
'msg': ('The output variable name must be a valid prefix '
'of a python variable, that is, start with a '
'letter')
}, {
'line': '--input /c/o -o a1234 --output path:/c/o',
'exception': UsageError,
'msg': '--storlet option is mandatory for the invocation'
}, {
'line': '--storlet a.b -o a1234 --output path:/c/o',
'exception': UsageError,
'msg': '--input option is mandatory for the invocation'
}, {
'line': ('--input /c/o --storlet a.b -o a1234 '
'--output path/c/o'),
'exception': UsageError,
'msg': ('swift object path must have the format: '
'"path:/<container>/<object>"')
}, {
'line': ('--input path:c/ --storlet a.b -o a1234 '
'--output path:/c/o'),
'exception': UsageError,
'msg': ('--input argument must be a full path')
}]
for scenario in scenarios:
with self.assertRaises(UsageError) as e:
self._call_put(scenario['line'])
self.assertEqual(scenario['msg'], e.exception.message)
def _test_put(self, line, outvar_name):
open_name = '%s.open' % 'storlets.tools.extensions.ipython'
with mock.patch(open_name, create=True) as mock_open:
mock_open.return_value = mock.MagicMock(spec=file)
self._call_put(line)
self.assertTrue(outvar_name in self.magics.shell.user_ns)
resp = self.magics.shell.user_ns[outvar_name]
self.assertEqual({}, resp.headers)
self.assertEqual(201, resp.status)
# sanity, no body
self.assertEqual('', resp.content)
def test_put(self):
outvar_name = 'a1234'
line = ('--input /c/o --storlet a.b '
'--output path:a/b -o %s' % outvar_name)
self._test_put(line, outvar_name)
def test_put_stdout_with_input(self):
params_name = 'params'
outvar_name = 'a1234'
line = ('--input /c/o --storlet a.b -o %s -i %s '
'--output path:a/b' % (outvar_name, params_name))
self.magics.shell.register(params_name, {'a': 'b'})
self._test_put(line, outvar_name)
def test_put_stdout_with_input_error(self):
params_name = 'params'
outvar_name = 'a1234'
line = ('--input /c/o --storlet a.b -o %s -i %s '
'--output path:a/b' % (outvar_name, params_name))
with self.assertRaises(KeyError):
self._test_put(line, outvar_name)
if __name__ == '__main__':
unittest.main()