comparison tests/test-check-interfaces.py @ 37181:0dfb5672f015

repository: define interface for local repositories Per discussions on the mailing list and at the 4.4 and 4.6 sprints, we want to start defining interfaces for local repository primitives so that we a) have a better idea of what the formal interface for various types is b) can more easily introduce alternate implementations of various components (e.g. in Rust). We have previously implemented interfaces that declare the peer and wire protocol APIs using the abc module. This commit introduces a monolithic interface for the localrepository class. It uses zope.interface - not abc - for defining and declaring the interface. The newly defined "completelocalrepository" interface is objectively horrible. It is based on what is actually in localrepository and doesn't represent a reasonable interface definition IMO. There's lots of... unwanted garbage in the interface. In other words, it reflects the horrible state of the localrepository "god object." But this is fine: a goal of this commit is to get the interface defined so that we have an interface. Future commits can refactor the interface into sub-interfaces, remove unwanted public attributes, etc. I attempted to define reasonable docstrings for the various interface members. But there are so many of them and I didn't know what some are used for. So I was lazy in a number of places and didn't write docstrings or detailed usage docs. Also, the members of the interface are defined in the order they are declared in localrepo.py. This revealed that the grouping of things in localrepo.py is... odd. The localrepository class now declares that it implements our newly defined interface. Unlike abc, zope.interface doesn't check interface conformance at type creation time (abc uses __metaclass__ magic to validate interface conformance when a type is created - usually at module import time). It does provide some functions for validating class and object conformance with declared interfaces. We add these checks to test-check-interfaces.py. We /could/ validate at run-time. But we hold off for now. (I'm a bit scared of doing that because of the various ways extensions monkeypatch repo instances.) After this commit, test-check-interfaces.py will fail if the set of public attributes on the localrepository class or instances change without corresponding updates to the interface. This is by design. Differential Revision: https://phab.mercurial-scm.org/D2933
author Gregory Szorc <gregory.szorc@gmail.com>
date Wed, 21 Mar 2018 19:48:36 -0700
parents 8e89c2bec1f7
children 78103e4138b1
comparison
equal deleted inserted replaced
37180:922b3fae9c7d 37181:0dfb5672f015
1 # Test that certain objects conform to well-defined interfaces. 1 # Test that certain objects conform to well-defined interfaces.
2 2
3 from __future__ import absolute_import, print_function 3 from __future__ import absolute_import, print_function
4 4
5 import os
6
7 from mercurial.thirdparty.zope import (
8 interface as zi,
9 )
10 from mercurial.thirdparty.zope.interface import (
11 verify as ziverify,
12 )
5 from mercurial import ( 13 from mercurial import (
6 bundlerepo, 14 bundlerepo,
7 httppeer, 15 httppeer,
8 localrepo, 16 localrepo,
17 repository,
9 sshpeer, 18 sshpeer,
10 statichttprepo, 19 statichttprepo,
11 ui as uimod, 20 ui as uimod,
12 unionrepo, 21 unionrepo,
13 ) 22 )
23
24 rootdir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
14 25
15 def checkobject(o): 26 def checkobject(o):
16 """Verify a constructed object conforms to interface rules. 27 """Verify a constructed object conforms to interface rules.
17 28
18 An object must have __abstractmethods__ defined. 29 An object must have __abstractmethods__ defined.
39 50
40 for attr in sorted(public - allowed): 51 for attr in sorted(public - allowed):
41 print('public attributes not in abstract interface: %s.%s' % ( 52 print('public attributes not in abstract interface: %s.%s' % (
42 name, attr)) 53 name, attr))
43 54
55 def checkzobject(o):
56 """Verify an object with a zope interface."""
57 ifaces = zi.providedBy(o)
58 if not ifaces:
59 print('%r does not provide any zope interfaces' % o)
60 return
61
62 # Run zope.interface's built-in verification routine. This verifies that
63 # everything that is supposed to be present is present.
64 for iface in ifaces:
65 ziverify.verifyObject(iface, o)
66
67 # Now verify that the object provides no extra public attributes that
68 # aren't declared as part of interfaces.
69 allowed = set()
70 for iface in ifaces:
71 allowed |= set(iface.names(all=True))
72
73 public = {a for a in dir(o) if not a.startswith('_')}
74
75 for attr in sorted(public - allowed):
76 print('public attribute not declared in interfaces: %s.%s' % (
77 o.__class__.__name__, attr))
78
44 # Facilitates testing localpeer. 79 # Facilitates testing localpeer.
45 class dummyrepo(object): 80 class dummyrepo(object):
46 def __init__(self): 81 def __init__(self):
47 self.ui = uimod.ui() 82 self.ui = uimod.ui()
48 def filtered(self, name): 83 def filtered(self, name):
66 def close(self): 101 def close(self):
67 pass 102 pass
68 103
69 def main(): 104 def main():
70 ui = uimod.ui() 105 ui = uimod.ui()
106 # Needed so we can open a local repo with obsstore without a warning.
107 ui.setconfig('experimental', 'evolution.createmarkers', True)
71 108
72 checkobject(badpeer()) 109 checkobject(badpeer())
73 checkobject(httppeer.httppeer(None, None, None, dummyopener())) 110 checkobject(httppeer.httppeer(None, None, None, dummyopener()))
74 checkobject(localrepo.localpeer(dummyrepo())) 111 checkobject(localrepo.localpeer(dummyrepo()))
75 checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, dummypipe(), 112 checkobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, dummypipe(),
78 dummypipe(), None, None)) 115 dummypipe(), None, None))
79 checkobject(bundlerepo.bundlepeer(dummyrepo())) 116 checkobject(bundlerepo.bundlepeer(dummyrepo()))
80 checkobject(statichttprepo.statichttppeer(dummyrepo())) 117 checkobject(statichttprepo.statichttppeer(dummyrepo()))
81 checkobject(unionrepo.unionpeer(dummyrepo())) 118 checkobject(unionrepo.unionpeer(dummyrepo()))
82 119
120 ziverify.verifyClass(repository.completelocalrepository,
121 localrepo.localrepository)
122 repo = localrepo.localrepository(ui, rootdir)
123 checkzobject(repo)
124
83 main() 125 main()