# Review package: 8306c6b..HEAD ## Commits ae9f3b6 fix(audio): NullSink-Verwurf-Zeile im CLI-Pfad sichtbar machen ## Files changed cmd/talk/say.go | 2 +- cmd/talk/say_test.go | 7 ++++++- internal/audio/audio.go | 12 +++++++----- internal/audio/audio_test.go | 17 +++++++++++++++-- internal/langdetect/detect.go | 2 +- internal/langdetect/detect_test.go | 1 + 6 files changed, 31 insertions(+), 10 deletions(-) ## Diff cmd/talk/say.go --- Go 106 return exitFailure 107 } 108 109 sink, err := audio.NewSink(cfg.Audio.Sink, stderr) 110 if err != nil { 111 fmt.Fprintf(stderr, "talk: %v\n", err) 112 return exitFailure cmd/talk/say_test.go --- 1/2 --- Go 52 52 53 // TestSayArgumentMitNullSink: `talk 53 // TestSayArgumentMitNullSink: `talk .. "Hallo Welt"` läuft mit .. "Hallo Welt"` läuft mit 54 // TALK_AUDIO_SINK=null tonlos durch 54 // TALK_AUDIO_SINK=null tonlos durch .. (Abnahmekriterium) — Synthese läuft .. (Abnahmekriterium) — Synthese läuft 55 // (fake espeak wurde gerufen), die 55 // (fake espeak wurde gerufen), und .. Wiedergabe wird verworfen. .. die Wiedergabe wird mit sichtbarer .. 56 // Verwurf-Zeile auf stderr verworfe .. .. n (extern beobachtbares Verhalten de .. .. s .. 57 // null-Sinks auf dem CLI-Pfad). 56 func TestSayArgumentMitNullSink(t *t 58 func TestSayArgumentMitNullSink(t *t .. esting.T) { .. esting.T) { 57 seen := withFakeEspeak(t) 59 seen := withFakeEspeak(t) 58 setGetenv(t, map[string]string{" 60 setGetenv(t, map[string]string{" .. TALK_AUDIO_SINK": "null"}) .. TALK_AUDIO_SINK": "null"}) cmd/talk/say_test.go --- 2/2 --- Go 65 67 if _, err := os.Stat(seen); err != nil { 66 68 t.Errorf("Synthese lief nicht (fake espeak nicht gerufen): %v", err) 67 69 } .. 70 if !strings.Contains(errBuf.String(), "verworfen (null sink)") { .. 71 t.Errorf("stderr = %q, will Verwurf-Zeile des null-Sinks (Synthese war erfolgreich)", errBuf.String()) .. 72 } 68 73 } 69 74 70 75 // TestSaySubcommandWieDefault: `talk say "Text"` verhält sich wie der internal/audio/audio.go --- Go 24 } 24 } 25 25 26 // NewSink liefert den Sink zum konf 26 // NewSink liefert den Sink zum konf .. igurierten Namen ("oto" oder "null") .. igurierten Namen ("oto" oder "null") .. . .. . .. 27 // log ist ein optionaler Log-Output .. .. — die CLI übergibt ihr stderr, über .. .. den .. 28 // der null-Sink seine Verwurf-Zeile .. .. schreibt (nil = still verwerfen); d .. .. er 27 // Unbekannte Namen sind ein klarer 29 // oto-Sink braucht kein Log. Unbeka .. Fehler. .. nnte Namen sind ein klarer Fehler. 28 func NewSink(name string) (Sink, err 30 func NewSink(name string, log io.Wri .. or) { .. ter) (Sink, error) { 29 switch name { 31 switch name { 30 case NameOto: 32 case NameOto: 31 return OtoSink{}, nil 33 return OtoSink{}, nil 32 case NameNull: 34 case NameNull: 33 return NullSink{}, nil 35 return NullSink{W: log}, nil 34 default: 36 default: 35 return nil, fmt.Errorf("unbe 37 return nil, fmt.Errorf("unbe .. kannter audio sink %q (erlaubt: %q, .. kannter audio sink %q (erlaubt: %q, .. %q)", name, NameOto, NameNull) .. %q)", name, NameOto, NameNull) 36 } 38 } 37 } 39 } 38 40 39 // NullSink verwirft jede Ansage ohn 41 // NullSink verwirft jede Ansage ohn .. e Ton — für CI, Server und Tests. Wu .. e Ton — für CI, Server und Tests. Wu .. rde .. rde 40 // ein W gesetzt, loggt Play eine Ze 42 // ein W gesetzt (NewSink reicht daf .. ile darüber (der CLI-Pfad hängt stde .. ür den Log-Output der CLI durch), lo .. rr .. ggt 41 // an, damit „läuft ohne Ton durch" 43 // Play eine Zeile darüber, damit „l .. auch sichtbar bleibt). .. äuft ohne Ton durch" sichtbar bleibt .. .. . 42 type NullSink struct { 44 type NullSink struct { 43 W io.Writer // optionaler Log-Ou 45 W io.Writer // optionaler Log-Ou .. tput; nil = still verwerfen .. tput; nil = still verwerfen 44 } 46 } internal/audio/audio_test.go --- Go 115 115 116 116 func TestNewSink(t *testing.T) { 117 117 for _, name := range []string{NameOto, NameNull} { 118 118 s, err := NewSink(name, nil) 119 119 if err != nil { 120 120 t.Fatalf("NewSink(%q): %v", name, err) 121 121 } 122 122 if s.Name() != name { 123 123 t.Errorf("NewSink(%q).Name() = %q", name, s.Name()) 124 124 } 125 125 } 126 126 if _, err := NewSink("mpv", nil); err == nil { 127 127 t.Error("NewSink(\"mpv\") soll fehlschlagen") 128 128 } ... 129 t.Run("null-Sink loggt über den Factory-Writer", func(t *testing.T) { ... 130 var buf bytes.Buffer ... 131 s, err := NewSink(NameNull, &buf) ... 132 if err != nil { ... 133 t.Fatalf("NewSink(%q): %v", NameNull, err) ... 134 } ... 135 if err := s.Play(context.Background(), []byte("audio")); err != nil { ... 136 t.Fatalf("Play: %v", err) ... 137 } ... 138 if !strings.Contains(buf.String(), "verworfen (null sink)") { ... 139 t.Errorf("Log = %q, will Verwurf-Zeile über den Factory-Writer", buf.String()) ... 140 } ... 141 }) 129 142 } 130 143 131 144 func TestNullSinkVerwirftUndLoggt(t *testing.T) { internal/langdetect/detect.go --- Go 68 "finished": {}, "started": {}, "completed": {}, "error": {}, 69 "warning": {}, "thanks": {}, "you": {}, "your": {}, 70 "we": {}, "it": {}, "this": {}, "that": {}, "these": {}, 71 "those": {}, "was": {}, "were": {}, "are": {}, "be": {}, 72 "been": {}, "has": {}, "have": {}, "had": {}, "can": {}, 73 "will": {}, "would": {}, "should": {}, "could": {}, "just": {}, 74 "only": {}, "still": {}, "already": {}, "now": {}, "today": {}, internal/langdetect/detect_test.go --- Go 15 15 {"nur whitespace", " \n\t ", "de"}, 16 16 {"nur interpunktion", "!!! ??? ...", "de"}, 17 17 {"deutsch satz", "Der Build wurde erfolgreich abgeschlossen und das Deployment ist fertig.", "de"}, .. 18 {"deutsch frage mit was", "Was macht er?", "de"}, 18 19 {"deutsch umlaute", "Größe früh Ärzteübung — schön daß es klappt", "de"}, 19 20 {"ß alleine", "weiß", "de"}, 20 21 {"englisch satz", "The build has finished and the deployment is done.", "en"},