//go:build enable_e2e_tests package teste2e import ( "crypto/tls" "os" "os/exec" "path/filepath" "strconv" "strings" "sync" "testing" "time" "github.com/stretchr/testify/require" ) type container struct { name string } func newContainer(image string, name string, args []string) (*container, error) { c := &container{ name: name, } exec.Command("docker", "kill", "gortsplib-test-"+name).Run() exec.Command("docker", "wait", "gortsplib-test-"+name).Run() cmd := []string{ "docker", "run", "--network=host", "--name=gortsplib-test-" + name, "gortsplib-test-" + image, } cmd = append(cmd, args...) ecmd := exec.Command(cmd[0], cmd[1:]...) ecmd.Stdout = nil ecmd.Stderr = os.Stderr err := ecmd.Start() if err != nil { return nil, err } time.Sleep(1 * time.Second) return c, nil } func (c *container) close() { exec.Command("docker", "kill", "gortsplib-test-"+c.name).Run() exec.Command("docker", "wait", "gortsplib-test-"+c.name).Run() exec.Command("docker", "rm", "gortsplib-test-"+c.name).Run() } func (c *container) wait() int { exec.Command("docker", "wait", "gortsplib-test-"+c.name).Run() out, _ := exec.Command("docker", "inspect", "gortsplib-test-"+c.name, "--format={{.State.ExitCode}}").Output() code, _ := strconv.ParseInt(string(out[:len(out)-1]), 10, 32) return int(code) } func buildImage(image string) error { ecmd := exec.Command("docker", "build", filepath.Join("images", image), "-t", "gortsplib-test-"+image) ecmd.Stdout = nil ecmd.Stderr = os.Stderr return ecmd.Run() } func TestServerVsExternal(t *testing.T) { files, err := os.ReadDir("images") require.NoError(t, err) var wg sync.WaitGroup for _, file := range files { cfile := file wg.Add(1) go func() { err := buildImage(cfile.Name()) require.NoError(t, err) wg.Done() }() } wg.Wait() for _, ca := range []struct { publisherSoft string publisherScheme string publisherProto string publisherProfile string publisherTunnel string readerSoft string readerScheme string readerProto string readerProfile string readerTunnel string }{ { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "udp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "udp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "udp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsp", readerProto: "udp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsp", publisherProto: "udp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "udp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsp", publisherProto: "udp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsp", readerProto: "udp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "udp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "multicast", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "udp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsp", readerProto: "multicast", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "udp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsp", publisherProto: "udp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsps", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsps", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsps", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsps", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsps", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsps", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsps", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsps", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "ffmpeg", publisherScheme: "rtsps", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsps", readerProto: "udp", readerProfile: "savp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsps", publisherProto: "udp", publisherProfile: "savp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsps", readerProto: "udp", readerProfile: "savp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsps", publisherProto: "udp", publisherProfile: "savp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsps", readerProto: "multicast", readerProfile: "savp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "http", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "none", }, { publisherSoft: "gstreamer", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "ffmpeg", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "http", }, { publisherSoft: "gstreamer", publisherScheme: "rtsp", publisherProto: "tcp", publisherProfile: "avp", publisherTunnel: "none", readerSoft: "gstreamer", readerScheme: "rtsp", readerProto: "tcp", readerProfile: "avp", readerTunnel: "http", }, } { t.Run(strings.Join([]string{ ca.publisherSoft, ca.publisherScheme, ca.publisherProto, ca.publisherProfile, ca.publisherTunnel, ca.readerSoft, ca.readerScheme, ca.readerProto, ca.readerProfile, ca.readerTunnel, }, "_"), func(t *testing.T) { ss := &sampleServer{} if ca.publisherScheme == "rtsps" { cert, err := tls.X509KeyPair(serverCert, serverKey) require.NoError(t, err) ss.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}} } err := ss.initialize() require.NoError(t, err) defer ss.close() switch ca.publisherSoft { case "ffmpeg": cnt1, err := newContainer("ffmpeg", "publish", []string{ "-re", "-stream_loop", "-1", "-i", "emptyvideo.mkv", "-c", "copy", "-f", "rtsp", "-rtsp_transport", ca.publisherProto, ca.publisherScheme + "://localhost:8554/test/stream?key=val", }) require.NoError(t, err) defer cnt1.close() case "gstreamer": var scheme string if ca.publisherTunnel == "http" { scheme = "rtsph" } else { scheme = ca.publisherScheme } var profile string if ca.publisherProfile == "savp" { profile = "GST_RTSP_PROFILE_SAVP" } else { profile = "GST_RTSP_PROFILE_AVP" } cnt1, err := newContainer("gstreamer", "publish", []string{ "filesrc location=emptyvideo.mkv ! matroskademux ! video/x-h264 ! rtspclientsink " + "location=" + scheme + "://127.0.0.1:8554/test/stream?key=val" + " protocols=" + ca.publisherProto + " profiles=" + profile + " tls-validation-flags=0 latency=0 timeout=0 rtx-time=0", }) require.NoError(t, err) defer cnt1.close() time.Sleep(1 * time.Second) } time.Sleep(1 * time.Second) switch ca.readerSoft { case "ffmpeg": var proto string switch { case ca.readerTunnel == "http": proto = "http" case ca.readerProto == "multicast": proto = "udp_multicast" default: proto = ca.readerProto } cnt2, err := newContainer("ffmpeg", "read", []string{ "-rtsp_transport", proto, "-i", ca.readerScheme + "://localhost:8554/test/stream?key=val", "-vframes", "1", "-f", "image2", "-y", "/dev/null", }) require.NoError(t, err) defer cnt2.close() require.Equal(t, 0, cnt2.wait()) case "gstreamer": var scheme string if ca.readerTunnel == "http" { scheme = "rtsph" } else { scheme = ca.readerScheme } var proto string if ca.readerProto == "multicast" { proto = "udp-mcast" } else { proto = ca.readerProto } cnt2, err := newContainer("gstreamer", "read", []string{ "rtspsrc location=" + scheme + "://127.0.0.1:8554/test/stream?key=val" + " protocols=" + proto + " tls-validation-flags=0 latency=0 " + "! application/x-rtp,media=video ! decodebin ! video/x-raw ! fakesink num-buffers=1", }) require.NoError(t, err) defer cnt2.close() require.Equal(t, 0, cnt2.wait()) } }) } }