diff --git a/neo/scripts/runner.py b/neo/scripts/runner.py
index 80e1b184bd5e0f52459030c47f53d392010afe28..d27b7f5304dbdfae9ec0bdd5a452529b38eeeb58 100755
--- a/neo/scripts/runner.py
+++ b/neo/scripts/runner.py
@@ -30,6 +30,7 @@ if filter(re.compile(r'--coverage$|-\w*c').match, sys.argv[1:]):
     # Start coverage as soon as possible.
     import coverage
     coverage = coverage.Coverage()
+    coverage.neotestrunner = []
     coverage.start()
 
 import neo
@@ -210,7 +211,7 @@ class TestRunner(BenchmarkRunner):
 
     def add_options(self, parser):
         parser.add_option('-c', '--coverage', action='store_true',
-            help='Enable coverage (not working yet for functional tests)')
+            help='Enable coverage')
         parser.add_option('-f', '--functional', action='store_true',
             help='Functional tests')
         parser.add_option('-u', '--unit', action='store_true',
@@ -267,6 +268,8 @@ Environment Variables:
             traceback.print_exc()
         if config.coverage:
             coverage.stop()
+            if coverage.neotestrunner:
+                coverage.combine(coverage.neotestrunner)
             coverage.save()
         # build report
         self._successful = runner.wasSuccessful()
diff --git a/neo/tests/functional/__init__.py b/neo/tests/functional/__init__.py
index 65acae5211588983c043cd2bac9af1ca9a966cc8..6ce35e748bf74f3a0e6dce6501c51fe22cb17799 100644
--- a/neo/tests/functional/__init__.py
+++ b/neo/tests/functional/__init__.py
@@ -42,6 +42,11 @@ from .. import ADDRESS_TYPE, DB_SOCKET, DB_USER, IP_VERSION_FORMAT_DICT, SSL, \
 from neo.client.Storage import Storage
 from neo.storage.database import buildDatabaseManager
 
+try:
+    coverage = sys.modules['neo.scripts.runner'].coverage
+except (AttributeError, KeyError):
+    coverage = None
+
 command_dict = {
     NodeTypes.MASTER: 'neomaster',
     NodeTypes.STORAGE: 'neostorage',
@@ -111,6 +116,10 @@ class PortAllocator(object):
 
 
 class NEOProcess(object):
+
+    _coverage_fd = None
+    _coverage_prefix = os.path.join(getTempDirectory(), 'coverage-')
+    _coverage_index = 0
     pid = 0
 
     def __init__(self, command, uuid, arg_dict):
@@ -136,12 +145,40 @@ class NEOProcess(object):
                 args.append(str(param))
         if with_uuid:
             args += '--uuid', str(self.uuid)
+        global coverage
+        if coverage:
+            cls = self.__class__
+            cls._coverage_index += 1
+            coverage_data_path = cls._coverage_prefix + str(cls._coverage_index)
+        self._coverage_fd, w = os.pipe()
+        def save_coverage(*args):
+            if coverage:
+                coverage.stop()
+                coverage.save()
+            if args:
+                os.close(w)
+                os.kill(os.getpid(), signal.SIGSTOP)
         self.pid = os.fork()
-        if self.pid == 0:
+        if self.pid:
+            # Wait that the signal to kill the child is set up.
+            os.close(w)
+            os.read(self._coverage_fd, 1)
+            if coverage:
+                coverage.neotestrunner.append(coverage_data_path)
+        else:
             # Child
             try:
                 # release SQLite debug log
                 logging.setup()
+                signal.signal(signal.SIGTERM, lambda *args: sys.exit())
+                if coverage:
+                    coverage.stop()
+                    from coverage import Coverage
+                    coverage = Coverage(coverage_data_path)
+                    coverage.start()
+                signal.signal(signal.SIGUSR2, save_coverage)
+                os.close(self._coverage_fd)
+                os.write(w, '\0')
                 sys.argv = [command] + args
                 getattr(neo.scripts,  command).main()
                 status = 0
@@ -158,6 +195,7 @@ class NEOProcess(object):
                 # prevent child from killing anything (cf __del__), or
                 # running any other cleanup code normally done by the parent
                 try:
+                    save_coverage()
                     os._exit(status)
                 except:
                     print >>sys.stderr, status
@@ -166,6 +204,15 @@ class NEOProcess(object):
         logging.info('pid %u: %s %s',
             self.pid, command, ' '.join(map(repr, args)))
 
+    def child_coverage(self):
+        r = self._coverage_fd
+        if r is not None:
+            try:
+                os.read(r, 1)
+            finally:
+                os.close(r)
+                del self._coverage_fd
+
     def kill(self, sig=signal.SIGTERM):
         if self.pid:
             logging.info('kill pid %u', self.pid)
@@ -186,12 +233,14 @@ class NEOProcess(object):
             # guaranteed way to handle them (other objects we would depend on
             # might already have been deleted).
             pass
+        assert self._coverage_fd is None, self._coverage_fd
 
-    def wait(self, options=0):
+    def wait(self):
         if self.pid == 0:
             raise AlreadyStopped
-        result = os.WEXITSTATUS(os.waitpid(self.pid, options)[1])
+        result = os.WEXITSTATUS(os.waitpid(self.pid, 0)[1])
         self.pid = 0
+        self.child_coverage()
         if result:
             raise NodeProcessError('%r %r exited with status %r' % (
                 self.command, self.arg_dict, result))
@@ -376,10 +425,12 @@ class NEOCluster(object):
         for process_list in self.process_dict.itervalues():
             for process in process_list:
                 try:
-                    process.kill(signal.SIGSTOP)
+                    process.kill(signal.SIGUSR2)
                     stopped_list.append(process)
                 except AlreadyStopped:
                     pass
+        for process in stopped_list:
+            process.child_coverage()
         error_list = []
         for process in stopped_list:
             try: