2016-08-04 1 views
0

これは私をOCDに夢中にさせている。私は次の関数があるとします。保証の100%テストカバレッジのために、このselectステートメントを書き換えるにはどうすればよいですか?

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 
     } 
    } 
} 

これは、各値vのDSTにF(V)を送信SRCまたはquitのいずれかが閉じられ、空や値が終了から受信されるまで、SRCから受け取りました。

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は、指定された時間後にchannelパラメータを閉じ、の後にwaitがチャネルパラメータを閉じます。

問題は、両方とも送受信の準備ができている場合、選択されたケースが(疑似)ランダムで一様に選択されるため、100%のカバレッジを提供しないという点です。 Mapの次バージョンでは、この問題を持っていますが、アップストリームチャネル(SRC)が閉じていない場合は潜在的な不定のブロックに苦しんでいません:

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%のテストカバレッジが保証されていればブロックされないベストバージョンの両方のバージョンを書くようには思えない。おそらく)。

私に何かインスピレーション?

P.S.なぜ上流チャネルが閉じていないとブロックしないのかが不思議であれば、これはOCDのもう1つのビットです。この関数はエクスポートされます。つまり、クライアントコードが正しく動作していないと、コードが正しく動作しません。私はそれが最初のバージョンであるよりも弾力的であることを望みます。

答えて

0

編集:それはまだ間違っていたので、この回答を大きく編集しました。これはかなりうまくいくようです。

さて、私はかなり羊のように感じています。私がこれを見ていたとき、私は燃えていたに違いない。これらのselect文を決定的な方法でトラバースすることは絶対に可能です。

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") 
    } 
} 
+0

複数のテスト関数に分割します。また、テストしていない2つの終了ケースがあります: 'src'から受信し、' dst'_then_閉じる 'quit'を送信し、' src'が閉じられたので終了します。 https://play.golang.org/p/KI9OJLsHdc – Kaedys

+0

これらはすでに他のテスト機能で処理されています。私が決して受信しないようにチャンネルを無制限に設定することによって、selectステートメントを確定的にトラバースすることができなかったので、この特定のものは私に適していました。 – burfl

+0

正直言って、それらをnilに設定する必要はありません。受信を呼び出さないバッファーのないチャンネルを作ることは、チャンネルがゼロであることと機械的に同じです。バッファなしの 'dst'チャンネルを作成し、それを' Map'に渡して、 '<-dst'を実行するのに気を使うことがなければ、あなたのテストはまったく変わりません。 – Kaedys

関連する問題