/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_remote.py

  • Committer: Martin
  • Date: 2010-05-25 17:27:52 UTC
  • mfrom: (5254 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5257.
  • Revision ID: gzlist@googlemail.com-20100525172752-amm089xcikv968sw
Merge bzr.dev to unite with similar changes already made

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
27
27
from cStringIO import StringIO
28
28
 
29
29
from bzrlib import (
 
30
    branch,
30
31
    bzrdir,
31
32
    config,
32
33
    errors,
36
37
    pack,
37
38
    remote,
38
39
    repository,
39
 
    smart,
40
40
    tests,
41
41
    treebuilder,
42
42
    urlutils,
54
54
    )
55
55
from bzrlib.repofmt import groupcompress_repo, pack_repo
56
56
from bzrlib.revision import NULL_REVISION
57
 
from bzrlib.smart import server, medium
 
57
from bzrlib.smart import medium
58
58
from bzrlib.smart.client import _SmartClient
59
59
from bzrlib.smart.repository import SmartServerRepositoryGetParentMap
60
60
from bzrlib.tests import (
61
61
    condition_isinstance,
62
62
    split_suite_by_condition,
63
63
    multiply_tests,
64
 
    KnownFailure,
 
64
    test_server,
65
65
    )
66
 
from bzrlib.transport import get_transport, http
 
66
from bzrlib.transport import get_transport
67
67
from bzrlib.transport.memory import MemoryTransport
68
68
from bzrlib.transport.remote import (
69
69
    RemoteTransport,
76
76
        standard_tests, condition_isinstance(BasicRemoteObjectTests))
77
77
    smart_server_version_scenarios = [
78
78
        ('HPSS-v2',
79
 
            {'transport_server': server.SmartTCPServer_for_testing_v2_only}),
 
79
         {'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
80
80
        ('HPSS-v3',
81
 
            {'transport_server': server.SmartTCPServer_for_testing})]
 
81
         {'transport_server': test_server.SmartTCPServer_for_testing})]
82
82
    return multiply_tests(to_adapt, smart_server_version_scenarios, result)
83
83
 
84
84
 
135
135
        b = BzrDir.open_from_transport(self.transport).open_branch()
136
136
        self.assertStartsWith(str(b), 'RemoteBranch(')
137
137
 
 
138
    def test_remote_bzrdir_repr(self):
 
139
        b = BzrDir.open_from_transport(self.transport)
 
140
        self.assertStartsWith(str(b), 'RemoteBzrDir(')
 
141
 
138
142
    def test_remote_branch_format_supports_stacking(self):
139
143
        t = self.transport
140
144
        self.make_branch('unstackable', format='pack-0.92')
414
418
        # Calling _remember_remote_is_before again with a lower value works.
415
419
        client_medium._remember_remote_is_before((1, 5))
416
420
        self.assertTrue(client_medium._is_remote_before((1, 5)))
417
 
        # You cannot call _remember_remote_is_before with a larger value.
418
 
        self.assertRaises(
419
 
            AssertionError, client_medium._remember_remote_is_before, (1, 9))
 
421
        # If you call _remember_remote_is_before with a higher value it logs a
 
422
        # warning, and continues to remember the lower value.
 
423
        self.assertNotContainsRe(self.get_log(), '_remember_remote_is_before')
 
424
        client_medium._remember_remote_is_before((1, 9))
 
425
        self.assertContainsRe(self.get_log(), '_remember_remote_is_before')
 
426
        self.assertTrue(client_medium._is_remote_before((1, 5)))
420
427
 
421
428
 
422
429
class TestBzrDirCloningMetaDir(TestRemote):
441
448
            'BzrDir.cloning_metadir', ('quack/', 'False'),
442
449
            'error', ('BranchReference',)),
443
450
        client.add_expected_call(
444
 
            'BzrDir.open_branchV2', ('quack/',),
 
451
            'BzrDir.open_branchV3', ('quack/',),
445
452
            'success', ('ref', self.get_url('referenced'))),
446
453
        a_bzrdir = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
447
454
            _client=client)
474
481
        self.assertFinished(client)
475
482
 
476
483
 
 
484
class TestBzrDirOpen(TestRemote):
 
485
 
 
486
    def make_fake_client_and_transport(self, path='quack'):
 
487
        transport = MemoryTransport()
 
488
        transport.mkdir(path)
 
489
        transport = transport.clone(path)
 
490
        client = FakeClient(transport.base)
 
491
        return client, transport
 
492
 
 
493
    def test_absent(self):
 
494
        client, transport = self.make_fake_client_and_transport()
 
495
        client.add_expected_call(
 
496
            'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
 
497
        self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
 
498
                remote.RemoteBzrDirFormat(), _client=client, _force_probe=True)
 
499
        self.assertFinished(client)
 
500
 
 
501
    def test_present_without_workingtree(self):
 
502
        client, transport = self.make_fake_client_and_transport()
 
503
        client.add_expected_call(
 
504
            'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
 
505
        bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
506
            _client=client, _force_probe=True)
 
507
        self.assertIsInstance(bd, RemoteBzrDir)
 
508
        self.assertFalse(bd.has_workingtree())
 
509
        self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
 
510
        self.assertFinished(client)
 
511
 
 
512
    def test_present_with_workingtree(self):
 
513
        client, transport = self.make_fake_client_and_transport()
 
514
        client.add_expected_call(
 
515
            'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
 
516
        bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
517
            _client=client, _force_probe=True)
 
518
        self.assertIsInstance(bd, RemoteBzrDir)
 
519
        self.assertTrue(bd.has_workingtree())
 
520
        self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
 
521
        self.assertFinished(client)
 
522
 
 
523
    def test_backwards_compat(self):
 
524
        client, transport = self.make_fake_client_and_transport()
 
525
        client.add_expected_call(
 
526
            'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
 
527
        client.add_expected_call(
 
528
            'BzrDir.open', ('quack/',), 'success', ('yes',))
 
529
        bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
530
            _client=client, _force_probe=True)
 
531
        self.assertIsInstance(bd, RemoteBzrDir)
 
532
        self.assertFinished(client)
 
533
 
 
534
    def test_backwards_compat_hpss_v2(self):
 
535
        client, transport = self.make_fake_client_and_transport()
 
536
        # Monkey-patch fake client to simulate real-world behaviour with v2
 
537
        # server: upon first RPC call detect the protocol version, and because
 
538
        # the version is 2 also do _remember_remote_is_before((1, 6)) before
 
539
        # continuing with the RPC.
 
540
        orig_check_call = client._check_call
 
541
        def check_call(method, args):
 
542
            client._medium._protocol_version = 2
 
543
            client._medium._remember_remote_is_before((1, 6))
 
544
            client._check_call = orig_check_call
 
545
            client._check_call(method, args)
 
546
        client._check_call = check_call
 
547
        client.add_expected_call(
 
548
            'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
 
549
        client.add_expected_call(
 
550
            'BzrDir.open', ('quack/',), 'success', ('yes',))
 
551
        bd = RemoteBzrDir(transport, remote.RemoteBzrDirFormat(),
 
552
            _client=client, _force_probe=True)
 
553
        self.assertIsInstance(bd, RemoteBzrDir)
 
554
        self.assertFinished(client)
 
555
 
 
556
 
477
557
class TestBzrDirOpenBranch(TestRemote):
478
558
 
479
559
    def test_backwards_compat(self):
481
561
        self.make_branch('.')
482
562
        a_dir = BzrDir.open(self.get_url('.'))
483
563
        self.reset_smart_call_log()
484
 
        verb = 'BzrDir.open_branchV2'
 
564
        verb = 'BzrDir.open_branchV3'
485
565
        self.disable_verb(verb)
486
566
        format = a_dir.open_branch()
487
567
        call_count = len([call for call in self.hpss_calls if
497
577
        transport = transport.clone('quack')
498
578
        client = FakeClient(transport.base)
499
579
        client.add_expected_call(
500
 
            'BzrDir.open_branchV2', ('quack/',),
 
580
            'BzrDir.open_branchV3', ('quack/',),
501
581
            'success', ('branch', branch_network_name))
502
582
        client.add_expected_call(
503
583
            'BzrDir.find_repositoryV3', ('quack/',),
522
602
            _client=client)
523
603
        self.assertRaises(errors.NotBranchError, bzrdir.open_branch)
524
604
        self.assertEqual(
525
 
            [('call', 'BzrDir.open_branchV2', ('quack/',))],
 
605
            [('call', 'BzrDir.open_branchV3', ('quack/',))],
526
606
            client._calls)
527
607
 
528
608
    def test__get_tree_branch(self):
529
609
        # _get_tree_branch is a form of open_branch, but it should only ask for
530
610
        # branch opening, not any other network requests.
531
611
        calls = []
532
 
        def open_branch():
 
612
        def open_branch(name=None):
533
613
            calls.append("Called")
534
614
            return "a-branch"
535
615
        transport = MemoryTransport()
552
632
        network_name = reference_format.network_name()
553
633
        branch_network_name = self.get_branch_format().network_name()
554
634
        client.add_expected_call(
555
 
            'BzrDir.open_branchV2', ('~hello/',),
 
635
            'BzrDir.open_branchV3', ('~hello/',),
556
636
            'success', ('branch', branch_network_name))
557
637
        client.add_expected_call(
558
638
            'BzrDir.find_repositoryV3', ('~hello/',),
1140
1220
        client = FakeClient(self.get_url())
1141
1221
        branch_network_name = self.get_branch_format().network_name()
1142
1222
        client.add_expected_call(
1143
 
            'BzrDir.open_branchV2', ('stacked/',),
 
1223
            'BzrDir.open_branchV3', ('stacked/',),
1144
1224
            'success', ('branch', branch_network_name))
1145
1225
        client.add_expected_call(
1146
1226
            'BzrDir.find_repositoryV3', ('stacked/',),
1168
1248
            len(branch.repository._real_repository._fallback_repositories))
1169
1249
 
1170
1250
    def test_get_stacked_on_real_branch(self):
1171
 
        base_branch = self.make_branch('base', format='1.6')
1172
 
        stacked_branch = self.make_branch('stacked', format='1.6')
 
1251
        base_branch = self.make_branch('base')
 
1252
        stacked_branch = self.make_branch('stacked')
1173
1253
        stacked_branch.set_stacked_on_url('../base')
1174
1254
        reference_format = self.get_repo_format()
1175
1255
        network_name = reference_format.network_name()
1176
1256
        client = FakeClient(self.get_url())
1177
1257
        branch_network_name = self.get_branch_format().network_name()
1178
1258
        client.add_expected_call(
1179
 
            'BzrDir.open_branchV2', ('stacked/',),
 
1259
            'BzrDir.open_branchV3', ('stacked/',),
1180
1260
            'success', ('branch', branch_network_name))
1181
1261
        client.add_expected_call(
1182
1262
            'BzrDir.find_repositoryV3', ('stacked/',),
1183
 
            'success', ('ok', '', 'no', 'no', 'yes', network_name))
 
1263
            'success', ('ok', '', 'yes', 'no', 'yes', network_name))
1184
1264
        # called twice, once from constructor and then again by us
1185
1265
        client.add_expected_call(
1186
1266
            'Branch.get_stacked_on_url', ('stacked/',),
1538
1618
    def test_get_multi_line_branch_conf(self):
1539
1619
        # Make sure that multiple-line branch.conf files are supported
1540
1620
        #
1541
 
        # https://bugs.edge.launchpad.net/bzr/+bug/354075
 
1621
        # https://bugs.launchpad.net/bzr/+bug/354075
1542
1622
        client = FakeClient()
1543
1623
        client.add_expected_call(
1544
1624
            'Branch.get_stacked_on_url', ('memory:///',),
1572
1652
        branch.unlock()
1573
1653
        self.assertFinished(client)
1574
1654
 
 
1655
    def test_set_option_with_dict(self):
 
1656
        client = FakeClient()
 
1657
        client.add_expected_call(
 
1658
            'Branch.get_stacked_on_url', ('memory:///',),
 
1659
            'error', ('NotStacked',),)
 
1660
        client.add_expected_call(
 
1661
            'Branch.lock_write', ('memory:///', '', ''),
 
1662
            'success', ('ok', 'branch token', 'repo token'))
 
1663
        encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
 
1664
        client.add_expected_call(
 
1665
            'Branch.set_config_option_dict', ('memory:///', 'branch token',
 
1666
            'repo token', encoded_dict_value, 'foo', ''),
 
1667
            'success', ())
 
1668
        client.add_expected_call(
 
1669
            'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
 
1670
            'success', ('ok',))
 
1671
        transport = MemoryTransport()
 
1672
        branch = self.make_remote_branch(transport, client)
 
1673
        branch.lock_write()
 
1674
        config = branch._get_config()
 
1675
        config.set_option(
 
1676
            {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
 
1677
            'foo')
 
1678
        branch.unlock()
 
1679
        self.assertFinished(client)
 
1680
 
1575
1681
    def test_backwards_compat_set_option(self):
1576
1682
        self.setup_smart_server_with_call_log()
1577
1683
        branch = self.make_branch('.')
1584
1690
        self.assertLength(10, self.hpss_calls)
1585
1691
        self.assertEqual('value', branch._get_config().get_option('name'))
1586
1692
 
 
1693
    def test_backwards_compat_set_option_with_dict(self):
 
1694
        self.setup_smart_server_with_call_log()
 
1695
        branch = self.make_branch('.')
 
1696
        verb = 'Branch.set_config_option_dict'
 
1697
        self.disable_verb(verb)
 
1698
        branch.lock_write()
 
1699
        self.addCleanup(branch.unlock)
 
1700
        self.reset_smart_call_log()
 
1701
        config = branch._get_config()
 
1702
        value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
 
1703
        config.set_option(value_dict, 'name')
 
1704
        self.assertLength(10, self.hpss_calls)
 
1705
        self.assertEqual(value_dict, branch._get_config().get_option('name'))
 
1706
 
1587
1707
 
1588
1708
class TestBranchLockWrite(RemoteBranchTestCase):
1589
1709
 
1731
1851
        return repo, client
1732
1852
 
1733
1853
 
 
1854
def remoted_description(format):
 
1855
    return 'Remote: ' + format.get_format_description()
 
1856
 
 
1857
 
 
1858
class TestBranchFormat(tests.TestCase):
 
1859
 
 
1860
    def test_get_format_description(self):
 
1861
        remote_format = RemoteBranchFormat()
 
1862
        real_format = branch.BranchFormat.get_default_format()
 
1863
        remote_format._network_name = real_format.network_name()
 
1864
        self.assertEqual(remoted_description(real_format),
 
1865
            remote_format.get_format_description())
 
1866
 
 
1867
 
1734
1868
class TestRepositoryFormat(TestRemoteRepository):
1735
1869
 
1736
1870
    def test_fast_delta(self):
1743
1877
        false_format._network_name = false_name
1744
1878
        self.assertEqual(False, false_format.fast_deltas)
1745
1879
 
 
1880
    def test_get_format_description(self):
 
1881
        remote_repo_format = RemoteRepositoryFormat()
 
1882
        real_format = repository.RepositoryFormat.get_default_format()
 
1883
        remote_repo_format._network_name = real_format.network_name()
 
1884
        self.assertEqual(remoted_description(real_format),
 
1885
            remote_repo_format.get_format_description())
 
1886
 
1746
1887
 
1747
1888
class TestRepositoryGatherStats(TestRemoteRepository):
1748
1889
 
1933
2074
        self.assertLength(1, self.hpss_calls)
1934
2075
 
1935
2076
    def disableExtraResults(self):
1936
 
        old_flag = SmartServerRepositoryGetParentMap.no_extra_results
1937
 
        SmartServerRepositoryGetParentMap.no_extra_results = True
1938
 
        def reset_values():
1939
 
            SmartServerRepositoryGetParentMap.no_extra_results = old_flag
1940
 
        self.addCleanup(reset_values)
 
2077
        self.overrideAttr(SmartServerRepositoryGetParentMap,
 
2078
                          'no_extra_results', True)
1941
2079
 
1942
2080
    def test_null_cached_missing_and_stop_key(self):
1943
2081
        self.setup_smart_server_with_call_log()
2002
2140
 
2003
2141
    def test_allows_new_revisions(self):
2004
2142
        """get_parent_map's results can be updated by commit."""
2005
 
        smart_server = server.SmartTCPServer_for_testing()
 
2143
        smart_server = test_server.SmartTCPServer_for_testing()
2006
2144
        self.start_server(smart_server)
2007
2145
        self.make_branch('branch')
2008
2146
        branch = Branch.open(smart_server.get_url() + '/branch')
2118
2256
        """
2119
2257
        # Make a repo with a fallback repo, both using a FakeClient.
2120
2258
        format = remote.response_tuple_to_repo_format(
2121
 
            ('yes', 'no', 'yes', 'fake-network-name'))
 
2259
            ('yes', 'no', 'yes', self.get_repo_format().network_name()))
2122
2260
        repo, client = self.setup_fake_client_and_repository('quack')
2123
2261
        repo._format = format
2124
2262
        fallback_repo, ignored = self.setup_fake_client_and_repository(
2125
2263
            'fallback')
2126
2264
        fallback_repo._client = client
 
2265
        fallback_repo._format = format
2127
2266
        repo.add_fallback_repository(fallback_repo)
2128
2267
        # First the client should ask the primary repo
2129
2268
        client.add_expected_call(
2150
2289
            repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2151
2290
        self.assertFinished(client)
2152
2291
 
 
2292
    def test_branch_fallback_locking(self):
 
2293
        """RemoteBranch.get_rev_id takes a read lock, and tries to call the
 
2294
        get_rev_id_for_revno verb.  If the verb is unknown the VFS fallback
 
2295
        will be invoked, which will fail if the repo is unlocked.
 
2296
        """
 
2297
        self.setup_smart_server_with_call_log()
 
2298
        tree = self.make_branch_and_memory_tree('.')
 
2299
        tree.lock_write()
 
2300
        tree.add('')
 
2301
        rev1 = tree.commit('First')
 
2302
        rev2 = tree.commit('Second')
 
2303
        tree.unlock()
 
2304
        branch = tree.branch
 
2305
        self.assertFalse(branch.is_locked())
 
2306
        self.reset_smart_call_log()
 
2307
        verb = 'Repository.get_rev_id_for_revno'
 
2308
        self.disable_verb(verb)
 
2309
        self.assertEqual(rev1, branch.get_rev_id(1))
 
2310
        self.assertLength(1, [call for call in self.hpss_calls if
 
2311
                              call.call.method == verb])
 
2312
 
2153
2313
 
2154
2314
class TestRepositoryIsShared(TestRemoteRepository):
2155
2315
 
2182
2342
        transport_path = 'quack'
2183
2343
        repo, client = self.setup_fake_client_and_repository(transport_path)
2184
2344
        client.add_success_response('ok', 'a token')
2185
 
        result = repo.lock_write()
 
2345
        token = repo.lock_write().repository_token
2186
2346
        self.assertEqual(
2187
2347
            [('call', 'Repository.lock_write', ('quack/', ''))],
2188
2348
            client._calls)
2189
 
        self.assertEqual('a token', result)
 
2349
        self.assertEqual('a token', token)
2190
2350
 
2191
2351
    def test_lock_write_already_locked(self):
2192
2352
        transport_path = 'quack'
2530
2690
    """RemoteRepository.copy_content_into optimizations"""
2531
2691
 
2532
2692
    def test_copy_content_remote_to_local(self):
2533
 
        self.transport_server = server.SmartTCPServer_for_testing
 
2693
        self.transport_server = test_server.SmartTCPServer_for_testing
2534
2694
        src_repo = self.make_repository('repo1')
2535
2695
        src_repo = repository.Repository.open(self.get_url('repo1'))
2536
2696
        # At the moment the tarball-based copy_content_into can't write back
2684
2844
        expected_error = errors.NotBranchError(path=bzrdir.root_transport.base)
2685
2845
        self.assertEqual(expected_error, translated_error)
2686
2846
 
 
2847
    def test_nobranch_one_arg(self):
 
2848
        bzrdir = self.make_bzrdir('')
 
2849
        translated_error = self.translateTuple(
 
2850
            ('nobranch', 'extra detail'), bzrdir=bzrdir)
 
2851
        expected_error = errors.NotBranchError(
 
2852
            path=bzrdir.root_transport.base,
 
2853
            detail='extra detail')
 
2854
        self.assertEqual(expected_error, translated_error)
 
2855
 
2687
2856
    def test_LockContention(self):
2688
2857
        translated_error = self.translateTuple(('LockContention',))
2689
2858
        expected_error = errors.LockContention('(remote lock)')
2802
2971
        # In addition to re-raising ErrorFromSmartServer, some debug info has
2803
2972
        # been muttered to the log file for developer to look at.
2804
2973
        self.assertContainsRe(
2805
 
            self._get_log(keep_log_file=True),
 
2974
            self.get_log(),
2806
2975
            "Missing key 'branch' in context")
2807
2976
 
2808
2977
    def test_path_missing(self):
2816
2985
        self.assertEqual(server_error, translated_error)
2817
2986
        # In addition to re-raising ErrorFromSmartServer, some debug info has
2818
2987
        # been muttered to the log file for developer to look at.
2819
 
        self.assertContainsRe(
2820
 
            self._get_log(keep_log_file=True), "Missing key 'path' in context")
 
2988
        self.assertContainsRe(self.get_log(), "Missing key 'path' in context")
2821
2989
 
2822
2990
 
2823
2991
class TestStacking(tests.TestCaseWithTransport):
2841
3009
        stacked_branch = self.make_branch('stacked', format='1.9')
2842
3010
        stacked_branch.set_stacked_on_url('../base')
2843
3011
        # start a server looking at this
2844
 
        smart_server = server.SmartTCPServer_for_testing()
 
3012
        smart_server = test_server.SmartTCPServer_for_testing()
2845
3013
        self.start_server(smart_server)
2846
3014
        remote_bzrdir = BzrDir.open(smart_server.get_url() + '/stacked')
2847
3015
        # can get its branch and repository
2951
3119
            local_tree.commit('more local changes are better')
2952
3120
            branch = Branch.open(self.get_url('tree3'))
2953
3121
            branch.lock_read()
 
3122
            self.addCleanup(branch.unlock)
2954
3123
            return None, branch
2955
3124
        rev_ord, expected_revs = self.get_ordered_revs('1.9', 'unordered',
2956
3125
            branch_factory=make_stacked_stacked)
3002
3171
        super(TestRemoteBranchEffort, self).setUp()
3003
3172
        # Create a smart server that publishes whatever the backing VFS server
3004
3173
        # does.
3005
 
        self.smart_server = server.SmartTCPServer_for_testing()
 
3174
        self.smart_server = test_server.SmartTCPServer_for_testing()
3006
3175
        self.start_server(self.smart_server, self.get_server())
3007
3176
        # Log all HPSS calls into self.hpss_calls.
3008
3177
        _SmartClient.hooks.install_named_hook(