package transcode import ( "reflect" "testing" ) // TestDetectNoFFmpeg is the first gate: without ffmpeg there is no capability, // full stop, and the candidates/probe are never consulted. func TestDetectNoFFmpeg(t *testing.T) { got := detect(checks{ ffmpeg: func() bool { return false }, candidates: func() []Encoder { t.Fatal("candidates consulted without ffmpeg") return nil }, probe: func(Encoder) bool { t.Fatal("probe run without ffmpeg") return false }, }) if got.Available() { t.Errorf("no ffmpeg: got %+v, want no capability", got) } } // TestDetectNoCandidates covers a host with ffmpeg but no device nodes at all. func TestDetectNoCandidates(t *testing.T) { got := detect(checks{ ffmpeg: func() bool { return true }, candidates: func() []Encoder { return nil }, probe: func(Encoder) bool { t.Fatal("probe run with no candidates") return false }, }) if got.Available() { t.Errorf("no candidates: got %+v, want no capability", got) } } // TestDetectProbeFails is the branch ADR-008 exists for: a device node exists // but the encode does not actually work. Reporting it would produce a transcode // that starts and then cannot keep up. func TestDetectProbeFails(t *testing.T) { got := detect(checks{ ffmpeg: func() bool { return true }, candidates: func() []Encoder { return []Encoder{{Name: EncoderVAAPI, Args: []string{"-c:v", "h264_vaapi"}}} }, probe: func(Encoder) bool { return false }, }) if got.Available() { t.Errorf("probe failed: got %+v, want no capability", got) } } // TestDetectFirstWorkingWins checks the candidate order and that a later // candidate is used when an earlier one fails. func TestDetectFirstWorkingWins(t *testing.T) { vaapi := Encoder{ Name: EncoderVAAPI, Args: []string{"-vaapi_device", "/dev/dri/renderD128", "-vf", "format=nv12,hwupload", "-c:v", "h264_vaapi"}, } nvenc := Encoder{Name: EncoderNVENC, Args: []string{"-c:v", "h264_nvenc"}} qsv := Encoder{Name: EncoderQSV, Args: []string{"-c:v", "h264_qsv"}} tests := []struct { name string ok map[string]bool want Encoder tried []string }{ { name: "vaapi works", ok: map[string]bool{EncoderVAAPI: true}, want: vaapi, tried: []string{EncoderVAAPI}, }, { name: "vaapi fails, nvenc works", ok: map[string]bool{EncoderNVENC: true}, want: nvenc, tried: []string{EncoderVAAPI, EncoderNVENC}, }, { name: "vaapi and nvenc fail, qsv works", ok: map[string]bool{EncoderQSV: true}, want: qsv, tried: []string{EncoderVAAPI, EncoderNVENC, EncoderQSV}, }, { name: "none work", ok: map[string]bool{}, want: Encoder{}, tried: []string{EncoderVAAPI, EncoderNVENC, EncoderQSV}, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var tried []string got := detect(checks{ ffmpeg: func() bool { return true }, candidates: func() []Encoder { return []Encoder{vaapi, nvenc, qsv} }, probe: func(e Encoder) bool { tried = append(tried, e.Name) return tc.ok[e.Name] }, }) if tc.want.Name == "" { if got.Available() { t.Fatalf("got %+v, want no capability", got) } } else { if got.Encoder != tc.want.Name { t.Errorf("Encoder = %q, want %q", got.Encoder, tc.want.Name) } if !reflect.DeepEqual(got.Args, tc.want.Args) { t.Errorf("Args = %q, want %q", got.Args, tc.want.Args) } } if !reflect.DeepEqual(tried, tc.tried) { t.Errorf("probed %q, want %q", tried, tc.tried) } }) } } // TestHostCandidatesOrder pins the candidate order the task requires: // VAAPI, then NVENC, then QSV. It reads this host, so it only asserts on // ordering, not on which nodes happen to exist. func TestHostCandidatesOrder(t *testing.T) { order := map[string]int{EncoderVAAPI: 0, EncoderNVENC: 1, EncoderQSV: 2} prev := -1 for _, enc := range hostCandidates() { i, ok := order[enc.Name] if !ok { t.Fatalf("hostCandidates returned unknown encoder %q", enc.Name) } if i <= prev { t.Errorf("candidate %q out of order", enc.Name) } prev = i if len(enc.Args) == 0 { t.Errorf("candidate %q has no args", enc.Name) } } } // TestDetectReal runs the real detection once. It must not panic and must be // consistent; on a host with no usable hardware it reports none. Cached, so // calling it twice must return the same thing. func TestDetectReal(t *testing.T) { first := Detect() t.Logf("real detection on this host: %+v", first) if second := Detect(); !reflect.DeepEqual(first, second) { t.Errorf("Detect not cached: %+v then %+v", first, second) } if first.Available() && first.Args == nil { t.Error("Detect reported an encoder with nil args") } } // TestProbeEncodeRejectsBogusEncoder proves the probe checks a real ffmpeg // result rather than trusting the candidate's name. Skips without ffmpeg. func TestProbeEncodeRejectsBogusEncoder(t *testing.T) { if !ffmpegOnPath() { t.Skip("ffmpeg not on PATH") } if probeEncode(Encoder{Name: "nope", Args: []string{"-c:v", "definitely_not_a_codec"}}) { t.Error("probeEncode accepted a nonexistent encoder") } }