Repository: twisted/twisted

Issue:
`parent` attribute of IService is not documented anywhere
|[<img alt="moshez's avatar" src="https://avatars.githubusercontent.com/u/422703?s=50" width="50" height="50">](https://github.com/moshez)| @moshez reported|
|-|-|
|Trac ID|trac#7922|
|Type|defect|
|Created|2015-06-01 01:54:25Z|
|Branch|https://github.com/twisted/twisted/tree/iservice-parent-7922|

Though the doc strings contain references to what happens to the `parent` attribute of a service when setServiceParent/unsetServiceParent are called, there is no place where that attribute is documented.

<details><summary>Searchable metadata</summary>

```
trac-id__7922 7922
type__defect defect
reporter__moshez moshez
priority__normal normal
milestone__ 
branch__iservice_parent_7922 iservice-parent-7922
branch_author__moshez moshez
status__closed closed
resolution__fixed fixed
component__core core
keywords__documentation documentation
time__1433123665041229 1433123665041229
changetime__1515532840174449 1515532840174449
version__None None
owner__moshez moshez
cc__glyph
```
</details>



Discussion:

Resolving patch:
diff --git a/src/twisted/application/service.py b/src/twisted/application/service.py
--- a/src/twisted/application/service.py
+++ b/src/twisted/application/service.py
@@ -92,13 +92,17 @@ class IService(Interface):
     A service.
 
     Run start-up and shut-down code at the appropriate times.
-
-    @type name:            C{string}
-    @ivar name:            The name of the service (or None)
-    @type running:         C{boolean}
-    @ivar running:         Whether the service is running.
     """
 
+    name = Attribute(
+        "A C{str} which is the name of the service or C{None}.")
+
+    running = Attribute(
+        "A C{boolean} which indicates whether the service is running.")
+
+    parent = Attribute(
+        "An C{IServiceCollection} which is the parent or C{None}.")
+
     def setName(name):
         """
         Set the name of the service.


Test patch:
diff --git a/src/twisted/application/test/test_service.py b/src/twisted/application/test/test_service.py
--- a/src/twisted/application/test/test_service.py
+++ b/src/twisted/application/test/test_service.py
@@ -7,14 +7,169 @@
 
 from __future__ import absolute_import, division
 
+from zope.interface import implementer
+from zope.interface.exceptions import BrokenImplementation
 from zope.interface.verify import verifyObject
 
 from twisted.persisted.sob import IPersistable
 from twisted.application.service import Application, IProcess
 from twisted.application.service import IService, IServiceCollection
+from twisted.application.service import Service
 from twisted.trial.unittest import TestCase
 
 
+@implementer(IService)
+class AlmostService(object):
+    """
+    Implement IService in a way that can fail.
+
+    In general, classes should maintain invariants that adhere
+    to the interfaces that they claim to implement --
+    otherwise, it is a bug.
+
+    This is a buggy class -- the IService implementation is fragile,
+    and several methods will break it. These bugs are intentional,
+    as the tests trigger them -- and then check that the class,
+    indeed, no longer complies with the interface (IService)
+    that it claims to comply with.
+
+    Since the verification will, by definition, only fail on buggy classes --
+    in other words, those which do not actually support the interface they
+    claim to support, we have to write a buggy class to properly verify
+    the interface.
+    """
+
+    def __init__(self, name, parent, running):
+        self.name = name
+        self.parent = parent
+        self.running = running
+
+
+    def makeInvalidByDeletingName(self):
+        """
+        Probably not a wise method to call.
+
+        This method removes the :code:`name` attribute,
+        which has to exist in IService classes.
+        """
+        del self.name
+
+
+    def makeInvalidByDeletingParent(self):
+        """
+        Probably not a wise method to call.
+
+        This method removes the :code:`parent` attribute,
+        which has to exist in IService classes.
+        """
+        del self.parent
+
+
+    def makeInvalidByDeletingRunning(self):
+        """
+        Probably not a wise method to call.
+
+        This method removes the :code:`running` attribute,
+        which has to exist in IService classes.
+        """
+        del self.running
+
+
+    def setName(self, name):
+        """
+        See L{twisted.application.service.IService}.
+
+        @param name: ignored
+        """
+
+
+    def setServiceParent(self, parent):
+        """
+        See L{twisted.application.service.IService}.
+
+        @param parent: ignored
+        """
+
+
+    def disownServiceParent(self):
+        """
+        See L{twisted.application.service.IService}.
+        """
+
+
+    def privilegedStartService(self):
+        """
+        See L{twisted.application.service.IService}.
+        """
+
+
+    def startService(self):
+        """
+        See L{twisted.application.service.IService}.
+        """
+
+
+    def stopService(self):
+        """
+        See L{twisted.application.service.IService}.
+        """
+
+
+
+class ServiceInterfaceTests(TestCase):
+    """
+    Tests for L{twisted.application.service.IService} implementation.
+    """
+    def setUp(self):
+        """
+        Build something that implements IService.
+        """
+        self.almostService = AlmostService(parent=None, running=False,
+                                           name=None)
+
+
+    def test_realService(self):
+        """
+        Service implements IService.
+        """
+        myService = Service()
+        verifyObject(IService, myService)
+
+
+    def test_hasAll(self):
+        """
+        AlmostService implements IService.
+        """
+        verifyObject(IService, self.almostService)
+
+
+    def test_noName(self):
+        """
+        AlmostService with no name does not implement IService.
+        """
+        self.almostService.makeInvalidByDeletingName()
+        with self.assertRaises(BrokenImplementation):
+            verifyObject(IService, self.almostService)
+
+
+    def test_noParent(self):
+        """
+        AlmostService with no parent does not implement IService.
+        """
+        self.almostService.makeInvalidByDeletingParent()
+        with self.assertRaises(BrokenImplementation):
+            verifyObject(IService, self.almostService)
+
+
+    def test_noRunning(self):
+        """
+        AlmostService with no running does not implement IService.
+        """
+        self.almostService.makeInvalidByDeletingRunning()
+        with self.assertRaises(BrokenImplementation):
+            verifyObject(IService, self.almostService)
+
+
 
 class ApplicationTests(TestCase):
     """
diff --git a/src/twisted/test/test_twistd.py b/src/twisted/test/test_twistd.py
--- a/src/twisted/test/test_twistd.py
+++ b/src/twisted/test/test_twistd.py
@@ -609,6 +609,9 @@ def removePID(self, pidfile):
         @implementer(service.IService, service.IProcess)
         class FakeService(object):
 
+            parent = None
+            running = None
+            name = None
             processName = None
             uid = None
             gid = None