Repository: cocotb/cocotb

Issue:
Add `FutureWarning` when a `fork`ed coroutine exception causes the test to end
If an error occurs in a `fork`ed Task before one `await`s a Task, the test ends. This can be non-deterministic and lead to inconsistent failures. Instead we should capture the error in the Task's outcome for propagation. If a Task results in an exception and it is never seen, then a `RuntimeWarning` would be thrown by the `__del__`.


Discussion:

Resolving patch:
diff --git a/cocotb/scheduler.py b/cocotb/scheduler.py
--- a/cocotb/scheduler.py
+++ b/cocotb/scheduler.py
@@ -538,6 +538,11 @@ def _unschedule(self, coro):
             except Exception as e:
                 coro.log.error("Exception raised by this forked coroutine")
                 e = remove_traceback_frames(e, ['_unschedule', 'get'])
+                warnings.warn(
+                    '"Unwatched" tasks that throw exceptions will not cause the test to fail. '
+                    "See issue #2664 for more details.",
+                    FutureWarning
+                )
                 self._abort_test(e)
 
     def _schedule_write(self, handle, write_func, *args):


Test patch:
diff --git a/tests/test_cases/issue_893/Makefile b/tests/test_cases/issue_893/Makefile
deleted file mode 100644
--- a/tests/test_cases/issue_893/Makefile
+++ /dev/null
@@ -1,33 +0,0 @@
-###############################################################################
-# Copyright (c) 2013 Potential Ventures Ltd
-# Copyright (c) 2013 SolarFlare Communications Inc
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#     * Redistributions of source code must retain the above copyright
-#       notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#     * Neither the name of Potential Ventures Ltd,
-#       SolarFlare Communications Inc nor the
-#       names of its contributors may be used to endorse or promote products
-#       derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL POTENTIAL VENTURES LTD BE LIABLE FOR ANY
-# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-###############################################################################
-
-
-include ../../designs/sample_module/Makefile
-
-MODULE = issue_893
diff --git a/tests/test_cases/issue_893/issue_893.py b/tests/test_cases/issue_893/issue_893.py
deleted file mode 100644
--- a/tests/test_cases/issue_893/issue_893.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import cocotb
-from cocotb.triggers import Timer
-
-
-async def coroutine_with_undef():
-    new_variable = undefined_variable  # noqa
-
-
-@cocotb.test(expect_error=NameError)
-async def fork_erroring_coroutine(dut):
-    cocotb.fork(coroutine_with_undef())
-    await Timer(10, units='ns')
diff --git a/tests/test_cases/test_cocotb/test_generator_coroutines.py b/tests/test_cases/test_cocotb/test_generator_coroutines.py
--- a/tests/test_cases/test_cocotb/test_generator_coroutines.py
+++ b/tests/test_cases/test_cocotb/test_generator_coroutines.py
@@ -109,14 +109,6 @@ def test_coroutine_error(dut):
         yield erroring_coro()
 
 
-@cocotb.test(expect_error=NameError)
-def test_fork_error(dut):
-    """Error in a coroutine that we fork"""
-    yield clock_gen(dut.clk)
-    cocotb.fork(erroring_coro())
-    yield clock_gen(dut.clk)
-
-
 @cocotb.test()
 def test_coroutine_return(dut):
     """ Test that the Python 3.3 syntax for returning from generators works """
diff --git a/tests/test_cases/test_forked_exception/Makefile b/tests/test_cases/test_forked_exception/Makefile
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/test_forked_exception/Makefile
@@ -0,0 +1,13 @@
+# Copyright cocotb contributors
+# Licensed under the Revised BSD License, see LICENSE for details.
+# SPDX-License-Identifier: BSD-3-Clause
+
+MODULE := test_forked_exception
+
+# 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
+	@test ! -f $(COCOTB_RESULTS_FILE)
+
+include ../../designs/sample_module/Makefile
diff --git a/tests/test_cases/test_forked_exception/test_forked_exception.py b/tests/test_cases/test_forked_exception/test_forked_exception.py
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/test_forked_exception/test_forked_exception.py
@@ -0,0 +1,31 @@
+# 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 warnings
+
+import cocotb
+from cocotb.triggers import Timer
+
+
+warnings.simplefilter("error", category=FutureWarning)
+
+
+class MyException(Exception):
+    ...
+
+
+@cocotb.test(expect_error=MyException)
+async def test_fail(_):
+
+    async def fails():
+        await Timer(10, 'ns')
+        raise MyException
+
+    cocotb.fork(fails())
+    await Timer(20, 'ns')