| 1 | """ |
|---|
| 2 | Ported to Python 3. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | import os |
|---|
| 6 | import tempfile |
|---|
| 7 | from io import BytesIO, StringIO |
|---|
| 8 | from os.path import join |
|---|
| 9 | |
|---|
| 10 | from twisted.trial import unittest |
|---|
| 11 | from twisted.internet import defer |
|---|
| 12 | |
|---|
| 13 | from allmydata.mutable.publish import MutableData |
|---|
| 14 | from allmydata.scripts.common_http import BadResponse |
|---|
| 15 | from allmydata.scripts.tahoe_status import _handle_response_for_fragment |
|---|
| 16 | from allmydata.scripts.tahoe_status import _get_request_parameters_for_fragment |
|---|
| 17 | from allmydata.scripts.tahoe_status import pretty_progress |
|---|
| 18 | from allmydata.scripts.tahoe_status import do_status |
|---|
| 19 | from allmydata.web.status import marshal_json |
|---|
| 20 | |
|---|
| 21 | from allmydata.immutable.upload import UploadStatus |
|---|
| 22 | from allmydata.immutable.downloader.status import DownloadStatus |
|---|
| 23 | from allmydata.mutable.publish import PublishStatus |
|---|
| 24 | from allmydata.mutable.retrieve import RetrieveStatus |
|---|
| 25 | from allmydata.mutable.servermap import UpdateStatus |
|---|
| 26 | from allmydata.util import jsonbytes as json |
|---|
| 27 | |
|---|
| 28 | from ..no_network import GridTestMixin |
|---|
| 29 | from ..common_web import do_http |
|---|
| 30 | from .common import CLITestMixin |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 33 | class FakeStatus: |
|---|
| 34 | def __init__(self): |
|---|
| 35 | self.status = [] |
|---|
| 36 | |
|---|
| 37 | def setServiceParent(self, p): |
|---|
| 38 | pass |
|---|
| 39 | |
|---|
| 40 | def get_status(self): |
|---|
| 41 | return self.status |
|---|
| 42 | |
|---|
| 43 | def get_storage_index(self): |
|---|
| 44 | return None |
|---|
| 45 | |
|---|
| 46 | def get_size(self): |
|---|
| 47 | return None |
|---|
| 48 | |
|---|
| 49 | |
|---|
| 50 | class ProgressBar(unittest.TestCase): |
|---|
| 51 | |
|---|
| 52 | def test_ascii0(self): |
|---|
| 53 | prog = pretty_progress(80.0, size=10, output_ascii=True) |
|---|
| 54 | self.assertEqual('########. ', prog) |
|---|
| 55 | |
|---|
| 56 | def test_ascii1(self): |
|---|
| 57 | prog = pretty_progress(10.0, size=10, output_ascii=True) |
|---|
| 58 | self.assertEqual('#. ', prog) |
|---|
| 59 | |
|---|
| 60 | def test_ascii2(self): |
|---|
| 61 | prog = pretty_progress(13.0, size=10, output_ascii=True) |
|---|
| 62 | self.assertEqual('#o ', prog) |
|---|
| 63 | |
|---|
| 64 | def test_ascii3(self): |
|---|
| 65 | prog = pretty_progress(90.0, size=10, output_ascii=True) |
|---|
| 66 | self.assertEqual('#########.', prog) |
|---|
| 67 | |
|---|
| 68 | def test_unicode0(self): |
|---|
| 69 | self.assertEqual( |
|---|
| 70 | pretty_progress(82.0, size=10, output_ascii=False), |
|---|
| 71 | u'\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258e ', |
|---|
| 72 | ) |
|---|
| 73 | |
|---|
| 74 | def test_unicode1(self): |
|---|
| 75 | self.assertEqual( |
|---|
| 76 | pretty_progress(100.0, size=10, output_ascii=False), |
|---|
| 77 | u'\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588', |
|---|
| 78 | ) |
|---|
| 79 | |
|---|
| 80 | |
|---|
| 81 | class _FakeOptions(dict): |
|---|
| 82 | def __init__(self): |
|---|
| 83 | self._tmp = tempfile.mkdtemp() |
|---|
| 84 | os.mkdir(join(self._tmp, 'private'), 0o777) |
|---|
| 85 | with open(join(self._tmp, 'private', 'api_auth_token'), 'w') as f: |
|---|
| 86 | f.write('a' * 32) |
|---|
| 87 | with open(join(self._tmp, 'node.url'), 'w') as f: |
|---|
| 88 | f.write('localhost:9000') |
|---|
| 89 | |
|---|
| 90 | self['node-directory'] = self._tmp |
|---|
| 91 | self['verbose'] = True |
|---|
| 92 | self.stdout = StringIO() |
|---|
| 93 | self.stderr = StringIO() |
|---|
| 94 | |
|---|
| 95 | |
|---|
| 96 | class Integration(GridTestMixin, CLITestMixin, unittest.TestCase): |
|---|
| 97 | |
|---|
| 98 | @defer.inlineCallbacks |
|---|
| 99 | def setUp(self): |
|---|
| 100 | yield super(Integration, self).setUp() |
|---|
| 101 | self.basedir = "cli/status" |
|---|
| 102 | self.set_up_grid() |
|---|
| 103 | |
|---|
| 104 | # upload something |
|---|
| 105 | c0 = self.g.clients[0] |
|---|
| 106 | data = MutableData(b"data" * 100) |
|---|
| 107 | filenode = yield c0.create_mutable_file(data) |
|---|
| 108 | self.uri = filenode.get_uri() |
|---|
| 109 | |
|---|
| 110 | # make sure our web-port is actually answering |
|---|
| 111 | yield do_http("get", 'http://127.0.0.1:{}/status?t=json'.format(self.client_webports[0])) |
|---|
| 112 | |
|---|
| 113 | def test_simple(self): |
|---|
| 114 | d = self.do_cli('status')# '--verbose') |
|---|
| 115 | |
|---|
| 116 | def _check(ign): |
|---|
| 117 | code, stdout, stderr = ign |
|---|
| 118 | self.assertEqual(code, 0, stderr) |
|---|
| 119 | self.assertTrue('Skipped 1' in stdout) |
|---|
| 120 | d.addCallback(_check) |
|---|
| 121 | return d |
|---|
| 122 | |
|---|
| 123 | @defer.inlineCallbacks |
|---|
| 124 | def test_help(self): |
|---|
| 125 | rc, _, _ = yield self.do_cli('status', '--help') |
|---|
| 126 | self.assertEqual(rc, 0) |
|---|
| 127 | |
|---|
| 128 | |
|---|
| 129 | class CommandStatus(unittest.TestCase): |
|---|
| 130 | """ |
|---|
| 131 | These tests just exercise the renderers and ensure they don't |
|---|
| 132 | catastrophically fail. |
|---|
| 133 | """ |
|---|
| 134 | |
|---|
| 135 | def setUp(self): |
|---|
| 136 | self.options = _FakeOptions() |
|---|
| 137 | |
|---|
| 138 | def test_no_operations(self): |
|---|
| 139 | values = [ |
|---|
| 140 | StringIO(json.dumps({ |
|---|
| 141 | "active": [], |
|---|
| 142 | "recent": [], |
|---|
| 143 | })), |
|---|
| 144 | StringIO(json.dumps({ |
|---|
| 145 | "counters": { |
|---|
| 146 | "bytes_downloaded": 0, |
|---|
| 147 | }, |
|---|
| 148 | "stats": { |
|---|
| 149 | "node.uptime": 0, |
|---|
| 150 | } |
|---|
| 151 | })), |
|---|
| 152 | ] |
|---|
| 153 | def do_http(*args, **kw): |
|---|
| 154 | return values.pop(0) |
|---|
| 155 | do_status(self.options, do_http) |
|---|
| 156 | |
|---|
| 157 | def test_simple(self): |
|---|
| 158 | recent_items = active_items = [ |
|---|
| 159 | UploadStatus(), |
|---|
| 160 | DownloadStatus(b"abcd", 12345), |
|---|
| 161 | PublishStatus(), |
|---|
| 162 | RetrieveStatus(), |
|---|
| 163 | UpdateStatus(), |
|---|
| 164 | FakeStatus(), |
|---|
| 165 | ] |
|---|
| 166 | values = [ |
|---|
| 167 | BytesIO(json.dumps({ |
|---|
| 168 | "active": list( |
|---|
| 169 | marshal_json(item) |
|---|
| 170 | for item |
|---|
| 171 | in active_items |
|---|
| 172 | ), |
|---|
| 173 | "recent": list( |
|---|
| 174 | marshal_json(item) |
|---|
| 175 | for item |
|---|
| 176 | in recent_items |
|---|
| 177 | ), |
|---|
| 178 | }).encode("utf-8")), |
|---|
| 179 | BytesIO(json.dumps({ |
|---|
| 180 | "counters": { |
|---|
| 181 | "bytes_downloaded": 0, |
|---|
| 182 | }, |
|---|
| 183 | "stats": { |
|---|
| 184 | "node.uptime": 0, |
|---|
| 185 | } |
|---|
| 186 | }).encode("utf-8")), |
|---|
| 187 | ] |
|---|
| 188 | def do_http(*args, **kw): |
|---|
| 189 | return values.pop(0) |
|---|
| 190 | do_status(self.options, do_http) |
|---|
| 191 | |
|---|
| 192 | def test_fetch_error(self): |
|---|
| 193 | def do_http(*args, **kw): |
|---|
| 194 | raise RuntimeError("boom") |
|---|
| 195 | do_status(self.options, do_http) |
|---|
| 196 | |
|---|
| 197 | |
|---|
| 198 | class JsonHelpers(unittest.TestCase): |
|---|
| 199 | |
|---|
| 200 | def test_bad_response(self): |
|---|
| 201 | def do_http(*args, **kw): |
|---|
| 202 | return |
|---|
| 203 | with self.assertRaises(RuntimeError) as ctx: |
|---|
| 204 | _handle_response_for_fragment( |
|---|
| 205 | BadResponse('the url', 'some err'), |
|---|
| 206 | 'http://localhost:1234', |
|---|
| 207 | ) |
|---|
| 208 | self.assertIn( |
|---|
| 209 | "Failed to get", |
|---|
| 210 | str(ctx.exception), |
|---|
| 211 | ) |
|---|
| 212 | |
|---|
| 213 | def test_happy_path(self): |
|---|
| 214 | resp = _handle_response_for_fragment( |
|---|
| 215 | StringIO('{"some": "json"}'), |
|---|
| 216 | 'http://localhost:1234/', |
|---|
| 217 | ) |
|---|
| 218 | self.assertEqual(resp, dict(some='json')) |
|---|
| 219 | |
|---|
| 220 | def test_happy_path_post(self): |
|---|
| 221 | resp = _handle_response_for_fragment( |
|---|
| 222 | StringIO('{"some": "json"}'), |
|---|
| 223 | 'http://localhost:1234/', |
|---|
| 224 | ) |
|---|
| 225 | self.assertEqual(resp, dict(some='json')) |
|---|
| 226 | |
|---|
| 227 | def test_no_data_returned(self): |
|---|
| 228 | with self.assertRaises(RuntimeError) as ctx: |
|---|
| 229 | _handle_response_for_fragment(StringIO('null'), 'http://localhost:1234') |
|---|
| 230 | self.assertIn('No data from', str(ctx.exception)) |
|---|
| 231 | |
|---|
| 232 | def test_no_post_args(self): |
|---|
| 233 | with self.assertRaises(ValueError) as ctx: |
|---|
| 234 | _get_request_parameters_for_fragment( |
|---|
| 235 | {'node-url': 'http://localhost:1234'}, |
|---|
| 236 | '/fragment', |
|---|
| 237 | method='POST', |
|---|
| 238 | post_args=None, |
|---|
| 239 | ) |
|---|
| 240 | self.assertIn( |
|---|
| 241 | "Must pass post_args", |
|---|
| 242 | str(ctx.exception), |
|---|
| 243 | ) |
|---|
| 244 | |
|---|
| 245 | def test_post_args_for_get(self): |
|---|
| 246 | with self.assertRaises(ValueError) as ctx: |
|---|
| 247 | _get_request_parameters_for_fragment( |
|---|
| 248 | {'node-url': 'http://localhost:1234'}, |
|---|
| 249 | '/fragment', |
|---|
| 250 | method='GET', |
|---|
| 251 | post_args={'foo': 'bar'} |
|---|
| 252 | ) |
|---|
| 253 | self.assertIn( |
|---|
| 254 | "only valid for POST", |
|---|
| 255 | str(ctx.exception), |
|---|
| 256 | ) |
|---|