Repository: cocotb/cocotb

Issue:
cocotb.hook does not do anything unless in a module present in COCOTB_HOOKS
Marking a function with `@cocotb.hook` is insufficient for it to do anything - you also have to set the `COCOTB_HOOKS` environment variable to contain the module name where the decorated function resides.

Either we should document this, or we should just remove `cocotb.hook`. None of our tests test `cocotb.hook`. Is anyone using it?


Discussion:
The hooks functionality was added in https://github.com/potentialventures/cocotb/pull/565 by @stuarthodgson and @chiggs. So it's most likely used or has been used by them. There's a tiny bit of documentation at https://github.com/potentialventures/cocotb/blob/b6f24c142b14ade52a99e8c8c3d2fd46d49a9e77/cocotb/__init__.py#L113-L114.

@stuarthodgson @chiggs 
- Are you using cocotb.hook() at the moment and is it still useful to you?
- Can you outline a quick use case?
It is used by others are organisation so please don't go removing it. I'll try and dignup some documentation for it buts it's a bit esoteric.
Thanks @stuarthodgson. A use case would be nice to make sure we can add tests to ensure it doesn't get broken.
I'm backtracking on this. It can be killed with great predudice.

Resolving patch:
diff --git a/cocotb/xunit_reporter.py b/cocotb/xunit_reporter.py
--- a/cocotb/xunit_reporter.py
+++ b/cocotb/xunit_reporter.py
@@ -28,40 +28,6 @@
 from xml.etree.ElementTree import Element, SubElement
 import xml.etree.ElementTree as ET
 
-import mmap
-from io import StringIO
-
-TRUNCATE_LINES = 100
-
-
-# file from  http://stackoverflow.com/questions/136168/get-last-n-lines-of-a-file-with-python-similar-to-tail
-class File(StringIO):
-
-    def countlines(self):
-        buf = mmap.mmap(self.fileno(), 0)
-        lines = 0
-        while buf.readline():
-            lines += 1
-        return lines
-
-    def head(self, lines_2find=1):
-        self.seek(0)                            # Rewind file
-        return [self.next() for x in range(lines_2find)]
-
-    def tail(self, lines_2find=1):
-        self.seek(0, 2)                         # go to end of file
-        bytes_in_file = self.tell()
-        lines_found, total_bytes_scanned = 0, 0
-        while (lines_2find+1 > lines_found and
-               bytes_in_file > total_bytes_scanned):
-            byte_block = min(1024, bytes_in_file-total_bytes_scanned)
-            self.seek(-(byte_block+total_bytes_scanned), 2)
-            total_bytes_scanned += byte_block
-            lines_found += self.read(1024).count('\n')
-        self.seek(-total_bytes_scanned, 2)
-        line_list = list(self.readlines())
-        return line_list[-lines_2find:]
-
 
 class XUnitReporter:
 
@@ -85,30 +51,6 @@ def add_property(self, testsuite=None, **kwargs):
         self.last_property = SubElement(testsuite, "property", **kwargs)
         return self.last_property
 
-    def update_testsuite(self, testsuite=None, **kwargs):
-        if testsuite is None:
-            testsuite = self.last_testsuite
-        for k in kwargs:
-            testsuite.set(k, str(kwargs[k]))
-
-    def update_testsuites(self, **kwargs):
-        for k in kwargs:
-            self.results.set(k, str(kwargs[k]))
-
-    def add_log(self, logfile, testcase=None):
-        if testcase is None:
-            testcase = self.last_testcase
-        log = SubElement(testcase, "system-out")
-        f = File(logfile, 'r+')
-        lines = f.countlines()
-        if lines > (TRUNCATE_LINES * 2):
-            head = f.head(TRUNCATE_LINES)
-            tail = f.tail(TRUNCATE_LINES)
-            log.text = "".join(head + list("[...truncated %d lines...]\n" %
-                               ((lines - (TRUNCATE_LINES*2)))) + tail)
-        else:
-            log.text = "".join(f.readlines())
-
     def add_failure(self, testcase=None, **kwargs):
         if testcase is None:
             testcase = self.last_testcase


Test patch:
diff --git a/tests/test_cases/test_failure/Makefile b/tests/test_cases/test_failure/Makefile
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/test_failure/Makefile
@@ -0,0 +1,14 @@
+# Copyright cocotb contributors
+# Licensed under the Revised BSD License, see LICENSE for details.
+# SPDX-License-Identifier: BSD-3-Clause
+
+MODULE := test_failure
+
+# ensure the test runs, squash any error code, and ensure a failing test was reported
+.PHONY: override_for_this_test
+override_for_this_test:
+	-$(MAKE) all
+	$(call check_for_results_file)
+	test $$(../../../bin/combine_results.py | grep "Failure in testsuite" | wc -l) -eq 1 && rm -f results.xml
+
+include ../../designs/sample_module/Makefile
diff --git a/tests/test_cases/test_failure/test_failure.py b/tests/test_cases/test_failure/test_failure.py
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/test_failure/test_failure.py
@@ -0,0 +1,15 @@
+# Copyright cocotb contributors
+# Licensed under the Revised BSD License, see LICENSE for details.
+# SPDX-License-Identifier: BSD-3-Clause
+"""
+Tests the failure path for tests in the regression.py and xunit_reporter.py.
+
+The Makefile in this folder is specially set up to squash any error code due
+to a failing test and ensures the failing test is reported properly.
+"""
+import cocotb
+
+
+@cocotb.test()
+async def test_fail():
+    assert False