py3: symlink follow-up

- Have the unit tests use WSGI strings, like a real system.
- Port the func tests.

Change-Id: I3a6f409208de45ebf9f55f7f59e4fe6ac6fbe163
This commit is contained in:
Tim Burke 2019-05-30 11:55:58 -07:00
parent 4f9595f113
commit 2e35376c6d
8 changed files with 192 additions and 156 deletions

View File

@ -199,12 +199,7 @@ def _check_symlink_header(req):
# validation first, here. # validation first, here.
error_body = 'X-Symlink-Target header must be of the form ' \ error_body = 'X-Symlink-Target header must be of the form ' \
'<container name>/<object name>' '<container name>/<object name>'
try: if wsgi_unquote(req.headers[TGT_OBJ_SYMLINK_HDR]).startswith('/'):
if wsgi_unquote(req.headers[TGT_OBJ_SYMLINK_HDR]).startswith('/'):
raise HTTPPreconditionFailed(
body=error_body,
request=req, content_type='text/plain')
except TypeError:
raise HTTPPreconditionFailed( raise HTTPPreconditionFailed(
body=error_body, body=error_body,
request=req, content_type='text/plain') request=req, content_type='text/plain')
@ -216,14 +211,9 @@ def _check_symlink_header(req):
req.headers[TGT_OBJ_SYMLINK_HDR] = wsgi_quote('%s/%s' % (container, obj)) req.headers[TGT_OBJ_SYMLINK_HDR] = wsgi_quote('%s/%s' % (container, obj))
# Check account format if it exists # Check account format if it exists
try: account = check_account_format(
account = check_account_format( req, wsgi_unquote(req.headers[TGT_ACCT_SYMLINK_HDR])) \
req, wsgi_unquote(req.headers[TGT_ACCT_SYMLINK_HDR])) \ if TGT_ACCT_SYMLINK_HDR in req.headers else None
if TGT_ACCT_SYMLINK_HDR in req.headers else None
except TypeError:
raise HTTPPreconditionFailed(
body='Account name cannot contain slashes',
request=req, content_type='text/plain')
# Extract request path # Extract request path
_junk, req_acc, req_cont, req_obj = req.split_path(4, 4, True) _junk, req_acc, req_cont, req_obj = req.split_path(4, 4, True)

View File

@ -40,42 +40,40 @@ class TestDloEnv(BaseEnv):
if not cont.create(): if not cont.create():
raise ResponseError(cls.conn.response) raise ResponseError(cls.conn.response)
# avoid getting a prefix that stops halfway through an encoded prefix = Utils.create_name(10)
# character
prefix = Utils.create_name().decode("utf-8")[:10].encode("utf-8")
cls.segment_prefix = prefix cls.segment_prefix = prefix
for letter in ('a', 'b', 'c', 'd', 'e'): for letter in ('a', 'b', 'c', 'd', 'e'):
file_item = cls.container.file("%s/seg_lower%s" % (prefix, letter)) file_item = cls.container.file("%s/seg_lower%s" % (prefix, letter))
file_item.write(letter * 10) file_item.write(letter.encode('ascii') * 10)
file_item = cls.container.file( file_item = cls.container.file(
"%s/seg_upper_%%ff%s" % (prefix, letter)) "%s/seg_upper_%%ff%s" % (prefix, letter))
file_item.write(letter.upper() * 10) file_item.write(letter.upper().encode('ascii') * 10)
for letter in ('f', 'g', 'h', 'i', 'j'): for letter in ('f', 'g', 'h', 'i', 'j'):
file_item = cls.container2.file("%s/seg_lower%s" % file_item = cls.container2.file("%s/seg_lower%s" %
(prefix, letter)) (prefix, letter))
file_item.write(letter * 10) file_item.write(letter.encode('ascii') * 10)
man1 = cls.container.file("man1") man1 = cls.container.file("man1")
man1.write('man1-contents', man1.write(b'man1-contents',
hdrs={"X-Object-Manifest": "%s/%s/seg_lower" % hdrs={"X-Object-Manifest": "%s/%s/seg_lower" %
(cls.container.name, prefix)}) (cls.container.name, prefix)})
man2 = cls.container.file("man2") man2 = cls.container.file("man2")
man2.write('man2-contents', man2.write(b'man2-contents',
hdrs={"X-Object-Manifest": "%s/%s/seg_upper_%%25ff" % hdrs={"X-Object-Manifest": "%s/%s/seg_upper_%%25ff" %
(cls.container.name, prefix)}) (cls.container.name, prefix)})
manall = cls.container.file("manall") manall = cls.container.file("manall")
manall.write('manall-contents', manall.write(b'manall-contents',
hdrs={"X-Object-Manifest": "%s/%s/seg" % hdrs={"X-Object-Manifest": "%s/%s/seg" %
(cls.container.name, prefix)}) (cls.container.name, prefix)})
mancont2 = cls.container.file("mancont2") mancont2 = cls.container.file("mancont2")
mancont2.write( mancont2.write(
'mancont2-contents', b'mancont2-contents',
hdrs={"X-Object-Manifest": "%s/%s/seg_lower" % hdrs={"X-Object-Manifest": "%s/%s/seg_lower" %
(cls.container2.name, prefix)}) (cls.container2.name, prefix)})

View File

@ -48,7 +48,7 @@ class TestSloEnv(BaseEnv):
('e', 1)): ('e', 1)):
seg_name = "seg_%s" % letter seg_name = "seg_%s" % letter
file_item = container.file(seg_name) file_item = container.file(seg_name)
file_item.write(letter * size) file_item.write(letter.encode('ascii') * size)
seg_info[seg_name] = { seg_info[seg_name] = {
'size_bytes': size, 'size_bytes': size,
'etag': file_item.md5, 'etag': file_item.md5,
@ -93,24 +93,26 @@ class TestSloEnv(BaseEnv):
file_item.write( file_item.write(
json.dumps([seg_info['seg_a'], seg_info['seg_b'], json.dumps([seg_info['seg_a'], seg_info['seg_b'],
seg_info['seg_c'], seg_info['seg_d'], seg_info['seg_c'], seg_info['seg_d'],
seg_info['seg_e']]), seg_info['seg_e']]).encode('ascii'),
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
cls.container.file('seg_with_%ff_funky_name').write('z' * 10) cls.container.file('seg_with_%ff_funky_name').write(b'z' * 10)
# Put the same manifest in the container2 # Put the same manifest in the container2
file_item = cls.container2.file("manifest-abcde") file_item = cls.container2.file("manifest-abcde")
file_item.write( file_item.write(
json.dumps([seg_info['seg_a'], seg_info['seg_b'], json.dumps([seg_info['seg_a'], seg_info['seg_b'],
seg_info['seg_c'], seg_info['seg_d'], seg_info['seg_c'], seg_info['seg_d'],
seg_info['seg_e']]), seg_info['seg_e']]).encode('ascii'),
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
file_item = cls.container.file('manifest-cd') file_item = cls.container.file('manifest-cd')
cd_json = json.dumps([seg_info['seg_c'], seg_info['seg_d']]) cd_json = json.dumps([
seg_info['seg_c'], seg_info['seg_d']]).encode('ascii')
file_item.write(cd_json, parms={'multipart-manifest': 'put'}) file_item.write(cd_json, parms={'multipart-manifest': 'put'})
cd_etag = hashlib.md5(seg_info['seg_c']['etag'] + cd_etag = hashlib.md5((
seg_info['seg_d']['etag']).hexdigest() seg_info['seg_c']['etag'] + seg_info['seg_d']['etag']
).encode('ascii')).hexdigest()
file_item = cls.container.file("manifest-bcd-submanifest") file_item = cls.container.file("manifest-bcd-submanifest")
file_item.write( file_item.write(
@ -119,10 +121,10 @@ class TestSloEnv(BaseEnv):
'size_bytes': (seg_info['seg_c']['size_bytes'] + 'size_bytes': (seg_info['seg_c']['size_bytes'] +
seg_info['seg_d']['size_bytes']), seg_info['seg_d']['size_bytes']),
'path': '/%s/%s' % (cls.container.name, 'path': '/%s/%s' % (cls.container.name,
'manifest-cd')}]), 'manifest-cd')}]).encode('ascii'),
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
bcd_submanifest_etag = hashlib.md5( bcd_submanifest_etag = hashlib.md5((
seg_info['seg_b']['etag'] + cd_etag).hexdigest() seg_info['seg_b']['etag'] + cd_etag).encode('ascii')).hexdigest()
file_item = cls.container.file("manifest-abcde-submanifest") file_item = cls.container.file("manifest-abcde-submanifest")
file_item.write( file_item.write(
@ -134,11 +136,11 @@ class TestSloEnv(BaseEnv):
seg_info['seg_d']['size_bytes']), seg_info['seg_d']['size_bytes']),
'path': '/%s/%s' % (cls.container.name, 'path': '/%s/%s' % (cls.container.name,
'manifest-bcd-submanifest')}, 'manifest-bcd-submanifest')},
seg_info['seg_e']]), seg_info['seg_e']]).encode('ascii'),
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
abcde_submanifest_etag = hashlib.md5( abcde_submanifest_etag = hashlib.md5((
seg_info['seg_a']['etag'] + bcd_submanifest_etag + seg_info['seg_a']['etag'] + bcd_submanifest_etag +
seg_info['seg_e']['etag']).hexdigest() seg_info['seg_e']['etag']).encode('ascii')).hexdigest()
abcde_submanifest_size = (seg_info['seg_a']['size_bytes'] + abcde_submanifest_size = (seg_info['seg_a']['size_bytes'] +
seg_info['seg_b']['size_bytes'] + seg_info['seg_b']['size_bytes'] +
seg_info['seg_c']['size_bytes'] + seg_info['seg_c']['size_bytes'] +
@ -162,12 +164,13 @@ class TestSloEnv(BaseEnv):
'size_bytes': abcde_submanifest_size, 'size_bytes': abcde_submanifest_size,
'path': '/%s/%s' % (cls.container.name, 'path': '/%s/%s' % (cls.container.name,
'manifest-abcde-submanifest'), 'manifest-abcde-submanifest'),
'range': '3145727-3145728'}]), # 'cd' 'range': '3145727-3145728'}]).encode('ascii'), # 'cd'
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
ranged_manifest_etag = hashlib.md5( ranged_manifest_etag = hashlib.md5((
abcde_submanifest_etag + ':3145727-4194304;' + abcde_submanifest_etag + ':3145727-4194304;' +
abcde_submanifest_etag + ':524288-1572863;' + abcde_submanifest_etag + ':524288-1572863;' +
abcde_submanifest_etag + ':3145727-3145728;').hexdigest() abcde_submanifest_etag + ':3145727-3145728;'
).encode('ascii')).hexdigest()
ranged_manifest_size = 2 * 1024 * 1024 + 4 ranged_manifest_size = 2 * 1024 * 1024 + 4
file_item = cls.container.file("ranged-submanifest") file_item = cls.container.file("ranged-submanifest")
@ -187,7 +190,7 @@ class TestSloEnv(BaseEnv):
'size_bytes': ranged_manifest_size, 'size_bytes': ranged_manifest_size,
'path': '/%s/%s' % (cls.container.name, 'path': '/%s/%s' % (cls.container.name,
'ranged-manifest'), 'ranged-manifest'),
'range': '-3'}]), 'range': '-3'}]).encode('ascii'),
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
file_item = cls.container.file("manifest-db") file_item = cls.container.file("manifest-db")
@ -197,7 +200,7 @@ class TestSloEnv(BaseEnv):
'size_bytes': None}, 'size_bytes': None},
{'path': seg_info['seg_b']['path'], 'etag': None, {'path': seg_info['seg_b']['path'], 'etag': None,
'size_bytes': None}, 'size_bytes': None},
]), parms={'multipart-manifest': 'put'}) ]).encode('ascii'), parms={'multipart-manifest': 'put'})
file_item = cls.container.file("ranged-manifest-repeated-segment") file_item = cls.container.file("ranged-manifest-repeated-segment")
file_item.write( file_item.write(
@ -208,20 +211,20 @@ class TestSloEnv(BaseEnv):
'size_bytes': None}, 'size_bytes': None},
{'path': seg_info['seg_b']['path'], 'etag': None, {'path': seg_info['seg_b']['path'], 'etag': None,
'size_bytes': None, 'range': '-1048578'}, 'size_bytes': None, 'range': '-1048578'},
]), parms={'multipart-manifest': 'put'}) ]).encode('ascii'), parms={'multipart-manifest': 'put'})
file_item = cls.container.file("mixed-object-data-manifest") file_item = cls.container.file("mixed-object-data-manifest")
file_item.write( file_item.write(
json.dumps([ json.dumps([
{'data': base64.b64encode('APRE' * 8)}, {'data': base64.b64encode(b'APRE' * 8).decode('ascii')},
{'path': seg_info['seg_a']['path']}, {'path': seg_info['seg_a']['path']},
{'data': base64.b64encode('APOS' * 16)}, {'data': base64.b64encode(b'APOS' * 16).decode('ascii')},
{'path': seg_info['seg_b']['path']}, {'path': seg_info['seg_b']['path']},
{'data': base64.b64encode('BPOS' * 32)}, {'data': base64.b64encode(b'BPOS' * 32).decode('ascii')},
{'data': base64.b64encode('CPRE' * 64)}, {'data': base64.b64encode(b'CPRE' * 64).decode('ascii')},
{'path': seg_info['seg_c']['path']}, {'path': seg_info['seg_c']['path']},
{'data': base64.b64encode('CPOS' * 8)}, {'data': base64.b64encode(b'CPOS' * 8).decode('ascii')},
]), parms={'multipart-manifest': 'put'} ]).encode('ascii'), parms={'multipart-manifest': 'put'}
) )
file_item = cls.container.file("nested-data-manifest") file_item = cls.container.file("nested-data-manifest")
@ -229,7 +232,7 @@ class TestSloEnv(BaseEnv):
json.dumps([ json.dumps([
{'path': '%s/%s' % (cls.container.name, {'path': '%s/%s' % (cls.container.name,
"mixed-object-data-manifest")} "mixed-object-data-manifest")}
]), parms={'multipart-manifest': 'put'} ]).encode('ascii'), parms={'multipart-manifest': 'put'}
) )

View File

@ -18,6 +18,7 @@ import hmac
import unittest2 import unittest2
import itertools import itertools
import hashlib import hashlib
import six
import time import time
from six.moves import urllib from six.moves import urllib
@ -35,8 +36,9 @@ from test.functional.test_tempurl import TestContainerTempurlEnv, \
TestTempurlEnv TestTempurlEnv
from test.functional.swift_test_client import ResponseError from test.functional.swift_test_client import ResponseError
import test.functional as tf import test.functional as tf
from test.unit import group_by_byte
TARGET_BODY = 'target body' TARGET_BODY = b'target body'
def setUpModule(): def setUpModule():
@ -76,7 +78,7 @@ class TestSymlinkEnv(BaseEnv):
@classmethod @classmethod
def _make_request(cls, url, token, parsed, conn, method, def _make_request(cls, url, token, parsed, conn, method,
container, obj='', headers=None, body='', container, obj='', headers=None, body=b'',
query_args=None): query_args=None):
headers = headers or {} headers = headers or {}
headers.update({'X-Auth-Token': token}) headers.update({'X-Auth-Token': token})
@ -179,7 +181,7 @@ class TestSymlink(Base):
self.env.tearDown() self.env.tearDown()
def _make_request(self, url, token, parsed, conn, method, def _make_request(self, url, token, parsed, conn, method,
container, obj='', headers=None, body='', container, obj='', headers=None, body=b'',
query_args=None, allow_redirects=True): query_args=None, allow_redirects=True):
headers = headers or {} headers = headers or {}
headers.update({'X-Auth-Token': token}) headers.update({'X-Auth-Token': token})
@ -195,7 +197,7 @@ class TestSymlink(Base):
return resp return resp
def _make_request_with_symlink_get(self, url, token, parsed, conn, method, def _make_request_with_symlink_get(self, url, token, parsed, conn, method,
container, obj, headers=None, body=''): container, obj, headers=None, body=b''):
resp = self._make_request( resp = self._make_request(
url, token, parsed, conn, method, container, obj, headers, body, url, token, parsed, conn, method, container, obj, headers, body,
query_args='symlink=get') query_args='symlink=get')
@ -245,7 +247,7 @@ class TestSymlink(Base):
self._make_request_with_symlink_get, method='GET', self._make_request_with_symlink_get, method='GET',
container=link_cont, obj=link_obj, use_account=use_account) container=link_cont, obj=link_obj, use_account=use_account)
self.assertEqual(resp.status, 200) self.assertEqual(resp.status, 200)
self.assertEqual(resp.content, '') self.assertEqual(resp.content, b'')
self.assertEqual(resp.getheader('content-length'), str(0)) self.assertEqual(resp.getheader('content-length'), str(0))
self.assertTrue(resp.getheader('x-symlink-target')) self.assertTrue(resp.getheader('x-symlink-target'))
@ -333,7 +335,7 @@ class TestSymlink(Base):
container=self.env.link_cont, obj=link_obj, container=self.env.link_cont, obj=link_obj,
headers=headers) headers=headers)
self.assertEqual(resp.status, 206) self.assertEqual(resp.status, 206)
self.assertEqual(resp.content, 'body') self.assertEqual(resp.content, b'body')
def test_create_symlink_before_target(self): def test_create_symlink_before_target(self):
link_obj = uuid4().hex link_obj = uuid4().hex
@ -431,7 +433,7 @@ class TestSymlink(Base):
container=container, container=container,
obj=too_many_chain_link) obj=too_many_chain_link)
self.assertEqual(resp.status, 409) self.assertEqual(resp.status, 409)
self.assertEqual(resp.content, '') self.assertEqual(resp.content, b'')
# try to GET to target object via too_many_chain_link # try to GET to target object via too_many_chain_link
resp = retry(self._make_request, method='GET', resp = retry(self._make_request, method='GET',
@ -440,7 +442,7 @@ class TestSymlink(Base):
self.assertEqual(resp.status, 409) self.assertEqual(resp.status, 409)
self.assertEqual( self.assertEqual(
resp.content, resp.content,
'Too many levels of symbolic links, maximum allowed is %d' % b'Too many levels of symbolic links, maximum allowed is %d' %
symloop_max) symloop_max)
# However, HEAD/GET to the (just) link is still ok # However, HEAD/GET to the (just) link is still ok
@ -522,7 +524,7 @@ class TestSymlink(Base):
resp = retry(self._make_request, method='PUT', resp = retry(self._make_request, method='PUT',
container=container, obj=too_many_recursion_manifest, container=container, obj=too_many_recursion_manifest,
body=manifest, body=manifest.encode('ascii'),
query_args='multipart-manifest=put') query_args='multipart-manifest=put')
self.assertEqual(resp.status, 201) # sanity self.assertEqual(resp.status, 201) # sanity
@ -533,8 +535,8 @@ class TestSymlink(Base):
# N.B. This error message is from slo middleware that uses default. # N.B. This error message is from slo middleware that uses default.
self.assertEqual( self.assertEqual(
resp.content, resp.content,
'<html><h1>Conflict</h1><p>There was a conflict when trying to' b'<html><h1>Conflict</h1><p>There was a conflict when trying to'
' complete your request.</p></html>') b' complete your request.</p></html>')
def test_symlink_put_missing_target_container(self): def test_symlink_put_missing_target_container(self):
link_obj = uuid4().hex link_obj = uuid4().hex
@ -546,8 +548,8 @@ class TestSymlink(Base):
headers=headers) headers=headers)
self.assertEqual(resp.status, 412) self.assertEqual(resp.status, 412)
self.assertEqual(resp.content, self.assertEqual(resp.content,
'X-Symlink-Target header must be of the form' b'X-Symlink-Target header must be of the form'
' <container name>/<object name>') b' <container name>/<object name>')
def test_symlink_put_non_zero_length(self): def test_symlink_put_non_zero_length(self):
link_obj = uuid4().hex link_obj = uuid4().hex
@ -559,7 +561,7 @@ class TestSymlink(Base):
self.assertEqual(resp.status, 400) self.assertEqual(resp.status, 400)
self.assertEqual(resp.content, self.assertEqual(resp.content,
'Symlink requests require a zero byte body') b'Symlink requests require a zero byte body')
def test_symlink_target_itself(self): def test_symlink_target_itself(self):
link_obj = uuid4().hex link_obj = uuid4().hex
@ -569,7 +571,7 @@ class TestSymlink(Base):
container=self.env.link_cont, obj=link_obj, container=self.env.link_cont, obj=link_obj,
headers=headers) headers=headers)
self.assertEqual(resp.status, 400) self.assertEqual(resp.status, 400)
self.assertEqual(resp.content, 'Symlink cannot target itself') self.assertEqual(resp.content, b'Symlink cannot target itself')
def test_symlink_target_each_other(self): def test_symlink_target_each_other(self):
symloop_max = cluster_info['symlink']['symloop_max'] symloop_max = cluster_info['symlink']['symloop_max']
@ -599,7 +601,7 @@ class TestSymlink(Base):
self.assertEqual(resp.status, 409) self.assertEqual(resp.status, 409)
self.assertEqual( self.assertEqual(
resp.content, resp.content,
'Too many levels of symbolic links, maximum allowed is %d' % b'Too many levels of symbolic links, maximum allowed is %d' %
symloop_max) symloop_max)
def test_symlink_put_copy_from(self): def test_symlink_put_copy_from(self):
@ -828,7 +830,7 @@ class TestSymlink(Base):
obj=self.env.tgt_obj, headers=headers, allow_redirects=False) obj=self.env.tgt_obj, headers=headers, allow_redirects=False)
self.assertEqual(resp.status, 400) self.assertEqual(resp.status, 400)
self.assertEqual(resp.content, self.assertEqual(resp.content,
'A PUT request is required to set a symlink target') b'A PUT request is required to set a symlink target')
def test_overwrite_symlink(self): def test_overwrite_symlink(self):
link_obj = uuid4().hex link_obj = uuid4().hex
@ -1010,41 +1012,39 @@ class TestSymlinkSlo(Base):
self.file_symlink.write(hdrs={'X-Symlink-Target': self.file_symlink.write(hdrs={'X-Symlink-Target':
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'manifest-abcde')}) 'manifest-abcde')})
file_contents = self.file_symlink.read() self.assertEqual([
self.assertEqual(4 * 1024 * 1024 + 1, len(file_contents)) (b'a', 1024 * 1024),
self.assertEqual('a', file_contents[0]) (b'b', 1024 * 1024),
self.assertEqual('a', file_contents[1024 * 1024 - 1]) (b'c', 1024 * 1024),
self.assertEqual('b', file_contents[1024 * 1024]) (b'd', 1024 * 1024),
self.assertEqual('d', file_contents[-2]) (b'e', 1),
self.assertEqual('e', file_contents[-1]) ], group_by_byte(self.file_symlink.read()))
def test_symlink_target_slo_nested_manifest(self): def test_symlink_target_slo_nested_manifest(self):
self.file_symlink.write(hdrs={'X-Symlink-Target': self.file_symlink.write(hdrs={'X-Symlink-Target':
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'manifest-abcde-submanifest')}) 'manifest-abcde-submanifest')})
file_contents = self.file_symlink.read() self.assertEqual([
self.assertEqual(4 * 1024 * 1024 + 1, len(file_contents)) (b'a', 1024 * 1024),
self.assertEqual('a', file_contents[0]) (b'b', 1024 * 1024),
self.assertEqual('a', file_contents[1024 * 1024 - 1]) (b'c', 1024 * 1024),
self.assertEqual('b', file_contents[1024 * 1024]) (b'd', 1024 * 1024),
self.assertEqual('d', file_contents[-2]) (b'e', 1),
self.assertEqual('e', file_contents[-1]) ], group_by_byte(self.file_symlink.read()))
def test_slo_get_ranged_manifest(self): def test_slo_get_ranged_manifest(self):
self.file_symlink.write(hdrs={'X-Symlink-Target': self.file_symlink.write(hdrs={'X-Symlink-Target':
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'ranged-manifest')}) 'ranged-manifest')})
grouped_file_contents = [
(char, sum(1 for _char in grp))
for char, grp in itertools.groupby(self.file_symlink.read())]
self.assertEqual([ self.assertEqual([
('c', 1), (b'c', 1),
('d', 1024 * 1024), (b'd', 1024 * 1024),
('e', 1), (b'e', 1),
('a', 512 * 1024), (b'a', 512 * 1024),
('b', 512 * 1024), (b'b', 512 * 1024),
('c', 1), (b'c', 1),
('d', 1)], grouped_file_contents) (b'd', 1),
], group_by_byte(self.file_symlink.read()))
def test_slo_ranged_get(self): def test_slo_ranged_get(self):
self.file_symlink.write(hdrs={'X-Symlink-Target': self.file_symlink.write(hdrs={'X-Symlink-Target':
@ -1052,10 +1052,11 @@ class TestSymlinkSlo(Base):
'manifest-abcde')}) 'manifest-abcde')})
file_contents = self.file_symlink.read(size=1024 * 1024 + 2, file_contents = self.file_symlink.read(size=1024 * 1024 + 2,
offset=1024 * 1024 - 1) offset=1024 * 1024 - 1)
self.assertEqual('a', file_contents[0]) self.assertEqual([
self.assertEqual('b', file_contents[1]) (b'a', 1),
self.assertEqual('b', file_contents[-2]) (b'b', 1024 * 1024),
self.assertEqual('c', file_contents[-1]) (b'c', 1),
], group_by_byte(file_contents))
class TestSymlinkSloEnv(TestSloEnv): class TestSymlinkSloEnv(TestSloEnv):
@ -1081,7 +1082,7 @@ class TestSymlinkSloEnv(TestSloEnv):
file_item = cls.container.file("manifest-linkto-ab") file_item = cls.container.file("manifest-linkto-ab")
file_item.write( file_item.write(
json.dumps([cls.link_seg_info['linkto_seg_a'], json.dumps([cls.link_seg_info['linkto_seg_a'],
cls.link_seg_info['linkto_seg_b']]), cls.link_seg_info['linkto_seg_b']]).encode('ascii'),
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
@ -1106,18 +1107,18 @@ class TestSymlinkToSloSegments(Base):
def test_slo_get_simple_manifest_with_links(self): def test_slo_get_simple_manifest_with_links(self):
file_item = self.env.container.file("manifest-linkto-ab") file_item = self.env.container.file("manifest-linkto-ab")
file_contents = file_item.read() self.assertEqual([
self.assertEqual(2 * 1024 * 1024, len(file_contents)) (b'a', 1024 * 1024),
self.assertEqual('a', file_contents[0]) (b'b', 1024 * 1024),
self.assertEqual('a', file_contents[1024 * 1024 - 1]) ], group_by_byte(file_item.read()))
self.assertEqual('b', file_contents[1024 * 1024])
def test_slo_container_listing(self): def test_slo_container_listing(self):
# the listing object size should equal the sum of the size of the # the listing object size should equal the sum of the size of the
# segments, not the size of the manifest body # segments, not the size of the manifest body
file_item = self.env.container.file(Utils.create_name()) file_item = self.env.container.file(Utils.create_name())
file_item.write( file_item.write(
json.dumps([self.env.link_seg_info['linkto_seg_a']]), json.dumps([
self.env.link_seg_info['linkto_seg_a']]).encode('ascii'),
parms={'multipart-manifest': 'put'}) parms={'multipart-manifest': 'put'})
# The container listing has the etag of the actual manifest object # The container listing has the etag of the actual manifest object
@ -1182,8 +1183,10 @@ class TestSymlinkToSloSegments(Base):
def test_slo_etag_is_hash_of_etags(self): def test_slo_etag_is_hash_of_etags(self):
expected_hash = hashlib.md5() expected_hash = hashlib.md5()
expected_hash.update(hashlib.md5('a' * 1024 * 1024).hexdigest()) expected_hash.update(hashlib.md5(
expected_hash.update(hashlib.md5('b' * 1024 * 1024).hexdigest()) b'a' * 1024 * 1024).hexdigest().encode('ascii'))
expected_hash.update(hashlib.md5(
b'b' * 1024 * 1024).hexdigest().encode('ascii'))
expected_etag = expected_hash.hexdigest() expected_etag = expected_hash.hexdigest()
file_item = self.env.container.file('manifest-linkto-ab') file_item = self.env.container.file('manifest-linkto-ab')
@ -1194,8 +1197,10 @@ class TestSymlinkToSloSegments(Base):
file_item.copy(self.env.container.name, "copied-abcde") file_item.copy(self.env.container.name, "copied-abcde")
copied = self.env.container.file("copied-abcde") copied = self.env.container.file("copied-abcde")
copied_contents = copied.read(parms={'multipart-manifest': 'get'}) self.assertEqual([
self.assertEqual(2 * 1024 * 1024, len(copied_contents)) (b'a', 1024 * 1024),
(b'b', 1024 * 1024),
], group_by_byte(copied.read(parms={'multipart-manifest': 'get'})))
def test_slo_copy_the_manifest(self): def test_slo_copy_the_manifest(self):
# first just perform some tests of the contents of the manifest itself # first just perform some tests of the contents of the manifest itself
@ -1270,31 +1275,44 @@ class TestSymlinkDlo(Base):
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'man1')}) 'man1')})
file_contents = file_symlink.read() self.assertEqual([
self.assertEqual( (b'a', 10),
file_contents, (b'b', 10),
"aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee") (b'c', 10),
(b'd', 10),
(b'e', 10),
], group_by_byte(file_symlink.read()))
link_obj = uuid4().hex link_obj = uuid4().hex
file_symlink = self.env.container.file(link_obj) file_symlink = self.env.container.file(link_obj)
file_symlink.write(hdrs={'X-Symlink-Target': file_symlink.write(hdrs={'X-Symlink-Target':
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'man2')}) 'man2')})
file_contents = file_symlink.read() self.assertEqual([
self.assertEqual( (b'A', 10),
file_contents, (b'B', 10),
"AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEE") (b'C', 10),
(b'D', 10),
(b'E', 10),
], group_by_byte(file_symlink.read()))
link_obj = uuid4().hex link_obj = uuid4().hex
file_symlink = self.env.container.file(link_obj) file_symlink = self.env.container.file(link_obj)
file_symlink.write(hdrs={'X-Symlink-Target': file_symlink.write(hdrs={'X-Symlink-Target':
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'manall')}) 'manall')})
file_contents = file_symlink.read() self.assertEqual([
self.assertEqual( (b'a', 10),
file_contents, (b'b', 10),
("aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeee" + (b'c', 10),
"AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEE")) (b'd', 10),
(b'e', 10),
(b'A', 10),
(b'B', 10),
(b'C', 10),
(b'D', 10),
(b'E', 10),
], group_by_byte(file_symlink.read()))
def test_get_manifest_document_itself(self): def test_get_manifest_document_itself(self):
link_obj = uuid4().hex link_obj = uuid4().hex
@ -1303,7 +1321,7 @@ class TestSymlinkDlo(Base):
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'man1')}) 'man1')})
file_contents = file_symlink.read(parms={'multipart-manifest': 'get'}) file_contents = file_symlink.read(parms={'multipart-manifest': 'get'})
self.assertEqual(file_contents, "man1-contents") self.assertEqual(file_contents, b"man1-contents")
self.assertEqual(file_symlink.info()['x_object_manifest'], self.assertEqual(file_symlink.info()['x_object_manifest'],
"%s/%s/seg_lower" % "%s/%s/seg_lower" %
(self.env.container.name, self.env.segment_prefix)) (self.env.container.name, self.env.segment_prefix))
@ -1314,11 +1332,15 @@ class TestSymlinkDlo(Base):
file_symlink.write(hdrs={'X-Symlink-Target': file_symlink.write(hdrs={'X-Symlink-Target':
'%s/%s' % (self.env.container.name, '%s/%s' % (self.env.container.name,
'man1')}) 'man1')})
file_contents = file_symlink.read(size=25, offset=8) self.assertEqual([
self.assertEqual(file_contents, "aabbbbbbbbbbccccccccccddd") (b'a', 2),
(b'b', 10),
(b'c', 10),
(b'd', 3),
], group_by_byte(file_symlink.read(size=25, offset=8)))
file_contents = file_symlink.read(size=1, offset=47) file_contents = file_symlink.read(size=1, offset=47)
self.assertEqual(file_contents, "e") self.assertEqual(file_contents, b"e")
def test_get_range_out_of_range(self): def test_get_range_out_of_range(self):
link_obj = uuid4().hex link_obj = uuid4().hex
@ -1373,7 +1395,7 @@ class TestSymlinkTargetObjectComparison(Base):
if self.env.expect_body: if self.env.expect_body:
self.assertTrue(body) self.assertTrue(body)
else: else:
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
@ -1395,7 +1417,7 @@ class TestSymlinkTargetObjectComparison(Base):
if self.env.expect_body: if self.env.expect_body:
self.assertTrue(body) self.assertTrue(body)
else: else:
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
@ -1417,7 +1439,7 @@ class TestSymlinkTargetObjectComparison(Base):
if self.env.expect_body: if self.env.expect_body:
self.assertTrue(body) self.assertTrue(body)
else: else:
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
@ -1440,7 +1462,7 @@ class TestSymlinkTargetObjectComparison(Base):
if self.env.expect_body: if self.env.expect_body:
self.assertTrue(body) self.assertTrue(body)
else: else:
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
@ -1464,7 +1486,7 @@ class TestSymlinkTargetObjectComparison(Base):
if self.env.expect_body: if self.env.expect_body:
self.assertTrue(body) self.assertTrue(body)
else: else:
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
self.assertTrue(file_symlink.info(hdrs=hdrs, parms=self.env.parms)) self.assertTrue(file_symlink.info(hdrs=hdrs, parms=self.env.parms))
@ -1493,7 +1515,7 @@ class TestSymlinkTargetObjectComparison(Base):
if self.env.expect_body: if self.env.expect_body:
self.assertTrue(body) self.assertTrue(body)
else: else:
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
self.assertTrue(file_symlink.info(hdrs=hdrs, parms=self.env.parms)) self.assertTrue(file_symlink.info(hdrs=hdrs, parms=self.env.parms))
@ -1521,7 +1543,7 @@ class TestSymlinkTargetObjectComparison(Base):
if self.env.expect_body: if self.env.expect_body:
self.assertTrue(body) self.assertTrue(body)
else: else:
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
@ -1600,7 +1622,7 @@ class TestSymlinkComparison(TestSymlinkTargetObjectComparison):
hdrs = {'If-Modified-Since': put_target_last_modified} hdrs = {'If-Modified-Since': put_target_last_modified}
body = file_symlink.read(hdrs=hdrs, parms=self.env.parms) body = file_symlink.read(hdrs=hdrs, parms=self.env.parms)
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
@ -1613,7 +1635,7 @@ class TestSymlinkComparison(TestSymlinkTargetObjectComparison):
hdrs = {'If-Unmodified-Since': last_modified} hdrs = {'If-Unmodified-Since': last_modified}
body = file_symlink.read(hdrs=hdrs, parms=self.env.parms) body = file_symlink.read(hdrs=hdrs, parms=self.env.parms)
self.assertEqual('', body) self.assertEqual(b'', body)
self.assert_status(200) self.assert_status(200)
self.assert_header('etag', md5) self.assert_header('etag', md5)
@ -1644,9 +1666,14 @@ class TestSymlinkAccountTempurl(Base):
self.env.tempurl_key) self.env.tempurl_key)
def tempurl_parms(self, method, expires, path, key): def tempurl_parms(self, method, expires, path, key):
path = urllib.parse.unquote(path)
if not six.PY2:
method = method.encode('utf8')
path = path.encode('utf8')
key = key.encode('utf8')
sig = hmac.new( sig = hmac.new(
key, key,
'%s\n%s\n%s' % (method, expires, urllib.parse.unquote(path)), b'%s\n%d\n%s' % (method, expires, path),
self.digest).hexdigest() self.digest).hexdigest()
return {'temp_url_sig': sig, 'temp_url_expires': str(expires)} return {'temp_url_sig': sig, 'temp_url_expires': str(expires)}
@ -1662,7 +1689,7 @@ class TestSymlinkAccountTempurl(Base):
# try to create symlink object # try to create symlink object
try: try:
new_sym.write( new_sym.write(
'', {'x-symlink-target': 'cont/foo'}, parms=put_parms, b'', {'x-symlink-target': 'cont/foo'}, parms=put_parms,
cfg={'no_auth_token': True}) cfg={'no_auth_token': True})
except ResponseError as e: except ResponseError as e:
self.assertEqual(e.status, 400) self.assertEqual(e.status, 400)
@ -1672,9 +1699,9 @@ class TestSymlinkAccountTempurl(Base):
def test_GET_symlink_inside_container(self): def test_GET_symlink_inside_container(self):
tgt_obj = self.env.container.file(Utils.create_name()) tgt_obj = self.env.container.file(Utils.create_name())
sym = self.env.container.file(Utils.create_name()) sym = self.env.container.file(Utils.create_name())
tgt_obj.write("target object body") tgt_obj.write(b"target object body")
sym.write( sym.write(
'', b'',
{'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)}) {'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)})
expires = int(time.time()) + 86400 expires = int(time.time()) + 86400
@ -1684,18 +1711,18 @@ class TestSymlinkAccountTempurl(Base):
contents = sym.read(parms=get_parms, cfg={'no_auth_token': True}) contents = sym.read(parms=get_parms, cfg={'no_auth_token': True})
self.assert_status([200]) self.assert_status([200])
self.assertEqual(contents, "target object body") self.assertEqual(contents, b"target object body")
def test_GET_symlink_outside_container(self): def test_GET_symlink_outside_container(self):
tgt_obj = self.env.container.file(Utils.create_name()) tgt_obj = self.env.container.file(Utils.create_name())
tgt_obj.write("target object body") tgt_obj.write(b"target object body")
container2 = self.env.account.container(Utils.create_name()) container2 = self.env.account.container(Utils.create_name())
container2.create() container2.create()
sym = container2.file(Utils.create_name()) sym = container2.file(Utils.create_name())
sym.write( sym.write(
'', b'',
{'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)}) {'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)})
expires = int(time.time()) + 86400 expires = int(time.time()) + 86400
@ -1706,7 +1733,7 @@ class TestSymlinkAccountTempurl(Base):
# cross container tempurl works fine for account tempurl key # cross container tempurl works fine for account tempurl key
contents = sym.read(parms=get_parms, cfg={'no_auth_token': True}) contents = sym.read(parms=get_parms, cfg={'no_auth_token': True})
self.assert_status([200]) self.assert_status([200])
self.assertEqual(contents, "target object body") self.assertEqual(contents, b"target object body")
class TestSymlinkContainerTempurl(Base): class TestSymlinkContainerTempurl(Base):
@ -1737,9 +1764,14 @@ class TestSymlinkContainerTempurl(Base):
'temp_url_expires': str(expires)} 'temp_url_expires': str(expires)}
def tempurl_sig(self, method, expires, path, key): def tempurl_sig(self, method, expires, path, key):
path = urllib.parse.unquote(path)
if not six.PY2:
method = method.encode('utf8')
path = path.encode('utf8')
key = key.encode('utf8')
return hmac.new( return hmac.new(
key, key,
'%s\n%s\n%s' % (method, expires, urllib.parse.unquote(path)), b'%s\n%d\n%s' % (method, expires, path),
self.digest).hexdigest() self.digest).hexdigest()
def test_PUT_symlink(self): def test_PUT_symlink(self):
@ -1756,7 +1788,7 @@ class TestSymlinkContainerTempurl(Base):
# try to create symlink object, should fail # try to create symlink object, should fail
try: try:
new_sym.write( new_sym.write(
'', {'x-symlink-target': 'cont/foo'}, parms=put_parms, b'', {'x-symlink-target': 'cont/foo'}, parms=put_parms,
cfg={'no_auth_token': True}) cfg={'no_auth_token': True})
except ResponseError as e: except ResponseError as e:
self.assertEqual(e.status, 400) self.assertEqual(e.status, 400)
@ -1766,9 +1798,9 @@ class TestSymlinkContainerTempurl(Base):
def test_GET_symlink_inside_container(self): def test_GET_symlink_inside_container(self):
tgt_obj = self.env.container.file(Utils.create_name()) tgt_obj = self.env.container.file(Utils.create_name())
sym = self.env.container.file(Utils.create_name()) sym = self.env.container.file(Utils.create_name())
tgt_obj.write("target object body") tgt_obj.write(b"target object body")
sym.write( sym.write(
'', b'',
{'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)}) {'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)})
expires = int(time.time()) + 86400 expires = int(time.time()) + 86400
@ -1780,18 +1812,18 @@ class TestSymlinkContainerTempurl(Base):
contents = sym.read(parms=parms, cfg={'no_auth_token': True}) contents = sym.read(parms=parms, cfg={'no_auth_token': True})
self.assert_status([200]) self.assert_status([200])
self.assertEqual(contents, "target object body") self.assertEqual(contents, b"target object body")
def test_GET_symlink_outside_container(self): def test_GET_symlink_outside_container(self):
tgt_obj = self.env.container.file(Utils.create_name()) tgt_obj = self.env.container.file(Utils.create_name())
tgt_obj.write("target object body") tgt_obj.write(b"target object body")
container2 = self.env.account.container(Utils.create_name()) container2 = self.env.account.container(Utils.create_name())
container2.create() container2.create()
sym = container2.file(Utils.create_name()) sym = container2.file(Utils.create_name())
sym.write( sym.write(
'', b'',
{'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)}) {'x-symlink-target': '%s/%s' % (self.env.container.name, tgt_obj)})
expires = int(time.time()) + 86400 expires = int(time.time()) + 86400

View File

@ -82,9 +82,9 @@ class TestTempurlEnv(TestTempurlBaseEnv):
raise ResponseError(cls.conn.response) raise ResponseError(cls.conn.response)
cls.obj = cls.container.file(Utils.create_name()) cls.obj = cls.container.file(Utils.create_name())
cls.obj.write("obj contents") cls.obj.write(b"obj contents")
cls.other_obj = cls.container.file(Utils.create_name()) cls.other_obj = cls.container.file(Utils.create_name())
cls.other_obj.write("other obj contents") cls.other_obj.write(b"other obj contents")
class TestTempurl(Base): class TestTempurl(Base):
@ -437,9 +437,9 @@ class TestContainerTempurlEnv(BaseEnv):
raise ResponseError(cls.conn.response) raise ResponseError(cls.conn.response)
cls.obj = cls.container.file(Utils.create_name()) cls.obj = cls.container.file(Utils.create_name())
cls.obj.write("obj contents") cls.obj.write(b"obj contents")
cls.other_obj = cls.container.file(Utils.create_name()) cls.other_obj = cls.container.file(Utils.create_name())
cls.other_obj.write("other obj contents") cls.other_obj.write(b"other obj contents")
class TestContainerTempurl(Base): class TestContainerTempurl(Base):

View File

@ -1424,3 +1424,13 @@ def attach_fake_replication_rpc(rpc, replicate_hook=None, errors=None):
return resp return resp
return FakeReplConnection return FakeReplConnection
def group_by_byte(contents):
# This looks a little funny, but iterating through a byte string on py3
# yields a sequence of ints, not a sequence of single-byte byte strings
# as it did on py2.
byte_iter = (contents[i:i + 1] for i in range(len(contents)))
return [
(char, sum(1 for _ in grp))
for char, grp in itertools.groupby(byte_iter)]

View File

@ -415,6 +415,9 @@ class TestSymlinkMiddleware(TestSymlinkMiddlewareBase):
do_test( do_test(
{'X-Symlink-Target': 'cont/obj', {'X-Symlink-Target': 'cont/obj',
'X-Symlink-Target-Account': target}) 'X-Symlink-Target-Account': target})
do_test(
{'X-Symlink-Target': 'cont/obj',
'X-Symlink-Target-Account': swob.wsgi_quote(target)})
def test_check_symlink_header_invalid_format(self): def test_check_symlink_header_invalid_format(self):
def do_test(headers, status, err_msg): def do_test(headers, status, err_msg):
@ -456,26 +459,25 @@ class TestSymlinkMiddleware(TestSymlinkMiddlewareBase):
'412 Precondition Failed', '412 Precondition Failed',
b'Account name cannot contain slashes') b'Account name cannot contain slashes')
# with multi-bytes # with multi-bytes
target = u'/\u30b0\u30e9\u30d6\u30eb/\u30a2\u30ba\u30ec\u30f3'
target = swob.bytes_to_wsgi(target.encode('utf8'))
do_test( do_test(
{'X-Symlink-Target': {'X-Symlink-Target': target},
u'/\u30b0\u30e9\u30d6\u30eb/\u30a2\u30ba\u30ec\u30f3'},
'412 Precondition Failed', '412 Precondition Failed',
b'X-Symlink-Target header must be of the ' b'X-Symlink-Target header must be of the '
b'form <container name>/<object name>') b'form <container name>/<object name>')
target = u'/\u30b0\u30e9\u30d6\u30eb/\u30a2\u30ba\u30ec\u30f3'
target = swob.bytes_to_wsgi(target.encode('utf8'))
do_test( do_test(
{'X-Symlink-Target': swob.wsgi_quote(target)}, {'X-Symlink-Target': swob.wsgi_quote(target)},
'412 Precondition Failed', '412 Precondition Failed',
b'X-Symlink-Target header must be of the ' b'X-Symlink-Target header must be of the '
b'form <container name>/<object name>') b'form <container name>/<object name>')
account = u'\u30b0\u30e9\u30d6\u30eb/\u30a2\u30ba\u30ec\u30f3' account = u'\u30b0\u30e9\u30d6\u30eb/\u30a2\u30ba\u30ec\u30f3'
account = swob.bytes_to_wsgi(account.encode('utf8'))
do_test( do_test(
{'X-Symlink-Target': 'c/o', {'X-Symlink-Target': 'c/o',
'X-Symlink-Target-Account': account}, 'X-Symlink-Target-Account': account},
'412 Precondition Failed', '412 Precondition Failed',
b'Account name cannot contain slashes') b'Account name cannot contain slashes')
account = swob.bytes_to_wsgi(account.encode('utf8'))
do_test( do_test(
{'X-Symlink-Target': 'c/o', {'X-Symlink-Target': 'c/o',
'X-Symlink-Target-Account': swob.wsgi_quote(account)}, 'X-Symlink-Target-Account': swob.wsgi_quote(account)},

View File

@ -129,6 +129,7 @@ basepython = python3
commands = commands =
pip install -U eventlet@git+https://github.com/eventlet/eventlet.git pip install -U eventlet@git+https://github.com/eventlet/eventlet.git
nosetests {posargs: \ nosetests {posargs: \
test/functional/test_symlink.py \
test/functional/tests.py} test/functional/tests.py}
[testenv:func-encryption] [testenv:func-encryption]