Repository: cocotb/cocotb

Issue:
No error traceback in forked coroutines in Python 3.7
Hello,
I don't know if it is an expected behavioral but when i have an error in a forked coroutine, i haven't got any traceback to know where it is.

For example:
```python
@cocotb.coroutine
def coroutine_with_undef():
    new_variable = undefined_variable
    yield Timer(1, units='ns')

@cocotb.test()
def error_test(dut):
    cocotb.fork(coroutine_with_undef())
    yield Timer(10, units='ns')
```

Output:
```
     0.00ns INFO     Running on Icarus Verilog version 10.2 (stable)
     0.00ns INFO     Python interpreter initialised and cocotb loaded!
     0.00ns INFO     Running tests with Cocotb v1.1.0 from /tmp/cocotb_prueba/venv/lib/python3.7/site-packages
     0.00ns INFO     Seeding Python random module with 1554336569
     0.00ns INFO     Found test cocotest.error_test
     0.00ns INFO     Running test 1/1: error_test
     0.00ns INFO     Starting test: "error_test"
                     Description: None
     0.00ns ERROR    Test Failed: error_test (result was TestError)
     0.00ns ERROR    Failed 1 out of 1 tests (0 skipped)
     0.00ns INFO     *****************************************************************************
                     ** TEST                 PASS/FAIL  SIM TIME(NS)  REAL TIME(S)  RATIO(NS/S) **
                     *****************************************************************************
                     ** cocotest.error_test    FAIL            0.00          0.00         3.76  **
                     *****************************************************************************
                     
     0.00ns INFO     *************************************************************************************
                     **                                 ERRORS : 1                                      **
                     *************************************************************************************
                     **                               SIM TIME : 0.00 NS                                **
                     **                              REAL TIME : 0.00 S                                 **
                     **                        SIM / REAL TIME : 0.28 NS/S                              **
                     *************************************************************************************
                     
     0.00ns INFO     Shutting down...
```
TestSuccess interpreted as an error in forked coroutine
Hello,
I update cocotb to 1.1 and i have some issues with error tracebacks and an unexpected behavioral of TestSuccess.  
Verilog:  
```verilog
`timescale 1ns/1ps
module toplevel;
endmodule
```
Test:  
```python
import cocotb
from cocotb.triggers import Timer
from cocotb.result import TestSuccess

@cocotb.coroutine
def raise_test_success():
    yield Timer(1, units='ns')
    raise TestSuccess("TestSuccess")

@cocotb.test()
def error_test(dut):
    cocotb.fork(raise_test_success())
    yield Timer(10, units='ns')
```
Output:
```
     0.00ns INFO     Running on Icarus Verilog version 10.2 (stable)
     0.00ns INFO     Python interpreter initialised and cocotb loaded!
     0.00ns INFO     Running tests with Cocotb v1.1.0 from /tmp/cocotb_prueba/venv/lib/python3.7/site-packages
     0.00ns INFO     Seeding Python random module with 1554336180
     0.00ns INFO     Found test cocotest.error_test
     0.00ns INFO     Running test 1/1: error_test
     0.00ns INFO     Starting test: "error_test"
                     Description: None
     1.00ns ERROR    Test Failed: error_test (result was TestError)
     1.00ns ERROR    Failed 1 out of 1 tests (0 skipped)
     1.00ns INFO     *****************************************************************************
                     ** TEST                 PASS/FAIL  SIM TIME(NS)  REAL TIME(S)  RATIO(NS/S) **
                     *****************************************************************************
                     ** cocotest.error_test    FAIL            1.00          0.00      2406.02  **
                     *****************************************************************************
                     
     1.00ns INFO     *************************************************************************************
                     **                                 ERRORS : 1                                      **
                     *************************************************************************************
                     **                               SIM TIME : 1.00 NS                                **
                     **                              REAL TIME : 0.01 S                                 **
                     **                        SIM / REAL TIME : 186.92 NS/S                            **
                     *************************************************************************************
```

I have tests running in previous version and raising TestSuccess works as it's expected.  


Discussion:
The tracebacks appears in python3.6 but not in 3.7
I’ve been also experiencing similar problem which makes debugging testbenches a real nightmare :( 
Python 3.7
Yes, I just brought this up in the gitter channel.  Glad someone else has made an issue about this.  I think this is rather high priority and would like to see it moved up.
@andresdemski: can you give an example of a test that only gives a traceback in 3.7?
the tracebacks aren't shown in 3.7.
Updated title to better reflect what's going on.
This is due to the error handling behavior in `cocotb.decorators.coroutine.__call__`. It catches any errors that occur before the coroutine becomes a `RunningCoroutine`, uses `traceback.print_exc()` to print the stack trace and then throws a `TestError`.

As a temporary solution, I think you'll find if you put a `yield NullTrigger()` in the coroutine definition before the erroring code, the error reporting will work.

I'm not certain why this causes an issue on 3.7 vs 3.6, but the behavior is bad. Exceptions are propagated through `yield`s now, so any throwing that occurs should simply be allowed to continue unravelling the stack. That try-except section should be removed.
@ktbarrett thanks for your analysis. Would you mind contributing a patch in a pull request?
This error happens in python3.6 and python3.7.
In python3.6 i got the following traceback:
```
     0.00ns INFO     Running on Icarus Verilog version 10.1 (stable)
     0.00ns INFO     Python interpreter initialised and cocotb loaded!
     0.00ns INFO     Running tests with Cocotb v1.1.0 from /usr/local/lib/python3.6/site-packages
     0.00ns INFO     Seeding Python random module with 1554382642
     0.00ns INFO     Found test cocotest.error_test
     0.00ns INFO     Running test 1/1: error_test
     0.00ns INFO     Starting test: "error_test"
                     Description: None
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/cocotb/decorators.py", line 283, in __call__
    return RunningCoroutine(self._func(*args, **kwargs), self)
  File "/tmp/cocotb_prueba/cocotest.py", line 7, in raise_test_success
    raise TestSuccess()
cocotb.result.TestSuccess
     0.00ns INFO     
     0.00ns ERROR    Test Failed: error_test (result was TestError)
     0.00ns ERROR    Failed 1 out of 1 tests (0 skipped)
```
I think this was a regression introduced in #640, in these lines.

https://github.com/potentialventures/cocotb/blob/e8d3b861f33c51548d682779cdbaca25f3460be7/cocotb/scheduler.py#L493-L502

There's also a bug here that the message of the `TestError` is never shown, making it rather useless.

Resolving patch:
diff --git a/cocotb/scheduler.py b/cocotb/scheduler.py
--- a/cocotb/scheduler.py
+++ b/cocotb/scheduler.py
@@ -470,12 +470,11 @@ def unschedule(self, coro):
                 # throws an error if the background coroutine errored
                 # and no one was monitoring it
                 coro.retval
+            except TestComplete as test_result:
+                self.log.debug("TestComplete received: {}".format(test_result.__class__.__name__))
+                self.finish_test(test_result)
             except Exception as e:
-                self._test_result = TestError(
-                    "Forked coroutine {} raised exception {}"
-                    .format(coro, e)
-                )
-                self._terminate = True
+                self.finish_test(create_error(self, "Forked coroutine {} raised exception: {}".format(coro, e)))
 
     def save_write(self, handle, value):
         if self._mode == Scheduler._MODE_READONLY:


Test patch:
diff --git a/tests/test_cases/issue_892/Makefile b/tests/test_cases/issue_892/Makefile
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/issue_892/Makefile
@@ -0,0 +1,33 @@
+###############################################################################
+# 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_892
diff --git a/tests/test_cases/issue_892/issue_892.py b/tests/test_cases/issue_892/issue_892.py
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/issue_892/issue_892.py
@@ -0,0 +1,13 @@
+import cocotb
+from cocotb.triggers import Timer
+from cocotb.result import TestSuccess
+
+@cocotb.coroutine
+def raise_test_success():
+    yield Timer(1, units='ns')
+    raise TestSuccess("TestSuccess")
+
+@cocotb.test()
+def error_test(dut):
+    cocotb.fork(raise_test_success())
+    yield Timer(10, units='ns')
diff --git a/tests/test_cases/issue_893/Makefile b/tests/test_cases/issue_893/Makefile
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/issue_893/Makefile
@@ -0,0 +1,33 @@
+###############################################################################
+# 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
new file mode 100644
--- /dev/null
+++ b/tests/test_cases/issue_893/issue_893.py
@@ -0,0 +1,13 @@
+import cocotb
+from cocotb.triggers import Timer, NullTrigger
+
+@cocotb.coroutine
+def coroutine_with_undef():
+    new_variable = undefined_variable
+    yield Timer(1, units='ns')
+
+@cocotb.test(expect_error=True)
+def fork_erroring_coroutine(dut):
+    cocotb.fork(coroutine_with_undef())
+    yield Timer(10, units='ns')
+