annotate tests/test-check-interfaces.py @ 33828:b70029f355a3

tests: verify that peer instances only expose interface members Our abstract interfaces are more useful if we guarantee that implementations conform to certain rules. Namely, we want to ensure that objects implementing interfaces don't expose new public attributes that aren't part of the interface. That way, as long as consumers don't access "internal" attributes (those beginning with "_") then (in theory) objects implementing interfaces can be swapped out and everything will "just work." We add a test that enforces our "no public attributes not part of the abstract interface" rule. We /could/ implement "interface compliance detection" at run-time. However, that is littered with problems. The obvious solutions are custom __new__ and __init__ methods. These rely on derived types actually calling the parent's implementation, which is no sure bet. Furthermore, __new__ and __init__ will likely be called before instance-specific attributes are assigned. In other words, they won't detect public attributes set on self.__dict__. This means public attribute detection won't be robust. We could work around lack of robust self.__dict__ public attribute detection by having our interfaces implement a custom __getattribute__, __getattr__, and/or __setattr__. However, this incurs an undesirable run-time penalty. And, subclasses could override our custom method, bypassing the check. The most robust solution is a non-runtime test. So that's what this commit implements. We have a generic function for validating that an object only has public attributes defined by abstract classes. Then, we instantiate some peers and verify a newly constructed object plays by the rules. Differential Revision: https://phab.mercurial-scm.org/D339
author Gregory Szorc <gregory.szorc@gmail.com>
date Thu, 10 Aug 2017 21:00:30 -0700
parents
children afcbc6f64d27
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
33828
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
1 # Test that certain objects conform to well-defined interfaces.
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
2
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
3 from __future__ import absolute_import, print_function
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
4
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
5 from mercurial import (
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
6 httppeer,
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
7 localrepo,
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
8 sshpeer,
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
9 ui as uimod,
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
10 )
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
11
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
12 def checkobject(o):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
13 """Verify a constructed object conforms to interface rules.
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
14
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
15 An object must have __abstractmethods__ defined.
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
16
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
17 All "public" attributes of the object (attributes not prefixed with
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
18 an underscore) must be in __abstractmethods__ or appear on a base class
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
19 with __abstractmethods__.
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
20 """
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
21 name = o.__class__.__name__
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
22
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
23 allowed = set()
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
24 for cls in o.__class__.__mro__:
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
25 if not getattr(cls, '__abstractmethods__', set()):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
26 continue
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
27
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
28 allowed |= cls.__abstractmethods__
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
29 allowed |= {a for a in dir(cls) if not a.startswith('_')}
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
30
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
31 if not allowed:
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
32 print('%s does not have abstract methods' % name)
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
33 return
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
34
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
35 public = {a for a in dir(o) if not a.startswith('_')}
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
36
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
37 for attr in sorted(public - allowed):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
38 print('public attributes not in abstract interface: %s.%s' % (
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
39 name, attr))
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
40
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
41 # Facilitates testing localpeer.
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
42 class dummyrepo(object):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
43 def __init__(self):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
44 self.ui = uimod.ui()
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
45 def filtered(self, name):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
46 pass
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
47 def _restrictcapabilities(self, caps):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
48 pass
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
49
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
50 # Facilitates testing sshpeer without requiring an SSH server.
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
51 class testingsshpeer(sshpeer.sshpeer):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
52 def _validaterepo(self, *args, **kwargs):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
53 pass
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
54
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
55 class badpeer(httppeer.httppeer):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
56 def __init__(self):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
57 super(badpeer, self).__init__(uimod.ui(), 'http://localhost')
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
58 self.badattribute = True
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
59
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
60 def badmethod(self):
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
61 pass
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
62
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
63 def main():
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
64 ui = uimod.ui()
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
65
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
66 checkobject(badpeer())
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
67 checkobject(httppeer.httppeer(ui, 'http://localhost'))
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
68 checkobject(localrepo.localpeer(dummyrepo()))
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
69 checkobject(testingsshpeer(ui, 'ssh://localhost/foo'))
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
70
b70029f355a3 tests: verify that peer instances only expose interface members
Gregory Szorc <gregory.szorc@gmail.com>
parents:
diff changeset
71 main()