如何重写此select语句以确保100%的测试覆盖率?

问题描述:

这让我对OCD感到疯狂.假设我具有以下功能:

This is driving me OCD-crazy. Suppose I have the following function:

func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{} {
    for {
        select {
        case v, ok := <- src:
            if !ok {
                return
            }
            select {
            case dst <- f(v):
            case <-quit:
                return
            }
        case <-quit:
            return
        }
    }
}

它在dst上为从src接收到的每个值v发送f(v),直到src或quit关闭并且为空或从quit接收到一个值.

It sends f(v) on dst for each value v received from src until either src or quit is closed and empty or a value is received from quit.

现在,假设我想编写一个测试来证明它可以被取消:

Now, suppose I want to write a test which demonstrates that it can be cancelled:

func TestMapCancel(t *testing.T) {
    var wg sync.WaitGroup

    quit := make(chan struct{})
    success := make(chan struct{})
    wg.Add(3)

    src := // channel providing arbitrary values until quit is closed
    dst := make(chan interface{})

    // mapper
    go func() {
        defer wg.Done()
        defer close(dst)
        Map(quit, dst, src, double)
    }()

    // provide a sink to consume values from dst until quit is closed
    timeout(quit, 10*time.Millisecond)
    wait(success, &wg)

    select {
    case <-success:
    case <-time.After(100 * time.Millisecond):
        t.Error("cancellation timed out")
    }
}

这里的未定义函数并不是很重要.请假设他们工作. timeout 在指定的时间后关闭其通道参数,而 wait wg.Wait()之后关闭其通道参数.

The undefined functions here aren't terribly important. Please assume they work. timeout closes its channel parameter after the specified amount of time, and wait closes its channel parameter after wg.Wait().

问题在于这不会提供100%的覆盖率,因为如果这两种情况都准备好发送/接收,则会在(伪)随机情况下统一选择一个选择大小写.以下版本的 Map 没有此问题,但是如果上游通道(src)没有关闭,则可能会受到不确定的阻塞:

The problem is that this won't offer 100% coverage because a select case is chosen uniformly at (pseudo-)random if both are ready to send/receive. The following version of Map doesn't have this problem, but suffers from potential indefinite blocking if the upstream channel (src) isn't closed:

func Map(quit <-chan struct{}, dst chan<- interface{}, src <-chan interface{}, f func(interface{}) interface{}) {
    for v := range src {
        select {
        case dst <- f(v):
        case <-quit:
            return
        }
    }
}

我可以通过重写测试使其在循环中重复几次来解决此问题,从而使每个分支都有机会随机选择.我尝试了最少10次迭代,并通过了所有测试,并达到了100%的覆盖率(除此以外,还有其他测试).但这使我不屑一顾,我似乎无法编写出两全其美的版本,如果上游通道没有关闭,并且保证提供100%的测试覆盖率,那么该版本就不会阻塞可能).

I can sort-of solve this by rewriting the test to repeat several times in a loop, so that each branch has a chance to be chosen at random. I've tried as few as 10 iterations and reached 100% coverage with all tests passing (there are other tests besides this one). But it bugs the crap out of me that I can't seem to write a best-of-both-worlds version that won't block if the upstream channel isn't closed and is guaranteed to give 100% test coverage (not just likely).

对我有启发吗?

P.S.,如果您好奇如果没有关闭上游通道就不会阻塞"为什么很重要,那只是OCD的另一部分.此函数已导出,这意味着如果客户端代码行为不当,则我的代码行为不当.我希望它比第一个版本更具弹性.

P.S., if you're curious why "doesn't block if upstream channel isn't closed" is important, it's just another bit of OCD. This function is exported, meaning that if client code is misbehaving, my code misbehaves. I'd like it to be more resilient than that, which the first version is.

我对此答案进行了大量编辑,因为它仍然不正确.这似乎工作得很好.

I heavily edited this answer because it was still incorrect. This seems to work quite well.

好的,所以我感到很讨厌.当我看着这个时,我一定已经精疲力尽了.绝对有可能以确定性的方式遍历那些选择语句:

Okay, so I'm feeling pretty sheepish. I must've been burned out when I was looking at this. It's absolutely possible to traverse those select statements in a deterministic fashion:

func TestMapCancel(t *testing.T) {
    src := make(chan interface{})
    quit := make(chan struct{})
    done := make(chan struct{})

    go func() {
        defer close(done)
        Map(quit, nil, src, double)
    }()

    close(quit)

    select {
    case <-done:
    case <-time.After(100 * time.Millisecond):
        t.Error("quitting pre-send failed")
    }

    src = make(chan interface{})
    quit = make(chan struct{})
    done = make(chan struct{})

    go func() {
        defer close(done)
        Map(quit, nil, src, double)
    }()

    src <- 1
    close(quit)

    select {
    case <-done:
    case <-time.After(100 * time.Millisecond):
        t.Error("quitting pre-send failed")
    }
}