2016-08-04 29 views
0

Das treibt mich OCD-verrückt. Angenommen, ich habe die folgende Funktion:Wie kann ich diese Select-Anweisung für eine garantierte 100% -Testabdeckung neu schreiben?

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

Es f sendet (v) auf dst für jeden Wert v von src erhalten, bis entweder src oder beenden geschlossen und leer oder ein Wert empfangen wird von beenden.

Nun, ich nehme an, einen Test schreiben möchten, die, dass es zeigt, abgebrochen werden kann:

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

Die undefinierte Funktionen sind hier nicht sehr wichtig. Bitte gehen Sie davon aus, dass sie funktionieren. timeout schließt seinen Kanalparameter nach der angegebenen Zeit und wait schließt seinen Kanalparameter nach wg.Wait().

Das Problem ist, dass dies keine 100% ige Abdeckung bietet, da ein select case einheitlich unter (pseudo-) random gewählt wird, wenn beide zum Senden/Empfangen bereit sind. Die folgende Version von Map hat dieses Problem nicht, leidet aber unter Potenzial unbestimmter Blockierung, wenn der Upstream-Kanal (src) nicht geschlossen ist: kann

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

I sort-of dieses Problem lösen, indem Sie den Test zu wiederholen Umschreiben mehrmals in einer Schleife, so dass jeder Zweig zufällig ausgewählt werden kann. Ich habe so wenig wie 10 Iterationen probiert und 100% Deckung mit allen bestandenen Tests erreicht (es gibt andere Tests außer diesem). Aber es bringt mich um den Verstand, dass ich scheinbar keine Best-of-Worlds-Version schreiben kann, die nicht blockiert, wenn der Upstream-Kanal nicht geschlossen ist und garantiert 100% Testabdeckung bietet (nicht nur wahrscheinlich).

Irgendwelche Inspiration für mich?

P.S., wenn Sie neugierig sind, warum "nicht blockiert, wenn Upstream-Kanal nicht geschlossen ist" ist wichtig, es ist nur ein weiteres Stück von OCD. Diese Funktion wird exportiert, was bedeutet, dass sich mein Code falsch verhält, wenn sich der Clientcode nicht richtig verhält. Ich möchte, dass es widerstandsfähiger ist als das, was die erste Version ist.

Antwort

0

EDIT: Ich habe diese Antwort stark bearbeitet, weil sie immer noch falsch war. Das scheint recht gut zu funktionieren.

Okay, ich fühle mich ziemlich schüchtern. Ich muss ausgebrannt sein, als ich das betrachtete. Es ist absolut möglich, diese Select-Anweisungen deterministisch zu durchlaufen:

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

Ich würde es in mehrere Testfunktionen aufteilen. Außerdem gibt es zwei weitere Fälle, die Sie nicht testen: Empfangen von 'src', dann Senden von' dst' _then_ Schließen 'quit', und Beenden, weil' src' geschlossen wurde. https://play.golang.org/p/KI9OJLsHdc – Kaedys

+0

Diese werden bereits in anderen Testfunktionen behandelt. Gerade dieser bestimmte gab mir Anfälle, weil ich nicht bemerkt hatte, dass ich die Auswahlanweisung deterministisch durchqueren könnte, indem ich Kanäle auf Null setze, so dass sie niemals empfangen würden. – burfl

+0

Sie müssen sie nicht einmal auf Null setzen, um ehrlich zu sein. Einfach ungepufferte Kanäle zu machen, die man nicht als Empfang bezeichnet, ist mechanisch identisch mit dem Kanal, der Null ist. Ihr Test würde sich überhaupt nicht ändern, wenn Sie einen ungepufferten 'dst'-Kanal erstellen würden und ihn an' Map' übergeben würden und sich einfach nie darum kümmern würden, ein '<-dst' zu machen. – Kaedys