Merge pull request #1103 from LeastAuthority/3525.test_status-no-mock

Remove usage of mock from allmydata/test/cli/test_status.py

Fixes: ticket:3525
This commit is contained in:
Jean-Paul Calderone 2021-09-22 16:48:34 -04:00 committed by GitHub
commit c67c0727e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 115 additions and 89 deletions

0
newsfragments/3525.minor Normal file
View File

View File

@ -11,21 +11,48 @@ if PY2:
from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401 from future.builtins import filter, map, zip, ascii, chr, hex, input, next, oct, open, pow, round, super, bytes, dict, list, object, range, str, max, min # noqa: F401
import os import os
from urllib.parse import urlencode, quote as url_quote from sys import stdout as _sys_stdout
from urllib.parse import urlencode
import json import json
from .common import BaseOptions from .common import BaseOptions
from allmydata.scripts.common import get_default_nodedir from allmydata.scripts.common import get_default_nodedir
from allmydata.scripts.common_http import do_http, BadResponse from allmydata.scripts.common_http import BadResponse
from allmydata.util.abbreviate import abbreviate_space, abbreviate_time from allmydata.util.abbreviate import abbreviate_space, abbreviate_time
from allmydata.util.encodingutil import argv_to_abspath from allmydata.util.encodingutil import argv_to_abspath
_print = print
def _get_json_for_fragment(options, fragment, method='GET', post_args=None): def print(*args, **kwargs):
""" """
returns the JSON for a particular URI-fragment (to which is Builtin ``print``-alike that will even write unicode which cannot be
pre-pended the node's URL) encoded using the specified output file's encoding.
This differs from the builtin print in that it will use the "replace"
encoding error handler and then write the result whereas builtin print
uses the "strict" encoding error handler.
"""
from past.builtins import unicode
out = kwargs.pop("file", None)
if out is None:
out = _sys_stdout
encoding = out.encoding or "ascii"
def ensafe(o):
if isinstance(o, unicode):
return o.encode(encoding, errors="replace").decode(encoding)
return o
return _print(
*(ensafe(a) for a in args),
file=out,
**kwargs
)
def _get_request_parameters_for_fragment(options, fragment, method, post_args):
"""
Get parameters for ``do_http`` for requesting the given fragment.
:return dict: A dictionary suitable for use as keyword arguments to
``do_http``.
""" """
nodeurl = options['node-url'] nodeurl = options['node-url']
if nodeurl.endswith('/'): if nodeurl.endswith('/'):
@ -40,7 +67,17 @@ def _get_json_for_fragment(options, fragment, method='GET', post_args=None):
body = '' body = ''
if post_args is not None: if post_args is not None:
raise ValueError("post_args= only valid for POST method") raise ValueError("post_args= only valid for POST method")
resp = do_http(method, url, body=body.encode("utf-8")) return dict(
method=method,
url=url,
body=body.encode("utf-8"),
)
def _handle_response_for_fragment(resp, nodeurl):
"""
Inspect an HTTP response and return the parsed payload, if possible.
"""
if isinstance(resp, BadResponse): if isinstance(resp, BadResponse):
# specifically NOT using format_http_error() here because the # specifically NOT using format_http_error() here because the
# URL is pretty sensitive (we're doing /uri/<key>). # URL is pretty sensitive (we're doing /uri/<key>).
@ -55,12 +92,6 @@ def _get_json_for_fragment(options, fragment, method='GET', post_args=None):
return parsed return parsed
def _get_json_for_cap(options, cap):
return _get_json_for_fragment(
options,
'uri/%s?t=json' % url_quote(cap),
)
def pretty_progress(percent, size=10, output_ascii=False): def pretty_progress(percent, size=10, output_ascii=False):
""" """
Displays a unicode or ascii based progress bar of a certain Displays a unicode or ascii based progress bar of a certain
@ -251,7 +282,10 @@ def render_recent(verbose, stdout, status_data):
print(u" Skipped {} non-upload/download operations; use --verbose to see".format(skipped), file=stdout) print(u" Skipped {} non-upload/download operations; use --verbose to see".format(skipped), file=stdout)
def do_status(options): def do_status(options, do_http=None):
if do_http is None:
from allmydata.scripts.common_http import do_http
nodedir = options["node-directory"] nodedir = options["node-directory"]
with open(os.path.join(nodedir, u'private', u'api_auth_token'), 'r') as f: with open(os.path.join(nodedir, u'private', u'api_auth_token'), 'r') as f:
token = f.read().strip() token = f.read().strip()
@ -260,25 +294,30 @@ def do_status(options):
# do *all* our data-retrievals first in case there's an error # do *all* our data-retrievals first in case there's an error
try: try:
status_data = _get_json_for_fragment( status_data = _handle_response_for_fragment(
do_http(**_get_request_parameters_for_fragment(
options, options,
'status?t=json', 'status?t=json',
method='POST', method='POST',
post_args=dict( post_args=dict(
t='json', t='json',
token=token, token=token,
),
)),
options['node-url'],
) )
) statistics_data = _handle_response_for_fragment(
statistics_data = _get_json_for_fragment( do_http(**_get_request_parameters_for_fragment(
options, options,
'statistics?t=json', 'statistics?t=json',
method='POST', method='POST',
post_args=dict( post_args=dict(
t='json', t='json',
token=token, token=token,
),
)),
options['node-url'],
) )
)
except Exception as e: except Exception as e:
print(u"failed to retrieve data: %s" % str(e), file=options.stderr) print(u"failed to retrieve data: %s" % str(e), file=options.stderr)
return 2 return 2

View File

@ -12,7 +12,6 @@ if PY2:
from six import ensure_text from six import ensure_text
import os import os
import mock
import tempfile import tempfile
from io import BytesIO, StringIO from io import BytesIO, StringIO
from os.path import join from os.path import join
@ -22,8 +21,8 @@ from twisted.internet import defer
from allmydata.mutable.publish import MutableData from allmydata.mutable.publish import MutableData
from allmydata.scripts.common_http import BadResponse from allmydata.scripts.common_http import BadResponse
from allmydata.scripts.tahoe_status import _get_json_for_fragment from allmydata.scripts.tahoe_status import _handle_response_for_fragment
from allmydata.scripts.tahoe_status import _get_json_for_cap from allmydata.scripts.tahoe_status import _get_request_parameters_for_fragment
from allmydata.scripts.tahoe_status import pretty_progress from allmydata.scripts.tahoe_status import pretty_progress
from allmydata.scripts.tahoe_status import do_status from allmydata.scripts.tahoe_status import do_status
from allmydata.web.status import marshal_json from allmydata.web.status import marshal_json
@ -140,17 +139,12 @@ class CommandStatus(unittest.TestCase):
""" """
These tests just exercise the renderers and ensure they don't These tests just exercise the renderers and ensure they don't
catastrophically fail. catastrophically fail.
They could be enhanced to look for "some" magic strings in the
results and assert they're in the output.
""" """
def setUp(self): def setUp(self):
self.options = _FakeOptions() self.options = _FakeOptions()
@mock.patch('allmydata.scripts.tahoe_status.do_http') def test_no_operations(self):
@mock.patch('sys.stdout', StringIO())
def test_no_operations(self, http):
values = [ values = [
StringIO(ensure_text(json.dumps({ StringIO(ensure_text(json.dumps({
"active": [], "active": [],
@ -165,12 +159,11 @@ class CommandStatus(unittest.TestCase):
} }
}))), }))),
] ]
http.side_effect = lambda *args, **kw: values.pop(0) def do_http(*args, **kw):
do_status(self.options) return values.pop(0)
do_status(self.options, do_http)
@mock.patch('allmydata.scripts.tahoe_status.do_http') def test_simple(self):
@mock.patch('sys.stdout', StringIO())
def test_simple(self, http):
recent_items = active_items = [ recent_items = active_items = [
UploadStatus(), UploadStatus(),
DownloadStatus(b"abcd", 12345), DownloadStatus(b"abcd", 12345),
@ -201,80 +194,72 @@ class CommandStatus(unittest.TestCase):
} }
}).encode("utf-8")), }).encode("utf-8")),
] ]
http.side_effect = lambda *args, **kw: values.pop(0) def do_http(*args, **kw):
do_status(self.options) return values.pop(0)
do_status(self.options, do_http)
@mock.patch('allmydata.scripts.tahoe_status.do_http') def test_fetch_error(self):
def test_fetch_error(self, http): def do_http(*args, **kw):
def boom(*args, **kw):
raise RuntimeError("boom") raise RuntimeError("boom")
http.side_effect = boom do_status(self.options, do_http)
do_status(self.options)
class JsonHelpers(unittest.TestCase): class JsonHelpers(unittest.TestCase):
@mock.patch('allmydata.scripts.tahoe_status.do_http') def test_bad_response(self):
def test_bad_response(self, http): def do_http(*args, **kw):
http.return_value = BadResponse('the url', 'some err') return
with self.assertRaises(RuntimeError) as ctx: with self.assertRaises(RuntimeError) as ctx:
_get_json_for_fragment({'node-url': 'http://localhost:1234'}, '/fragment') _handle_response_for_fragment(
self.assertTrue( BadResponse('the url', 'some err'),
"Failed to get" in str(ctx.exception) 'http://localhost:1234',
)
self.assertIn(
"Failed to get",
str(ctx.exception),
) )
@mock.patch('allmydata.scripts.tahoe_status.do_http') def test_happy_path(self):
def test_happy_path(self, http): resp = _handle_response_for_fragment(
http.return_value = StringIO('{"some": "json"}') StringIO('{"some": "json"}'),
resp = _get_json_for_fragment({'node-url': 'http://localhost:1234/'}, '/fragment/') 'http://localhost:1234/',
self.assertEqual(resp, dict(some='json'))
@mock.patch('allmydata.scripts.tahoe_status.do_http')
def test_happy_path_post(self, http):
http.return_value = StringIO('{"some": "json"}')
resp = _get_json_for_fragment(
{'node-url': 'http://localhost:1234/'},
'/fragment/',
method='POST',
post_args={'foo': 'bar'}
) )
self.assertEqual(resp, dict(some='json')) self.assertEqual(resp, dict(some='json'))
@mock.patch('allmydata.scripts.tahoe_status.do_http') def test_happy_path_post(self):
def test_happy_path_for_cap(self, http): resp = _handle_response_for_fragment(
http.return_value = StringIO('{"some": "json"}') StringIO('{"some": "json"}'),
resp = _get_json_for_cap({'node-url': 'http://localhost:1234'}, 'fake cap') 'http://localhost:1234/',
)
self.assertEqual(resp, dict(some='json')) self.assertEqual(resp, dict(some='json'))
@mock.patch('allmydata.scripts.tahoe_status.do_http') def test_no_data_returned(self):
def test_no_data_returned(self, http):
http.return_value = StringIO('null')
with self.assertRaises(RuntimeError) as ctx: with self.assertRaises(RuntimeError) as ctx:
_get_json_for_cap({'node-url': 'http://localhost:1234'}, 'fake cap') _handle_response_for_fragment(StringIO('null'), 'http://localhost:1234')
self.assertTrue('No data from' in str(ctx.exception)) self.assertIn('No data from', str(ctx.exception))
def test_no_post_args(self): def test_no_post_args(self):
with self.assertRaises(ValueError) as ctx: with self.assertRaises(ValueError) as ctx:
_get_json_for_fragment( _get_request_parameters_for_fragment(
{'node-url': 'http://localhost:1234'}, {'node-url': 'http://localhost:1234'},
'/fragment', '/fragment',
method='POST', method='POST',
post_args=None, post_args=None,
) )
self.assertTrue( self.assertIn(
"Must pass post_args" in str(ctx.exception) "Must pass post_args",
str(ctx.exception),
) )
def test_post_args_for_get(self): def test_post_args_for_get(self):
with self.assertRaises(ValueError) as ctx: with self.assertRaises(ValueError) as ctx:
_get_json_for_fragment( _get_request_parameters_for_fragment(
{'node-url': 'http://localhost:1234'}, {'node-url': 'http://localhost:1234'},
'/fragment', '/fragment',
method='GET', method='GET',
post_args={'foo': 'bar'} post_args={'foo': 'bar'}
) )
self.assertTrue( self.assertIn(
"only valid for POST" in str(ctx.exception) "only valid for POST",
str(ctx.exception),
) )

View File

@ -74,6 +74,8 @@ commands =
tahoe --version tahoe --version
python -c "import sys; print('sys.stdout.encoding:', sys.stdout.encoding)"
# Run tests with -b to catch bugs like `"%s" % (some_bytes,)`. -b makes # Run tests with -b to catch bugs like `"%s" % (some_bytes,)`. -b makes
# Python emit BytesWarnings, and warnings configuration in # Python emit BytesWarnings, and warnings configuration in
# src/allmydata/tests/__init__.py turns allmydata's BytesWarnings into # src/allmydata/tests/__init__.py turns allmydata's BytesWarnings into