From 417054aabad6cf4b891fd7909fd36a086dd8a0a8 Mon Sep 17 00:00:00 2001
From: david-sarah <david-sarah@jacaranda.org>
Date: Thu, 15 Sep 2011 09:15:32 -0700
Subject: [PATCH] Add a script 'misc/coding_tools/check-interfaces.py' that
 checks whether zope interfaces are enforced. Also add 'check-interfaces',
 'version-and-path', and 'code-checks' targets to the Makefile. fixes #1474

---
 Makefile                              | 19 +++++++
 misc/coding_tools/check-interfaces.py | 81 +++++++++++++++++++++++++++
 2 files changed, 100 insertions(+)
 create mode 100644 misc/coding_tools/check-interfaces.py

diff --git a/Makefile b/Makefile
index 47492e2d..e5a8121a 100644
--- a/Makefile
+++ b/Makefile
@@ -121,12 +121,26 @@ upload-coverage:
 	false
 endif
 
+code-checks: build version-and-path check-interfaces -find-trailing-spaces -check-umids pyflakes
+
+version-and-path:
+	$(TAHOE) --version-and-path
+
+check-interfaces:
+	$(TAHOE) @misc/coding_tools/check-interfaces.py 2>&1 |tee violations.txt
+	@echo
 
 pyflakes:
 	$(PYTHON) -OOu `which pyflakes` $(SOURCES) |sort |uniq
+	@echo
 
 check-umids:
 	$(PYTHON) misc/coding_tools/check-umids.py `find $(SOURCES) -name '*.py'`
+	@echo
+
+-check-umids:
+	-$(PYTHON) misc/coding_tools/check-umids.py `find $(SOURCES) -name '*.py'`
+	@echo
 
 count-lines:
 	@echo -n "files: "
@@ -213,6 +227,11 @@ clean:
 
 find-trailing-spaces:
 	$(PYTHON) misc/coding_tools/find-trailing-spaces.py -r $(SOURCES)
+	@echo
+
+-find-trailing-spaces:
+	-$(PYTHON) misc/coding_tools/find-trailing-spaces.py -r $(SOURCES)
+	@echo
 
 # The test-desert-island target grabs the tahoe-deps tarball, unpacks it,
 # does a build, then asserts that the build did not try to download anything
diff --git a/misc/coding_tools/check-interfaces.py b/misc/coding_tools/check-interfaces.py
new file mode 100644
index 00000000..2a22cffc
--- /dev/null
+++ b/misc/coding_tools/check-interfaces.py
@@ -0,0 +1,81 @@
+
+# To check a particular Tahoe source distribution, this should be invoked from
+# the root directory of that distribution as
+#
+#   bin/tahoe @misc/coding_tools/check-interfaces.py
+
+import os, sys, re
+
+import zope.interface as zi
+from zope.interface.verify import verifyClass
+from zope.interface.advice import addClassAdvisor
+
+
+interesting_modules = re.compile(r'(allmydata)|(foolscap)\..*')
+excluded_classnames = re.compile(r'(_)|(Mock)|(Fake).*')
+excluded_file_basenames = re.compile(r'check_.*')
+
+
+other_modules_with_violations = set()
+
+# deep magic
+def strictly_implements(*interfaces):
+    frame = sys._getframe(1)
+    f_locals = frame.f_locals
+
+    # Try to make sure we were called from a class def. Assumes Python > 2.2.
+    if f_locals is frame.f_globals or '__module__' not in f_locals:
+        raise TypeError("implements can be used only from a class definition.")
+
+    if '__implements_advice_data__' in f_locals:
+        raise TypeError("implements can be used only once in a class definition.")
+
+    def _implements_advice(cls):
+        interfaces, classImplements = cls.__dict__['__implements_advice_data__']
+        del cls.__implements_advice_data__
+        classImplements(cls, *interfaces)
+
+        if interesting_modules.match(cls.__module__):
+            if not excluded_classnames.match(cls.__name__):
+                for interface in interfaces:
+                    try:
+                        verifyClass(interface, cls)
+                    except Exception, e:
+                        print >>sys.stderr, ("%s.%s does not implement %s.%s:\n%s"
+                                             % (cls.__module__, cls.__name__,
+                                                interface.__module__, interface.__name__, e))
+        else:
+            other_modules_with_violations.add(cls.__module__)
+        return cls
+
+    f_locals['__implements_advice_data__'] = interfaces, zi.classImplements
+    addClassAdvisor(_implements_advice, depth=2)
+
+
+# patchee-monkey
+zi.implements = strictly_implements
+
+
+# attempt to avoid side-effects from importing command scripts
+sys.argv = ['', '--help']
+
+
+from twisted.python.filepath import FilePath
+
+# import modules under src/
+src = FilePath('src')
+for fp in src.walk():
+    (basepath, ext) = fp.splitext()
+    if ext == '.py' and not excluded_file_basenames.match(fp.basename()):
+        relpath = os.path.relpath(basepath, src.path)
+        module = relpath.replace(os.path.sep, '/').replace('/', '.')
+        try:
+            __import__(module)
+        except ImportError:
+            import traceback
+            traceback.print_exc()
+            print >>sys.stderr
+
+others = list(other_modules_with_violations)
+others.sort()
+print >>sys.stderr, "There were also interface violations in:\n", ", ".join(others), "\n"
-- 
2.45.2