Search code examples
gotestingconditional-variable

How to solve deadlock(waiting for singal from a failed test)


I have two goroutines which are two TestXxx functions during the testing. I use a conditional variable to synchronize these goroutines. However, once one of them fails the test while the other one is waiting for the signal. Here comes a deadlock. Also, I want TestFunctionA to be failed if TestFunctionB is failed.

var cond sync.Cond
func TestFunctionA(t *testing.T){
   // ... Some codes...
   cond.Wait()
}
func TestFunctionB(t *testing.T){
   // ... Some codes...
   t.Fail()
   // ... Some codes...
   cond.Broadcast()
}

I've tried some ways, for example:

var cond sync.Cond
var A_t *testing.T
func TestFunctionA(t *testing.T){
   // ... Some codes...
   A_t = t
   // ... Some codes...
   cond.Wait()
}
func TestFunctionB(t *testing.T){
   // ... Some codes...
   t.Cleanup(func(){
      if !A_t.Failed(){
          A_t.Fail()
      }
      cond.Broadcast()
   })
   t.Fail()
   // ... Some codes...
   cond.Broadcast()
}

But the A_t.Fail() is still triggered when there is no error in FunctionB.

And I'm also considering using context.Context(). However, I have no idea how to run a Testfunction with a context. Thank you for reading my question! I appreciate any comment or discussion!


Solution

  • A test should not interact with another test. But we can share anything between test cases when using subtests.

    Here is an example:

    package main
    
    import (
        "errors"
        "testing"
    )
    
    func TestFruits(t *testing.T) {
        var err error
        t.Run("test apple", getTestAppleFunc(&err))
        t.Run("test banana", getTestBananaFunc(&err))
    }
    
    func handleError(t *testing.T, err *error) {
        if err != nil && *err != nil {
            t.Error(*err)
        }
    }
    
    func getTestAppleFunc(err *error) func(*testing.T) {
        return func(t *testing.T) {
            handleError(t, err)
            *err = errors.New("Apple failed")
        }
    }
    
    func getTestBananaFunc(err *error) func(*testing.T) {
        return func(t *testing.T) {
            handleError(t, err)
        }
    }
    
    • In the functions getTestBananaFunc and getTestAppleFunc, the pointer of error is passed as an argument.
    • In the above example, getTestAppleFunc is executed first.
    • If an error is assigned in getTestAppleFunc, as shown in the above example, the getTestBananaFunc function will fail.