/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_http.py

  • Committer: Vincent Ladeuil
  • Date: 2011-08-16 13:12:40 UTC
  • mfrom: (6071 +trunk)
  • mto: This revision was merged to the branch mainline in revision 6076.
  • Revision ID: v.ladeuil+lp@free.fr-20110816131240-gcyn9cik86dxwgz3
Merge into trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
32
32
import bzrlib
33
33
from bzrlib import (
34
34
    bzrdir,
 
35
    cethread,
35
36
    config,
 
37
    debug,
36
38
    errors,
37
39
    osutils,
38
40
    remote as _mod_remote,
39
41
    tests,
 
42
    trace,
40
43
    transport,
41
44
    ui,
42
45
    )
90
93
        ]
91
94
 
92
95
 
93
 
def vary_by_http_proxy_auth_scheme():
94
 
    return [
95
 
        ('basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
96
 
        ('digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
97
 
        ('basicdigest',
98
 
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
99
 
        ]
100
 
 
101
 
 
102
96
def vary_by_http_auth_scheme():
103
 
    return [
 
97
    scenarios = [
104
98
        ('basic', dict(_auth_server=http_utils.HTTPBasicAuthServer)),
105
99
        ('digest', dict(_auth_server=http_utils.HTTPDigestAuthServer)),
106
100
        ('basicdigest',
107
101
            dict(_auth_server=http_utils.HTTPBasicAndDigestAuthServer)),
108
102
        ]
 
103
    # Add some attributes common to all scenarios
 
104
    for scenario_id, scenario_dict in scenarios:
 
105
        scenario_dict.update(_auth_header='Authorization',
 
106
                             _username_prompt_prefix='',
 
107
                             _password_prompt_prefix='')
 
108
    return scenarios
 
109
 
 
110
 
 
111
def vary_by_http_proxy_auth_scheme():
 
112
    scenarios = [
 
113
        ('proxy-basic', dict(_auth_server=http_utils.ProxyBasicAuthServer)),
 
114
        ('proxy-digest', dict(_auth_server=http_utils.ProxyDigestAuthServer)),
 
115
        ('proxy-basicdigest',
 
116
            dict(_auth_server=http_utils.ProxyBasicAndDigestAuthServer)),
 
117
        ]
 
118
    # Add some attributes common to all scenarios
 
119
    for scenario_id, scenario_dict in scenarios:
 
120
        scenario_dict.update(_auth_header='Proxy-Authorization',
 
121
                             _username_prompt_prefix='Proxy ',
 
122
                             _password_prompt_prefix='Proxy ')
 
123
    return scenarios
109
124
 
110
125
 
111
126
def vary_by_http_activity():
113
128
        ('urllib,http', dict(_activity_server=ActivityHTTPServer,
114
129
                            _transport=_urllib.HttpTransport_urllib,)),
115
130
        ]
116
 
    if tests.HTTPSServerFeature.available():
 
131
    if features.HTTPSServerFeature.available():
117
132
        activity_scenarios.append(
118
133
            ('urllib,https', dict(_activity_server=ActivityHTTPSServer,
119
134
                                _transport=_urllib.HttpTransport_urllib,)),)
121
136
        activity_scenarios.append(
122
137
            ('pycurl,http', dict(_activity_server=ActivityHTTPServer,
123
138
                                _transport=PyCurlTransport,)),)
124
 
        if tests.HTTPSServerFeature.available():
 
139
        if features.HTTPSServerFeature.available():
125
140
            from bzrlib.tests import (
126
141
                ssl_certs,
127
142
                )
178
193
        self._sock.bind(('127.0.0.1', 0))
179
194
        self.host, self.port = self._sock.getsockname()
180
195
        self._ready = threading.Event()
181
 
        self._thread = test_server.ThreadWithException(
182
 
            event=self._ready, target=self._accept_read_and_reply)
 
196
        self._thread = test_server.TestThread(
 
197
            sync_event=self._ready, target=self._accept_read_and_reply)
183
198
        self._thread.start()
184
199
        if 'threads' in tests.selftest_debug_flags:
185
200
            sys.stderr.write('Thread started: %s\n' % (self._thread.ident,))
254
269
        self.assertEqual('realm="Thou should not pass"', remainder)
255
270
 
256
271
 
 
272
class TestHTTPRangeParsing(tests.TestCase):
 
273
 
 
274
    def setUp(self):
 
275
        super(TestHTTPRangeParsing, self).setUp()
 
276
        # We focus on range  parsing here and ignore everything else
 
277
        class RequestHandler(http_server.TestingHTTPRequestHandler):
 
278
            def setup(self): pass
 
279
            def handle(self): pass
 
280
            def finish(self): pass
 
281
 
 
282
        self.req_handler = RequestHandler(None, None, None)
 
283
 
 
284
    def assertRanges(self, ranges, header, file_size):
 
285
        self.assertEquals(ranges,
 
286
                          self.req_handler._parse_ranges(header, file_size))
 
287
 
 
288
    def test_simple_range(self):
 
289
        self.assertRanges([(0,2)], 'bytes=0-2', 12)
 
290
 
 
291
    def test_tail(self):
 
292
        self.assertRanges([(8, 11)], 'bytes=-4', 12)
 
293
 
 
294
    def test_tail_bigger_than_file(self):
 
295
        self.assertRanges([(0, 11)], 'bytes=-99', 12)
 
296
 
 
297
    def test_range_without_end(self):
 
298
        self.assertRanges([(4, 11)], 'bytes=4-', 12)
 
299
 
 
300
    def test_invalid_ranges(self):
 
301
        self.assertRanges(None, 'bytes=12-22', 12)
 
302
        self.assertRanges(None, 'bytes=1-3,12-22', 12)
 
303
        self.assertRanges(None, 'bytes=-', 12)
 
304
 
 
305
 
257
306
class TestHTTPServer(tests.TestCase):
258
307
    """Test the HTTP servers implementations."""
259
308
 
427
476
    """Test the http connections."""
428
477
 
429
478
    scenarios = multiply_scenarios(
430
 
        vary_by_http_client_implementation(), 
 
479
        vary_by_http_client_implementation(),
431
480
        vary_by_http_protocol_version(),
432
481
        )
433
482
 
492
541
class TestPost(tests.TestCase):
493
542
 
494
543
    scenarios = multiply_scenarios(
495
 
        vary_by_http_client_implementation(), 
 
544
        vary_by_http_client_implementation(),
496
545
        vary_by_http_protocol_version(),
497
546
        )
498
547
 
551
600
    """
552
601
 
553
602
    scenarios = multiply_scenarios(
554
 
        vary_by_http_client_implementation(), 
 
603
        vary_by_http_client_implementation(),
555
604
        vary_by_http_protocol_version(),
556
605
        )
557
606
 
999
1048
        self.assertEqual('single', t._range_hint)
1000
1049
 
1001
1050
 
 
1051
class TruncatedBeforeBoundaryRequestHandler(
 
1052
    http_server.TestingHTTPRequestHandler):
 
1053
    """Truncation before a boundary, like in bug 198646"""
 
1054
 
 
1055
    _truncated_ranges = 1
 
1056
 
 
1057
    def get_multiple_ranges(self, file, file_size, ranges):
 
1058
        self.send_response(206)
 
1059
        self.send_header('Accept-Ranges', 'bytes')
 
1060
        boundary = 'tagada'
 
1061
        self.send_header('Content-Type',
 
1062
                         'multipart/byteranges; boundary=%s' % boundary)
 
1063
        boundary_line = '--%s\r\n' % boundary
 
1064
        # Calculate the Content-Length
 
1065
        content_length = 0
 
1066
        for (start, end) in ranges:
 
1067
            content_length += len(boundary_line)
 
1068
            content_length += self._header_line_length(
 
1069
                'Content-type', 'application/octet-stream')
 
1070
            content_length += self._header_line_length(
 
1071
                'Content-Range', 'bytes %d-%d/%d' % (start, end, file_size))
 
1072
            content_length += len('\r\n') # end headers
 
1073
            content_length += end - start # + 1
 
1074
        content_length += len(boundary_line)
 
1075
        self.send_header('Content-length', content_length)
 
1076
        self.end_headers()
 
1077
 
 
1078
        # Send the multipart body
 
1079
        cur = 0
 
1080
        for (start, end) in ranges:
 
1081
            if cur + self._truncated_ranges >= len(ranges):
 
1082
                # Abruptly ends the response and close the connection
 
1083
                self.close_connection = 1
 
1084
                return
 
1085
            self.wfile.write(boundary_line)
 
1086
            self.send_header('Content-type', 'application/octet-stream')
 
1087
            self.send_header('Content-Range', 'bytes %d-%d/%d'
 
1088
                             % (start, end, file_size))
 
1089
            self.end_headers()
 
1090
            self.send_range_content(file, start, end - start + 1)
 
1091
            cur += 1
 
1092
        # Final boundary
 
1093
        self.wfile.write(boundary_line)
 
1094
 
 
1095
 
 
1096
class TestTruncatedBeforeBoundary(TestSpecificRequestHandler):
 
1097
    """Tests the case of bug 198646, disconnecting before a boundary."""
 
1098
 
 
1099
    _req_handler_class = TruncatedBeforeBoundaryRequestHandler
 
1100
 
 
1101
    def setUp(self):
 
1102
        super(TestTruncatedBeforeBoundary, self).setUp()
 
1103
        self.build_tree_contents([('a', '0123456789')],)
 
1104
 
 
1105
    def test_readv_with_short_reads(self):
 
1106
        server = self.get_readonly_server()
 
1107
        t = self.get_readonly_transport()
 
1108
        # Force separate ranges for each offset
 
1109
        t._bytes_to_read_before_seek = 0
 
1110
        ireadv = iter(t.readv('a', ((0, 1), (2, 1), (4, 2), (9, 1))))
 
1111
        self.assertEqual((0, '0'), ireadv.next())
 
1112
        self.assertEqual((2, '2'), ireadv.next())
 
1113
        self.assertEqual((4, '45'), ireadv.next())
 
1114
        self.assertEqual((9, '9'), ireadv.next())
 
1115
 
 
1116
 
1002
1117
class LimitedRangeRequestHandler(http_server.TestingHTTPRequestHandler):
1003
1118
    """Errors out when range specifiers exceed the limit"""
1004
1119
 
1029
1144
    """Tests readv requests against a server erroring out on too much ranges."""
1030
1145
 
1031
1146
    scenarios = multiply_scenarios(
1032
 
        vary_by_http_client_implementation(), 
 
1147
        vary_by_http_client_implementation(),
1033
1148
        vary_by_http_protocol_version(),
1034
1149
        )
1035
1150
 
1075
1190
 
1076
1191
    def _proxied_request(self):
1077
1192
        handler = _urllib2_wrappers.ProxyHandler()
1078
 
        request = _urllib2_wrappers.Request('GET','http://baz/buzzle')
 
1193
        request = _urllib2_wrappers.Request('GET', 'http://baz/buzzle')
1079
1194
        handler.set_proxy(request, 'http')
1080
1195
        return request
1081
1196
 
 
1197
    def assertEvaluateProxyBypass(self, expected, host, no_proxy):
 
1198
        handler = _urllib2_wrappers.ProxyHandler()
 
1199
        self.assertEquals(expected,
 
1200
                          handler.evaluate_proxy_bypass(host, no_proxy))
 
1201
 
1082
1202
    def test_empty_user(self):
1083
1203
        self.overrideEnv('http_proxy', 'http://bar.com')
1084
1204
        request = self._proxied_request()
1085
1205
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
1086
1206
 
 
1207
    def test_user_with_at(self):
 
1208
        self.overrideEnv('http_proxy',
 
1209
                         'http://username@domain:password@proxy_host:1234')
 
1210
        request = self._proxied_request()
 
1211
        self.assertFalse(request.headers.has_key('Proxy-authorization'))
 
1212
 
1087
1213
    def test_invalid_proxy(self):
1088
1214
        """A proxy env variable without scheme"""
1089
1215
        self.overrideEnv('http_proxy', 'host:1234')
1090
1216
        self.assertRaises(errors.InvalidURL, self._proxied_request)
1091
1217
 
 
1218
    def test_evaluate_proxy_bypass_true(self):
 
1219
        """The host is not proxied"""
 
1220
        self.assertEvaluateProxyBypass(True, 'example.com', 'example.com')
 
1221
        self.assertEvaluateProxyBypass(True, 'bzr.example.com', '*example.com')
 
1222
 
 
1223
    def test_evaluate_proxy_bypass_false(self):
 
1224
        """The host is proxied"""
 
1225
        self.assertEvaluateProxyBypass(False, 'bzr.example.com', None)
 
1226
 
 
1227
    def test_evaluate_proxy_bypass_unknown(self):
 
1228
        """The host is not explicitly proxied"""
 
1229
        self.assertEvaluateProxyBypass(None, 'example.com', 'not.example.com')
 
1230
        self.assertEvaluateProxyBypass(None, 'bzr.example.com', 'example.com')
 
1231
 
 
1232
    def test_evaluate_proxy_bypass_empty_entries(self):
 
1233
        """Ignore empty entries"""
 
1234
        self.assertEvaluateProxyBypass(None, 'example.com', '')
 
1235
        self.assertEvaluateProxyBypass(None, 'example.com', ',')
 
1236
        self.assertEvaluateProxyBypass(None, 'example.com', 'foo,,bar')
 
1237
 
1092
1238
 
1093
1239
class TestProxyHttpServer(http_utils.TestCaseWithTwoWebservers):
1094
1240
    """Tests proxy server.
1100
1246
    """
1101
1247
 
1102
1248
    scenarios = multiply_scenarios(
1103
 
        vary_by_http_client_implementation(), 
 
1249
        vary_by_http_client_implementation(),
1104
1250
        vary_by_http_protocol_version(),
1105
1251
        )
1106
1252
 
1197
1343
    """Test the Range header in GET methods."""
1198
1344
 
1199
1345
    scenarios = multiply_scenarios(
1200
 
        vary_by_http_client_implementation(), 
 
1346
        vary_by_http_client_implementation(),
1201
1347
        vary_by_http_protocol_version(),
1202
1348
        )
1203
1349
 
1247
1393
    """Test redirection between http servers."""
1248
1394
 
1249
1395
    scenarios = multiply_scenarios(
1250
 
        vary_by_http_client_implementation(), 
 
1396
        vary_by_http_client_implementation(),
1251
1397
        vary_by_http_protocol_version(),
1252
1398
        )
1253
1399
 
1320
1466
    """
1321
1467
 
1322
1468
    scenarios = multiply_scenarios(
1323
 
        vary_by_http_client_implementation(), 
 
1469
        vary_by_http_client_implementation(),
1324
1470
        vary_by_http_protocol_version(),
1325
1471
        )
1326
1472
 
1375
1521
    """Test transport.do_catching_redirections."""
1376
1522
 
1377
1523
    scenarios = multiply_scenarios(
1378
 
        vary_by_http_client_implementation(), 
 
1524
        vary_by_http_client_implementation(),
1379
1525
        vary_by_http_protocol_version(),
1380
1526
        )
1381
1527
 
1423
1569
                          self.get_a, self.old_transport, redirected)
1424
1570
 
1425
1571
 
 
1572
def _setup_authentication_config(**kwargs):
 
1573
    conf = config.AuthenticationConfig()
 
1574
    conf._get_config().update({'httptest': kwargs})
 
1575
    conf._save()
 
1576
 
 
1577
 
 
1578
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
 
1579
    """Unit tests for glue by which urllib2 asks us for authentication"""
 
1580
 
 
1581
    def test_get_user_password_without_port(self):
 
1582
        """We cope if urllib2 doesn't tell us the port.
 
1583
 
 
1584
        See https://bugs.launchpad.net/bzr/+bug/654684
 
1585
        """
 
1586
        user = 'joe'
 
1587
        password = 'foo'
 
1588
        _setup_authentication_config(scheme='http', host='localhost',
 
1589
                                     user=user, password=password)
 
1590
        handler = _urllib2_wrappers.HTTPAuthHandler()
 
1591
        got_pass = handler.get_user_password(dict(
 
1592
            user='joe',
 
1593
            protocol='http',
 
1594
            host='localhost',
 
1595
            path='/',
 
1596
            realm='Realm',
 
1597
            ))
 
1598
        self.assertEquals((user, password), got_pass)
 
1599
 
 
1600
 
1426
1601
class TestAuth(http_utils.TestCaseWithWebserver):
1427
1602
    """Test authentication scheme"""
1428
1603
 
1432
1607
        vary_by_http_auth_scheme(),
1433
1608
        )
1434
1609
 
1435
 
    _auth_header = 'Authorization'
1436
 
    _password_prompt_prefix = ''
1437
 
    _username_prompt_prefix = ''
1438
 
    # Set by load_tests
1439
 
    _auth_server = None
1440
 
 
1441
1610
    def setUp(self):
1442
1611
        super(TestAuth, self).setUp()
1443
1612
        self.server = self.get_readonly_server()
1584
1753
        ui.ui_factory = tests.TestUIFactory(stdin=stdin_content,
1585
1754
                                            stderr=tests.StringIOWrapper())
1586
1755
        # Create a minimal config file with the right password
1587
 
        _setup_authentication_config(
1588
 
            scheme='http', 
1589
 
            port=self.server.port,
1590
 
            user=user,
1591
 
            password=password)
 
1756
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1757
                                     user=user, password=password)
1592
1758
        # Issue a request to the server to connect
1593
1759
        self.assertEqual('contents of a\n',t.get('a').read())
1594
1760
        # stdin should have  been left untouched
1601
1767
                                     http_utils.ProxyDigestAuthServer):
1602
1768
            raise tests.TestNotApplicable('HTTP/proxy auth digest only test')
1603
1769
        if self._testing_pycurl():
1604
 
            raise tests.KnownFailure(
 
1770
            self.knownFailure(
1605
1771
                'pycurl does not handle a nonce change')
1606
1772
        self.server.add_user('joe', 'foo')
1607
1773
        t = self.get_user_transport('joe', 'foo')
1624
1790
        user = 'joe'
1625
1791
        password = 'foo'
1626
1792
        self.server.add_user(user, password)
1627
 
        _setup_authentication_config(
1628
 
            scheme='http', 
1629
 
            port=self.server.port,
1630
 
            user=user,
1631
 
            password=password)
 
1793
        _setup_authentication_config(scheme='http', port=self.server.port,
 
1794
                                     user=user, password=password)
1632
1795
        t = self.get_user_transport(None, None)
1633
1796
        # Issue a request to the server to connect
1634
1797
        self.assertEqual('contents of a\n', t.get('a').read())
1635
1798
        # Only one 'Authentication Required' error should occur
1636
1799
        self.assertEqual(1, self.server.auth_required_errors)
1637
1800
 
1638
 
 
1639
 
def _setup_authentication_config(**kwargs):
1640
 
    conf = config.AuthenticationConfig()
1641
 
    conf._get_config().update({'httptest': kwargs})
1642
 
    conf._save()
1643
 
 
1644
 
 
1645
 
 
1646
 
class TestUrllib2AuthHandler(tests.TestCaseWithTransport):
1647
 
    """Unit tests for glue by which urllib2 asks us for authentication"""
1648
 
 
1649
 
    def test_get_user_password_without_port(self):
1650
 
        """We cope if urllib2 doesn't tell us the port.
1651
 
 
1652
 
        See https://bugs.launchpad.net/bzr/+bug/654684
1653
 
        """
 
1801
    def test_no_credential_leaks_in_log(self):
 
1802
        self.overrideAttr(debug, 'debug_flags', set(['http']))
1654
1803
        user = 'joe'
1655
 
        password = 'foo'
1656
 
        _setup_authentication_config(
1657
 
            scheme='http', 
1658
 
            host='localhost',
1659
 
            user=user,
1660
 
            password=password)
1661
 
        handler = _urllib2_wrappers.HTTPAuthHandler()
1662
 
        got_pass = handler.get_user_password(dict(
1663
 
            user='joe',
1664
 
            protocol='http',
1665
 
            host='localhost',
1666
 
            path='/',
1667
 
            realm='Realm',
1668
 
            ))
1669
 
        self.assertEquals((user, password), got_pass)
 
1804
        password = 'very-sensitive-password'
 
1805
        self.server.add_user(user, password)
 
1806
        t = self.get_user_transport(user, password)
 
1807
        # Capture the debug calls to mutter
 
1808
        self.mutters = []
 
1809
        def mutter(*args):
 
1810
            lines = args[0] % args[1:]
 
1811
            # Some calls output multiple lines, just split them now since we
 
1812
            # care about a single one later.
 
1813
            self.mutters.extend(lines.splitlines())
 
1814
        self.overrideAttr(trace, 'mutter', mutter)
 
1815
        # Issue a request to the server to connect
 
1816
        self.assertEqual(True, t.has('a'))
 
1817
        # Only one 'Authentication Required' error should occur
 
1818
        self.assertEqual(1, self.server.auth_required_errors)
 
1819
        # Since the authentification succeeded, there should be a corresponding
 
1820
        # debug line
 
1821
        sent_auth_headers = [line for line in self.mutters
 
1822
                             if line.startswith('> %s' % (self._auth_header,))]
 
1823
        self.assertLength(1, sent_auth_headers)
 
1824
        self.assertStartsWith(sent_auth_headers[0],
 
1825
                              '> %s: <masked>' % (self._auth_header,))
1670
1826
 
1671
1827
 
1672
1828
class TestProxyAuth(TestAuth):
1673
 
    """Test proxy authentication schemes."""
 
1829
    """Test proxy authentication schemes.
 
1830
 
 
1831
    This inherits from TestAuth to tweak the setUp and filter some failing
 
1832
    tests.
 
1833
    """
1674
1834
 
1675
1835
    scenarios = multiply_scenarios(
1676
1836
        vary_by_http_client_implementation(),
1678
1838
        vary_by_http_proxy_auth_scheme(),
1679
1839
        )
1680
1840
 
1681
 
    _auth_header = 'Proxy-authorization'
1682
 
    _password_prompt_prefix = 'Proxy '
1683
 
    _username_prompt_prefix = 'Proxy '
1684
 
 
1685
1841
    def setUp(self):
1686
1842
        super(TestProxyAuth, self).setUp()
1687
1843
        # Override the contents to avoid false positives
1699
1855
        if self._testing_pycurl():
1700
1856
            import pycurl
1701
1857
            if pycurl.version_info()[1] < '7.16.0':
1702
 
                raise tests.KnownFailure(
 
1858
                self.knownFailure(
1703
1859
                    'pycurl < 7.16.0 does not handle empty proxy passwords')
1704
1860
        super(TestProxyAuth, self).test_empty_pass()
1705
1861
 
1730
1886
class SmartHTTPTunnellingTest(tests.TestCaseWithTransport):
1731
1887
 
1732
1888
    scenarios = multiply_scenarios(
1733
 
        vary_by_http_client_implementation(), 
 
1889
        vary_by_http_client_implementation(),
1734
1890
        vary_by_http_protocol_version(),
1735
1891
        )
1736
1892
 
1871
2027
        r = t._redirected_to('http://www.example.com/foo',
1872
2028
                             'https://foo.example.com/foo')
1873
2029
        self.assertIsInstance(r, type(t))
1874
 
        self.assertEqual(t._user, r._user)
 
2030
        self.assertEqual(t._parsed_url.user, r._parsed_url.user)
1875
2031
 
1876
2032
 
1877
2033
class PredefinedRequestHandler(http_server.TestingHTTPRequestHandler):
1930
2086
    pass
1931
2087
 
1932
2088
 
1933
 
if tests.HTTPSServerFeature.available():
 
2089
if features.HTTPSServerFeature.available():
1934
2090
    from bzrlib.tests import https_server
1935
2091
    class ActivityHTTPSServer(ActivityServerMixin, https_server.HTTPSServer):
1936
2092
        pass
1947
2103
        tests.TestCase.setUp(self)
1948
2104
        self.server = self._activity_server(self._protocol_version)
1949
2105
        self.server.start_server()
1950
 
        self.activities = {}
 
2106
        _activities = {} # Don't close over self and create a cycle
1951
2107
        def report_activity(t, bytes, direction):
1952
 
            count = self.activities.get(direction, 0)
 
2108
            count = _activities.get(direction, 0)
1953
2109
            count += bytes
1954
 
            self.activities[direction] = count
 
2110
            _activities[direction] = count
 
2111
        self.activities = _activities
1955
2112
 
1956
2113
        # We override at class level because constructors may propagate the
1957
2114
        # bound method and render instance overriding ineffective (an
2182
2339
        # stdout should be empty, stderr will contains the prompts
2183
2340
        self.assertEqual('', stdout.getvalue())
2184
2341
 
2185