diff --git a/.github/workflows/test_highlevel.yml b/.github/workflows/test_highlevel.yml new file mode 100644 index 00000000..f9169be9 --- /dev/null +++ b/.github/workflows/test_highlevel.yml @@ -0,0 +1,20 @@ +name: test_highlevel + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-go@v2 + with: + go-version: "1.19" + + - run: make test-highlevel-nodocker diff --git a/Makefile b/Makefile index 841053d7..0c84e011 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ help: @echo " format format source files" @echo " test run tests" @echo " test32 run tests on a 32-bit system" + @echo " test-highlevel run high-level tests" @echo " lint run linters" @echo " bench NAME=n run bench environment" @echo " run run app" diff --git a/internal/core/api_test.go b/internal/core/api_test.go index 1ec037dd..54b84c76 100644 --- a/internal/core/api_test.go +++ b/internal/core/api_test.go @@ -66,7 +66,7 @@ func TestAPIConfigGet(t *testing.T) { p, ok := newInstance("api: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() var out map[string]interface{} err := httpRequest(http.MethodGet, "http://localhost:9997/v1/config/get", nil, &out) @@ -77,7 +77,7 @@ func TestAPIConfigGet(t *testing.T) { func TestAPIConfigSet(t *testing.T) { p, ok := newInstance("api: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() err := httpRequest(http.MethodPost, "http://localhost:9997/v1/config/set", map[string]interface{}{ "rtmpDisable": true, @@ -99,7 +99,7 @@ func TestAPIConfigSet(t *testing.T) { func TestAPIConfigPathsAdd(t *testing.T) { p, ok := newInstance("api: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() err := httpRequest(http.MethodPost, "http://localhost:9997/v1/config/paths/add/my/path", map[string]interface{}{ "source": "rtsp://127.0.0.1:9999/mypath", @@ -117,7 +117,7 @@ func TestAPIConfigPathsAdd(t *testing.T) { func TestAPIConfigPathsEdit(t *testing.T) { p, ok := newInstance("api: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() err := httpRequest(http.MethodPost, "http://localhost:9997/v1/config/paths/add/my/path", map[string]interface{}{ "source": "rtsp://127.0.0.1:9999/mypath", @@ -144,7 +144,7 @@ func TestAPIConfigPathsEdit(t *testing.T) { func TestAPIConfigPathsRemove(t *testing.T) { p, ok := newInstance("api: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() err := httpRequest(http.MethodPost, "http://localhost:9997/v1/config/paths/add/my/path", map[string]interface{}{ "source": "rtsp://127.0.0.1:9999/mypath", @@ -184,7 +184,7 @@ func TestAPIPathsList(t *testing.T) { "paths:\n" + " mypath:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() tracks := gortsplib.Tracks{ &gortsplib.TrackH264{ @@ -242,7 +242,7 @@ func TestAPIPathsList(t *testing.T) { "paths:\n" + " mypath:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() tracks := gortsplib.Tracks{ &gortsplib.TrackH264{ @@ -291,7 +291,7 @@ func TestAPIPathsList(t *testing.T) { " source: rtsp://127.0.0.1:1234/mypath\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() var out pathList err := httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) @@ -316,7 +316,7 @@ func TestAPIPathsList(t *testing.T) { " source: rtmp://127.0.0.1:1234/mypath\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() var out pathList err := httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) @@ -341,7 +341,7 @@ func TestAPIPathsList(t *testing.T) { " source: http://127.0.0.1:1234/mypath\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() var out pathList err := httpRequest(http.MethodGet, "http://localhost:9997/v1/paths/list", nil, &out) @@ -397,7 +397,7 @@ func TestAPIProtocolSpecificList(t *testing.T) { p, ok := newInstance(conf) require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -561,7 +561,7 @@ func TestAPIKick(t *testing.T) { p, ok := newInstance(conf) require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -589,16 +589,29 @@ func TestAPIKick(t *testing.T) { defer source.Close() case "rtmp": - cnt1, err := newContainer("ffmpeg", "source", []string{ - "-re", - "-stream_loop", "-1", - "-i", "emptyvideo.mkv", - "-c", "copy", - "-f", "flv", - "rtmp://localhost:1935/test1/test2", - }) + u, err := url.Parse("rtmp://localhost:1935/mypath") + require.NoError(t, err) + + nconn, err := net.Dial("tcp", u.Host) + require.NoError(t, err) + defer nconn.Close() + conn := rtmp.NewConn(nconn) + + err = conn.InitializeClient(u, true) + require.NoError(t, err) + + videoTrack := &gortsplib.TrackH264{ + PayloadType: 96, + SPS: []byte{ // 1920x1080 baseline + 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, + 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, + }, + PPS: []byte{0x08, 0x06, 0x07, 0x08}, + } + + err = conn.WriteTracks(videoTrack, nil) require.NoError(t, err) - defer cnt1.close() } var pa string diff --git a/internal/core/core.go b/internal/core/core.go index f16dbf59..914022a1 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -103,7 +103,8 @@ func New(args []string) (*Core, bool) { return p, true } -func (p *Core) close() { +// Close closes Core and waits for all goroutines to return. +func (p *Core) Close() { p.ctxCancel() <-p.done } diff --git a/internal/core/core_test.go b/internal/core/core_test.go index 073df6da..a954fd7b 100644 --- a/internal/core/core_test.go +++ b/internal/core/core_test.go @@ -7,7 +7,6 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "testing" "time" @@ -70,54 +69,6 @@ y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD -----END RSA PRIVATE KEY----- `) -type container struct { - name string -} - -func newContainer(image string, name string, args []string) (*container, error) { - c := &container{ - name: name, - } - - exec.Command("docker", "kill", "rtsp-simple-server-test-"+name).Run() - exec.Command("docker", "wait", "rtsp-simple-server-test-"+name).Run() - - // --network=host is needed to test multicast - cmd := []string{ - "docker", "run", - "--network=host", - "--name=rtsp-simple-server-test-" + name, - "rtsp-simple-server-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", "rtsp-simple-server-test-"+c.name).Run() - exec.Command("docker", "wait", "rtsp-simple-server-test-"+c.name).Run() - exec.Command("docker", "rm", "rtsp-simple-server-test-"+c.name).Run() -} - -func (c *container) wait() int { - exec.Command("docker", "wait", "rtsp-simple-server-test-"+c.name).Run() - out, _ := exec.Command("docker", "inspect", "rtsp-simple-server-test-"+c.name, - "-f", "{{.State.ExitCode}}").Output() - code, _ := strconv.ParseInt(string(out[:len(out)-1]), 10, 64) - return int(code) -} - func writeTempFile(byts []byte) (string, error) { tmpf, err := os.CreateTemp(os.TempDir(), "rtsp-") if err != nil { @@ -153,7 +104,7 @@ func TestCorePathAutoDeletion(t *testing.T) { p, ok := newInstance("paths:\n" + " all:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() func() { conn, err := net.Dial("tcp", "localhost:8554") @@ -288,7 +239,7 @@ func main() { " runOnDemand: %s\n"+ " runOnDemandCloseAfter: 1s\n", execFile)) require.Equal(t, true, ok) - defer p1.close() + defer p1.Close() func() { conn, err := net.Dial("tcp", "localhost:8554") @@ -367,7 +318,7 @@ func TestCorePathRunOnReady(t *testing.T) { " runOnReady: touch %s\n", doneFile)) require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -402,7 +353,7 @@ func TestCoreHotReloading(t *testing.T) { p, ok := New([]string{confPath}) require.Equal(t, true, ok) - defer p.close() + defer p.Close() func() { track := &gortsplib.TrackH264{ diff --git a/internal/core/hls_server_test.go b/internal/core/hls_server_test.go index 64ff4b60..2b328409 100644 --- a/internal/core/hls_server_test.go +++ b/internal/core/hls_server_test.go @@ -6,7 +6,6 @@ import ( "net" "net/http" "testing" - "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" @@ -79,7 +78,7 @@ func (ts *testHTTPAuthenticator) onAuth(ctx *gin.Context) { func TestHLSServerNotFound(t *testing.T) { p, ok := newInstance("") require.Equal(t, true, ok) - defer p.close() + defer p.Close() req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8888/stream/", nil) require.NoError(t, err) @@ -89,109 +88,3 @@ func TestHLSServerNotFound(t *testing.T) { defer res.Body.Close() require.Equal(t, http.StatusNotFound, res.StatusCode) } - -func TestHLSServerRead(t *testing.T) { - p, ok := newInstance("paths:\n" + - " all:\n") - require.Equal(t, true, ok) - defer p.close() - - cnt1, err := newContainer("ffmpeg", "source", []string{ - "-re", - "-stream_loop", "-1", - "-i", "emptyvideo.mkv", - "-c", "copy", - "-f", "rtsp", - "rtsp://127.0.0.1:8554/test/stream", - }) - require.NoError(t, err) - defer cnt1.close() - - time.Sleep(1 * time.Second) - - cnt2, err := newContainer("ffmpeg", "dest", []string{ - "-i", "http://127.0.0.1:8888/test/stream/index.m3u8", - "-vframes", "1", - "-f", "image2", - "-y", "/dev/null", - }) - require.NoError(t, err) - defer cnt2.close() - require.Equal(t, 0, cnt2.wait()) -} - -func TestHLSServerAuth(t *testing.T) { - for _, mode := range []string{ - "internal", - "external", - } { - for _, result := range []string{ - "success", - "fail", - } { - t.Run(mode+"_"+result, func(t *testing.T) { - var conf string - if mode == "internal" { - conf = "paths:\n" + - " all:\n" + - " readUser: testreader\n" + - " readPass: testpass\n" + - " readIPs: [127.0.0.0/16]\n" - } else { - conf = "externalAuthenticationURL: http://127.0.0.1:9120/auth\n" + - "paths:\n" + - " all:\n" - } - - p, ok := newInstance(conf) - require.Equal(t, true, ok) - defer p.close() - - var a *testHTTPAuthenticator - if mode == "external" { - var err error - a, err = newTestHTTPAuthenticator("publish") - require.NoError(t, err) - } - - cnt1, err := newContainer("ffmpeg", "source", []string{ - "-re", - "-stream_loop", "-1", - "-i", "emptyvideo.mkv", - "-c", "copy", - "-f", "rtsp", - "rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value", - }) - require.NoError(t, err) - defer cnt1.close() - - time.Sleep(1 * time.Second) - - if mode == "external" { - a.close() - var err error - a, err = newTestHTTPAuthenticator("read") - require.NoError(t, err) - defer a.close() - } - - var usr string - if result == "success" { - usr = "testreader" - } else { - usr = "testreader2" - } - - res, err := http.Get("http://" + usr + ":testpass@127.0.0.1:8888/teststream/index.m3u8?param=value") - require.NoError(t, err) - defer res.Body.Close() - - if result == "success" { - require.Equal(t, http.StatusOK, res.StatusCode) - } else { - require.Equal(t, http.StatusUnauthorized, res.StatusCode) - } - }) - } - } -} diff --git a/internal/core/hls_source_test.go b/internal/core/hls_source_test.go index 04217da3..e979b5ce 100644 --- a/internal/core/hls_source_test.go +++ b/internal/core/hls_source_test.go @@ -128,7 +128,7 @@ func TestHLSSource(t *testing.T) { " source: http://localhost:5780/stream.m3u8\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() time.Sleep(1 * time.Second) diff --git a/internal/core/metrics_test.go b/internal/core/metrics_test.go index c13f3759..ad831859 100644 --- a/internal/core/metrics_test.go +++ b/internal/core/metrics_test.go @@ -2,13 +2,17 @@ package core import ( "io" + "net" "net/http" + "net/url" "os" "strings" "testing" "github.com/aler9/gortsplib" "github.com/stretchr/testify/require" + + "github.com/aler9/rtsp-simple-server/internal/rtmp" ) func TestMetrics(t *testing.T) { @@ -27,7 +31,7 @@ func TestMetrics(t *testing.T) { "paths:\n" + " all:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -42,16 +46,29 @@ func TestMetrics(t *testing.T) { require.NoError(t, err) defer source.Close() - cnt1, err := newContainer("ffmpeg", "source", []string{ - "-re", - "-stream_loop", "-1", - "-i", "emptyvideo.mkv", - "-c", "copy", - "-f", "flv", - "rtmp://localhost:1935/rtmp_path", - }) + u, err := url.Parse("rtmp://localhost:1935/rtmp_path") + require.NoError(t, err) + + nconn, err := net.Dial("tcp", u.Host) + require.NoError(t, err) + defer nconn.Close() + conn := rtmp.NewConn(nconn) + + err = conn.InitializeClient(u, true) + require.NoError(t, err) + + videoTrack := &gortsplib.TrackH264{ + PayloadType: 96, + SPS: []byte{ // 1920x1080 baseline + 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, + 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, + 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, + }, + PPS: []byte{0x08, 0x06, 0x07, 0x08}, + } + + err = conn.WriteTracks(videoTrack, nil) require.NoError(t, err) - defer cnt1.close() func() { res, err := http.Get("http://localhost:8888/rtsp_path/index.m3u8") diff --git a/internal/core/rtmp_server_test.go b/internal/core/rtmp_server_test.go index 35837836..f13f0354 100644 --- a/internal/core/rtmp_server_test.go +++ b/internal/core/rtmp_server_test.go @@ -29,7 +29,7 @@ func TestRTMPServerPublishRead(t *testing.T) { "paths:\n" + " all:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() } else { port = "1936" @@ -49,7 +49,7 @@ func TestRTMPServerPublishRead(t *testing.T) { "paths:\n" + " all:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() } u, err := url.Parse("rtmp://127.0.0.1:" + port + "/mystream") @@ -169,7 +169,7 @@ func TestRTMPServerAuth(t *testing.T) { p, ok := newInstance(conf) require.Equal(t, true, ok) - defer p.close() + defer p.Close() var a *testHTTPAuthenticator if ca == "external" { @@ -239,7 +239,7 @@ func TestRTMPServerAuthFail(t *testing.T) { " publishUser: testuser2\n" + " publishPass: testpass\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() u1, err := url.Parse("rtmp://127.0.0.1:1935/teststream?user=testuser&pass=testpass") require.NoError(t, err) @@ -289,7 +289,7 @@ func TestRTMPServerAuthFail(t *testing.T) { "paths:\n" + " all:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() a, err := newTestHTTPAuthenticator("publish") require.NoError(t, err) @@ -346,7 +346,7 @@ func TestRTMPServerAuthFail(t *testing.T) { " readUser: testuser2\n" + " readPass: testpass\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() u1, err := url.Parse("rtmp://127.0.0.1:1935/teststream") require.NoError(t, err) diff --git a/internal/core/rtmp_source_test.go b/internal/core/rtmp_source_test.go index f5963d34..fa6f283a 100644 --- a/internal/core/rtmp_source_test.go +++ b/internal/core/rtmp_source_test.go @@ -102,7 +102,7 @@ func TestRTMPSource(t *testing.T) { " source: rtmp://localhost:1937/teststream\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() } else { p, ok := newInstance("paths:\n" + " proxied:\n" + @@ -110,7 +110,7 @@ func TestRTMPSource(t *testing.T) { " sourceFingerprint: 33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() } c := gortsplib.Client{ diff --git a/internal/core/rtsp_server_test.go b/internal/core/rtsp_server_test.go index 65f43c4c..da57ed7b 100644 --- a/internal/core/rtsp_server_test.go +++ b/internal/core/rtsp_server_test.go @@ -1,9 +1,7 @@ package core import ( - "os" "testing" - "time" "github.com/aler9/gortsplib" "github.com/aler9/gortsplib/pkg/url" @@ -11,191 +9,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestRTSPServerPublishRead(t *testing.T) { - for _, ca := range []struct { - publisherSoft string - publisherProto string - readerSoft string - readerProto string - }{ - {"ffmpeg", "udp", "ffmpeg", "udp"}, - {"ffmpeg", "udp", "ffmpeg", "multicast"}, - {"ffmpeg", "udp", "ffmpeg", "tcp"}, - {"ffmpeg", "udp", "gstreamer", "udp"}, - {"ffmpeg", "udp", "gstreamer", "multicast"}, - {"ffmpeg", "udp", "gstreamer", "tcp"}, - {"ffmpeg", "udp", "vlc", "udp"}, - {"ffmpeg", "udp", "vlc", "multicast"}, - {"ffmpeg", "udp", "vlc", "tcp"}, - {"ffmpeg", "tcp", "ffmpeg", "udp"}, - {"gstreamer", "udp", "ffmpeg", "udp"}, - {"gstreamer", "tcp", "ffmpeg", "udp"}, - {"ffmpeg", "tls", "ffmpeg", "tls"}, - {"ffmpeg", "tls", "gstreamer", "tls"}, - {"gstreamer", "tls", "ffmpeg", "tls"}, - } { - t.Run(ca.publisherSoft+"_"+ca.publisherProto+"_"+ - ca.readerSoft+"_"+ca.readerProto, func(t *testing.T) { - var proto string - var port string - if ca.publisherProto != "tls" { - proto = "rtsp" - port = "8554" - - p, ok := newInstance("rtmpDisable: yes\n" + - "hlsDisable: yes\n" + - "readTimeout: 20s\n" + - "paths:\n" + - " all:\n") - require.Equal(t, true, ok) - defer p.close() - } else { - proto = "rtsps" - port = "8322" - - serverCertFpath, err := writeTempFile(serverCert) - require.NoError(t, err) - defer os.Remove(serverCertFpath) - - serverKeyFpath, err := writeTempFile(serverKey) - require.NoError(t, err) - defer os.Remove(serverKeyFpath) - - p, ok := newInstance("rtmpDisable: yes\n" + - "hlsDisable: yes\n" + - "readTimeout: 20s\n" + - "protocols: [tcp]\n" + - "encryption: \"yes\"\n" + - "serverCert: " + serverCertFpath + "\n" + - "serverKey: " + serverKeyFpath + "\n" + - "paths:\n" + - " all:\n") - require.Equal(t, true, ok) - defer p.close() - } - - switch ca.publisherSoft { - case "ffmpeg": - ps := func() string { - switch ca.publisherProto { - case "udp", "tcp": - return ca.publisherProto - - default: // tls - return "tcp" - } - }() - - cnt1, err := newContainer("ffmpeg", "source", []string{ - "-re", - "-stream_loop", "-1", - "-i", "emptyvideo.mkv", - "-c", "copy", - "-f", "rtsp", - "-rtsp_transport", - ps, - proto + "://localhost:" + port + "/teststream", - }) - require.NoError(t, err) - defer cnt1.close() - - time.Sleep(1 * time.Second) - - case "gstreamer": - ps := func() string { - switch ca.publisherProto { - case "udp", "tcp": - return ca.publisherProto - - default: // tls - return "tcp" - } - }() - - cnt1, err := newContainer("gstreamer", "source", []string{ - "filesrc location=emptyvideo.mkv ! matroskademux ! video/x-h264 ! rtspclientsink " + - "location=" + proto + "://localhost:" + port + "/teststream " + - "protocols=" + ps + " 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": - ps := func() string { - switch ca.readerProto { - case "udp", "tcp": - return ca.publisherProto - - case "multicast": - return "udp_multicast" - - default: // tls - return "tcp" - } - }() - - cnt2, err := newContainer("ffmpeg", "dest", []string{ - "-rtsp_transport", ps, - "-i", proto + "://localhost:" + port + "/teststream", - "-vframes", "1", - "-f", "image2", - "-y", "/dev/null", - }) - require.NoError(t, err) - defer cnt2.close() - require.Equal(t, 0, cnt2.wait()) - - case "gstreamer": - ps := func() string { - switch ca.readerProto { - case "udp", "tcp": - return ca.publisherProto - - case "multicast": - return "udp-mcast" - - default: // tls - return "tcp" - } - }() - - cnt2, err := newContainer("gstreamer", "read", []string{ - "rtspsrc location=" + proto + "://127.0.0.1:" + port + "/teststream " + - "protocols=" + ps + " " + - "tls-validation-flags=0 latency=0 " + - "! application/x-rtp,media=video ! decodebin ! exitafterframe ! fakesink", - }) - require.NoError(t, err) - defer cnt2.close() - require.Equal(t, 0, cnt2.wait()) - - case "vlc": - args := []string{} - if ca.readerProto == "tcp" { - args = append(args, "--rtsp-tcp") - } - - ur := proto + "://localhost:" + port + "/teststream" - if ca.readerProto == "multicast" { - ur += "?vlcmulticast" - } - - args = append(args, ur) - cnt2, err := newContainer("vlc", "dest", args) - require.NoError(t, err) - defer cnt2.close() - require.Equal(t, 0, cnt2.wait()) - } - }) - } -} - func TestRTSPServerAuth(t *testing.T) { for _, ca := range []string{ "internal", @@ -222,7 +35,7 @@ func TestRTSPServerAuth(t *testing.T) { p, ok := newInstance(conf) require.Equal(t, true, ok) - defer p.close() + defer p.Close() var a *testHTTPAuthenticator if ca == "external" { @@ -278,7 +91,7 @@ func TestRTSPServerAuth(t *testing.T) { " publishUser: sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ=\n" + " publishPass: sha256:E9JJ8stBJ7QM+nV4ZoUCeHk/gU3tPFh/5YieiJp6n2w=\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -326,7 +139,7 @@ func TestRTSPServerAuthFail(t *testing.T) { " publishUser: testuser\n" + " publishPass: testpass\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -373,7 +186,7 @@ func TestRTSPServerAuthFail(t *testing.T) { " readUser: testuser\n" + " readPass: testpass\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() c := gortsplib.Client{} @@ -396,7 +209,7 @@ func TestRTSPServerAuthFail(t *testing.T) { " all:\n" + " publishIPs: [128.0.0.1/32]\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -418,7 +231,7 @@ func TestRTSPServerAuthFail(t *testing.T) { "paths:\n" + " all:\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() a, err := newTestHTTPAuthenticator("publish") require.NoError(t, err) @@ -457,7 +270,7 @@ func TestRTSPServerPublisherOverride(t *testing.T) { p, ok := newInstance(conf) require.Equal(t, true, ok) - defer p.close() + defer p.Close() track := &gortsplib.TrackH264{ PayloadType: 96, @@ -546,43 +359,6 @@ func TestRTSPServerPublisherOverride(t *testing.T) { } } -func TestRTSPServerRedirect(t *testing.T) { - p1, ok := newInstance("rtmpDisable: yes\n" + - "hlsDisable: yes\n" + - "paths:\n" + - " path1:\n" + - " source: redirect\n" + - " sourceRedirect: rtsp://localhost:8554/path2\n" + - " path2:\n") - require.Equal(t, true, ok) - defer p1.close() - - cnt1, err := newContainer("ffmpeg", "source", []string{ - "-re", - "-stream_loop", "-1", - "-i", "emptyvideo.mkv", - "-c", "copy", - "-f", "rtsp", - "-rtsp_transport", "udp", - "rtsp://localhost:8554/path2", - }) - require.NoError(t, err) - defer cnt1.close() - - time.Sleep(1 * time.Second) - - cnt2, err := newContainer("ffmpeg", "dest", []string{ - "-rtsp_transport", "udp", - "-i", "rtsp://localhost:8554/path1", - "-vframes", "1", - "-f", "image2", - "-y", "/dev/null", - }) - require.NoError(t, err) - defer cnt2.close() - require.Equal(t, 0, cnt2.wait()) -} - func TestRTSPServerFallback(t *testing.T) { for _, ca := range []string{ "absolute", @@ -603,7 +379,7 @@ func TestRTSPServerFallback(t *testing.T) { " fallback: " + val + "\n" + " path2:\n") require.Equal(t, true, ok) - defer p1.close() + defer p1.Close() source := gortsplib.Client{} err := source.StartPublishing("rtsp://localhost:8554/path2", diff --git a/internal/core/rtsp_source_test.go b/internal/core/rtsp_source_test.go index c2c07f06..b465c52e 100644 --- a/internal/core/rtsp_source_test.go +++ b/internal/core/rtsp_source_test.go @@ -134,7 +134,7 @@ func TestRTSPSource(t *testing.T) { " sourceProtocol: " + source + "\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() } else { p, ok := newInstance("paths:\n" + " proxied:\n" + @@ -142,7 +142,7 @@ func TestRTSPSource(t *testing.T) { " sourceFingerprint: 33949E05FFFB5FF3E8AA16F8213A6251B4D9363804BA53233C4DA9A46D6F2739\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() } received := make(chan struct{}) @@ -231,7 +231,7 @@ func TestRTSPSourceNoPassword(t *testing.T) { " source: rtsp://testuser:@127.0.0.1:8555/teststream\n" + " sourceProtocol: tcp\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() <-done } @@ -275,7 +275,7 @@ func TestRTSPSourceDynamicH264Params(t *testing.T) { " proxied:\n" + " source: rtsp://127.0.0.1:8555/teststream\n") require.Equal(t, true, ok) - defer p.close() + defer p.Close() time.Sleep(1 * time.Second) diff --git a/internal/highleveltests/build_images_test.go b/internal/highleveltests/build_images_test.go new file mode 100644 index 00000000..ac9e97ed --- /dev/null +++ b/internal/highleveltests/build_images_test.go @@ -0,0 +1,31 @@ +//go:build enable_highlevel_tests +// +build enable_highlevel_tests + +package highleveltests + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func buildImage(image string) error { + ecmd := exec.Command("docker", "build", filepath.Join("images", image), + "-t", "rtsp-simple-server-test-"+image) + ecmd.Stdout = nil + ecmd.Stderr = os.Stderr + return ecmd.Run() +} + +func TestBuildImages(t *testing.T) { + files, err := os.ReadDir("images") + require.NoError(t, err) + + for _, file := range files { + err := buildImage(file.Name()) + require.NoError(t, err) + } +} diff --git a/internal/highleveltests/hls_server_test.go b/internal/highleveltests/hls_server_test.go new file mode 100644 index 00000000..33030c8b --- /dev/null +++ b/internal/highleveltests/hls_server_test.go @@ -0,0 +1,186 @@ +//go:build enable_highlevel_tests +// +build enable_highlevel_tests + +package highleveltests + +import ( + "context" + "encoding/json" + "net" + "net/http" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" +) + +type testHTTPAuthenticator struct { + action string + + s *http.Server +} + +func newTestHTTPAuthenticator(action string) (*testHTTPAuthenticator, error) { + ln, err := net.Listen("tcp", "127.0.0.1:9120") + if err != nil { + return nil, err + } + + ts := &testHTTPAuthenticator{ + action: action, + } + + router := gin.New() + router.POST("/auth", ts.onAuth) + + ts.s = &http.Server{Handler: router} + go ts.s.Serve(ln) + + return ts, nil +} + +func (ts *testHTTPAuthenticator) close() { + ts.s.Shutdown(context.Background()) +} + +func (ts *testHTTPAuthenticator) onAuth(ctx *gin.Context) { + var in struct { + IP string `json:"ip"` + User string `json:"user"` + Password string `json:"password"` + Path string `json:"path"` + Action string `json:"action"` + Query string `json:"query"` + } + err := json.NewDecoder(ctx.Request.Body).Decode(&in) + if err != nil { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } + + var user string + if ts.action == "publish" { + user = "testpublisher" + } else { + user = "testreader" + } + + if in.IP != "127.0.0.1" || + in.User != user || + in.Password != "testpass" || + in.Path != "teststream" || + in.Action != ts.action || + (in.Query != "user=testreader&pass=testpass¶m=value" && + in.Query != "user=testpublisher&pass=testpass¶m=value" && + in.Query != "param=value") { + ctx.AbortWithStatus(http.StatusBadRequest) + return + } +} + +func TestHLSServerRead(t *testing.T) { + p, ok := newInstance("paths:\n" + + " all:\n") + require.Equal(t, true, ok) + defer p.Close() + + cnt1, err := newContainer("ffmpeg", "source", []string{ + "-re", + "-stream_loop", "-1", + "-i", "emptyvideo.mkv", + "-c", "copy", + "-f", "rtsp", + "rtsp://127.0.0.1:8554/test/stream", + }) + require.NoError(t, err) + defer cnt1.close() + + time.Sleep(1 * time.Second) + + cnt2, err := newContainer("ffmpeg", "dest", []string{ + "-i", "http://127.0.0.1:8888/test/stream/index.m3u8", + "-vframes", "1", + "-f", "image2", + "-y", "/dev/null", + }) + require.NoError(t, err) + defer cnt2.close() + require.Equal(t, 0, cnt2.wait()) +} + +func TestHLSServerAuth(t *testing.T) { + for _, mode := range []string{ + "internal", + "external", + } { + for _, result := range []string{ + "success", + "fail", + } { + t.Run(mode+"_"+result, func(t *testing.T) { + var conf string + if mode == "internal" { + conf = "paths:\n" + + " all:\n" + + " readUser: testreader\n" + + " readPass: testpass\n" + + " readIPs: [127.0.0.0/16]\n" + } else { + conf = "externalAuthenticationURL: http://127.0.0.1:9120/auth\n" + + "paths:\n" + + " all:\n" + } + + p, ok := newInstance(conf) + require.Equal(t, true, ok) + defer p.Close() + + var a *testHTTPAuthenticator + if mode == "external" { + var err error + a, err = newTestHTTPAuthenticator("publish") + require.NoError(t, err) + } + + cnt1, err := newContainer("ffmpeg", "source", []string{ + "-re", + "-stream_loop", "-1", + "-i", "emptyvideo.mkv", + "-c", "copy", + "-f", "rtsp", + "rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value", + }) + require.NoError(t, err) + defer cnt1.close() + + time.Sleep(1 * time.Second) + + if mode == "external" { + a.close() + var err error + a, err = newTestHTTPAuthenticator("read") + require.NoError(t, err) + defer a.close() + } + + var usr string + if result == "success" { + usr = "testreader" + } else { + usr = "testreader2" + } + + res, err := http.Get("http://" + usr + ":testpass@127.0.0.1:8888/teststream/index.m3u8?param=value") + require.NoError(t, err) + defer res.Body.Close() + + if result == "success" { + require.Equal(t, http.StatusOK, res.StatusCode) + } else { + require.Equal(t, http.StatusUnauthorized, res.StatusCode) + } + }) + } + } +} diff --git a/testimages/ffmpeg/Dockerfile b/internal/highleveltests/images/ffmpeg/Dockerfile similarity index 100% rename from testimages/ffmpeg/Dockerfile rename to internal/highleveltests/images/ffmpeg/Dockerfile diff --git a/testimages/ffmpeg/emptyvideo.mkv b/internal/highleveltests/images/ffmpeg/emptyvideo.mkv similarity index 100% rename from testimages/ffmpeg/emptyvideo.mkv rename to internal/highleveltests/images/ffmpeg/emptyvideo.mkv diff --git a/testimages/ffmpeg/emptyvideoaudio.mkv b/internal/highleveltests/images/ffmpeg/emptyvideoaudio.mkv similarity index 100% rename from testimages/ffmpeg/emptyvideoaudio.mkv rename to internal/highleveltests/images/ffmpeg/emptyvideoaudio.mkv diff --git a/testimages/ffmpeg/start.sh b/internal/highleveltests/images/ffmpeg/start.sh similarity index 100% rename from testimages/ffmpeg/start.sh rename to internal/highleveltests/images/ffmpeg/start.sh diff --git a/testimages/gstreamer/Dockerfile b/internal/highleveltests/images/gstreamer/Dockerfile similarity index 100% rename from testimages/gstreamer/Dockerfile rename to internal/highleveltests/images/gstreamer/Dockerfile diff --git a/testimages/gstreamer/emptyvideo.mkv b/internal/highleveltests/images/gstreamer/emptyvideo.mkv similarity index 100% rename from testimages/gstreamer/emptyvideo.mkv rename to internal/highleveltests/images/gstreamer/emptyvideo.mkv diff --git a/testimages/gstreamer/exitafterframe.c b/internal/highleveltests/images/gstreamer/exitafterframe.c similarity index 100% rename from testimages/gstreamer/exitafterframe.c rename to internal/highleveltests/images/gstreamer/exitafterframe.c diff --git a/testimages/gstreamer/start.sh b/internal/highleveltests/images/gstreamer/start.sh similarity index 100% rename from testimages/gstreamer/start.sh rename to internal/highleveltests/images/gstreamer/start.sh diff --git a/testimages/vlc/Dockerfile b/internal/highleveltests/images/vlc/Dockerfile similarity index 100% rename from testimages/vlc/Dockerfile rename to internal/highleveltests/images/vlc/Dockerfile diff --git a/testimages/vlc/start.sh b/internal/highleveltests/images/vlc/start.sh similarity index 100% rename from testimages/vlc/start.sh rename to internal/highleveltests/images/vlc/start.sh diff --git a/internal/highleveltests/rtsp_server_test.go b/internal/highleveltests/rtsp_server_test.go new file mode 100644 index 00000000..7481a6a7 --- /dev/null +++ b/internal/highleveltests/rtsp_server_test.go @@ -0,0 +1,234 @@ +//go:build enable_highlevel_tests +// +build enable_highlevel_tests + +package highleveltests + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestRTSPServerPublishRead(t *testing.T) { + for _, ca := range []struct { + publisherSoft string + publisherProto string + readerSoft string + readerProto string + }{ + {"ffmpeg", "udp", "ffmpeg", "udp"}, + {"ffmpeg", "udp", "ffmpeg", "multicast"}, + {"ffmpeg", "udp", "ffmpeg", "tcp"}, + {"ffmpeg", "udp", "gstreamer", "udp"}, + {"ffmpeg", "udp", "gstreamer", "multicast"}, + {"ffmpeg", "udp", "gstreamer", "tcp"}, + {"ffmpeg", "udp", "vlc", "udp"}, + {"ffmpeg", "udp", "vlc", "multicast"}, + {"ffmpeg", "udp", "vlc", "tcp"}, + {"ffmpeg", "tcp", "ffmpeg", "udp"}, + {"gstreamer", "udp", "ffmpeg", "udp"}, + {"gstreamer", "tcp", "ffmpeg", "udp"}, + {"ffmpeg", "tls", "ffmpeg", "tls"}, + {"ffmpeg", "tls", "gstreamer", "tls"}, + {"gstreamer", "tls", "ffmpeg", "tls"}, + } { + t.Run(ca.publisherSoft+"_"+ca.publisherProto+"_"+ + ca.readerSoft+"_"+ca.readerProto, func(t *testing.T) { + var proto string + var port string + if ca.publisherProto != "tls" { + proto = "rtsp" + port = "8554" + + p, ok := newInstance("rtmpDisable: yes\n" + + "hlsDisable: yes\n" + + "readTimeout: 20s\n" + + "paths:\n" + + " all:\n") + require.Equal(t, true, ok) + defer p.Close() + } else { + proto = "rtsps" + port = "8322" + + serverCertFpath, err := writeTempFile(serverCert) + require.NoError(t, err) + defer os.Remove(serverCertFpath) + + serverKeyFpath, err := writeTempFile(serverKey) + require.NoError(t, err) + defer os.Remove(serverKeyFpath) + + p, ok := newInstance("rtmpDisable: yes\n" + + "hlsDisable: yes\n" + + "readTimeout: 20s\n" + + "protocols: [tcp]\n" + + "encryption: \"yes\"\n" + + "serverCert: " + serverCertFpath + "\n" + + "serverKey: " + serverKeyFpath + "\n" + + "paths:\n" + + " all:\n") + require.Equal(t, true, ok) + defer p.Close() + } + + switch ca.publisherSoft { + case "ffmpeg": + ps := func() string { + switch ca.publisherProto { + case "udp", "tcp": + return ca.publisherProto + + default: // tls + return "tcp" + } + }() + + cnt1, err := newContainer("ffmpeg", "source", []string{ + "-re", + "-stream_loop", "-1", + "-i", "emptyvideo.mkv", + "-c", "copy", + "-f", "rtsp", + "-rtsp_transport", + ps, + proto + "://localhost:" + port + "/teststream", + }) + require.NoError(t, err) + defer cnt1.close() + + time.Sleep(1 * time.Second) + + case "gstreamer": + ps := func() string { + switch ca.publisherProto { + case "udp", "tcp": + return ca.publisherProto + + default: // tls + return "tcp" + } + }() + + cnt1, err := newContainer("gstreamer", "source", []string{ + "filesrc location=emptyvideo.mkv ! matroskademux ! video/x-h264 ! rtspclientsink " + + "location=" + proto + "://localhost:" + port + "/teststream " + + "protocols=" + ps + " 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": + ps := func() string { + switch ca.readerProto { + case "udp", "tcp": + return ca.publisherProto + + case "multicast": + return "udp_multicast" + + default: // tls + return "tcp" + } + }() + + cnt2, err := newContainer("ffmpeg", "dest", []string{ + "-rtsp_transport", ps, + "-i", proto + "://localhost:" + port + "/teststream", + "-vframes", "1", + "-f", "image2", + "-y", "/dev/null", + }) + require.NoError(t, err) + defer cnt2.close() + require.Equal(t, 0, cnt2.wait()) + + case "gstreamer": + ps := func() string { + switch ca.readerProto { + case "udp", "tcp": + return ca.publisherProto + + case "multicast": + return "udp-mcast" + + default: // tls + return "tcp" + } + }() + + cnt2, err := newContainer("gstreamer", "read", []string{ + "rtspsrc location=" + proto + "://127.0.0.1:" + port + "/teststream " + + "protocols=" + ps + " " + + "tls-validation-flags=0 latency=0 " + + "! application/x-rtp,media=video ! decodebin ! exitafterframe ! fakesink", + }) + require.NoError(t, err) + defer cnt2.close() + require.Equal(t, 0, cnt2.wait()) + + case "vlc": + args := []string{} + if ca.readerProto == "tcp" { + args = append(args, "--rtsp-tcp") + } + + ur := proto + "://localhost:" + port + "/teststream" + if ca.readerProto == "multicast" { + ur += "?vlcmulticast" + } + + args = append(args, ur) + cnt2, err := newContainer("vlc", "dest", args) + require.NoError(t, err) + defer cnt2.close() + require.Equal(t, 0, cnt2.wait()) + } + }) + } +} + +func TestRTSPServerRedirect(t *testing.T) { + p1, ok := newInstance("rtmpDisable: yes\n" + + "hlsDisable: yes\n" + + "paths:\n" + + " path1:\n" + + " source: redirect\n" + + " sourceRedirect: rtsp://localhost:8554/path2\n" + + " path2:\n") + require.Equal(t, true, ok) + defer p1.Close() + + cnt1, err := newContainer("ffmpeg", "source", []string{ + "-re", + "-stream_loop", "-1", + "-i", "emptyvideo.mkv", + "-c", "copy", + "-f", "rtsp", + "-rtsp_transport", "udp", + "rtsp://localhost:8554/path2", + }) + require.NoError(t, err) + defer cnt1.close() + + time.Sleep(1 * time.Second) + + cnt2, err := newContainer("ffmpeg", "dest", []string{ + "-rtsp_transport", "udp", + "-i", "rtsp://localhost:8554/path1", + "-vframes", "1", + "-f", "image2", + "-y", "/dev/null", + }) + require.NoError(t, err) + defer cnt2.close() + require.Equal(t, 0, cnt2.wait()) +} diff --git a/internal/highleveltests/tests_test.go b/internal/highleveltests/tests_test.go new file mode 100644 index 00000000..d7638ba0 --- /dev/null +++ b/internal/highleveltests/tests_test.go @@ -0,0 +1,142 @@ +//go:build enable_highlevel_tests +// +build enable_highlevel_tests + +package highleveltests + +import ( + "os" + "os/exec" + "strconv" + "time" + + "github.com/aler9/rtsp-simple-server/internal/core" +) + +var serverCert = []byte(`-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUXw1hEC3LFpTsllv7D3ARJyEq7sIwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMDEyMTMxNzQ0NThaFw0zMDEy +MTExNzQ0NThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDG8DyyS51810GsGwgWr5rjJK7OE1kTTLSNEEKax8Bj +zOyiaz8rA2JGl2VUEpi2UjDr9Cm7nd+YIEVs91IIBOb7LGqObBh1kGF3u5aZxLkv +NJE+HrLVvUhaDobK2NU+Wibqc/EI3DfUkt1rSINvv9flwTFu1qHeuLWhoySzDKEp +OzYxpFhwjVSokZIjT4Red3OtFz7gl2E6OAWe2qoh5CwLYVdMWtKR0Xuw3BkDPk9I +qkQKx3fqv97LPEzhyZYjDT5WvGrgZ1WDAN3booxXF3oA1H3GHQc4m/vcLatOtb8e +nI59gMQLEbnp08cl873bAuNuM95EZieXTHNbwUnq5iybAgMBAAGjUzBRMB0GA1Ud +DgQWBBQBKhJh8eWu0a4au9X/2fKhkFX2vjAfBgNVHSMEGDAWgBQBKhJh8eWu0a4a +u9X/2fKhkFX2vjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj +3aCW0YPKukYgVK9cwN0IbVy/D0C1UPT4nupJcy/E0iC7MXPZ9D/SZxYQoAkdptdO +xfI+RXkpQZLdODNx9uvV+cHyZHZyjtE5ENu/i5Rer2cWI/mSLZm5lUQyx+0KZ2Yu +tEI1bsebDK30msa8QSTn0WidW9XhFnl3gRi4wRdimcQapOWYVs7ih+nAlSvng7NI +XpAyRs8PIEbpDDBMWnldrX4TP6EWYUi49gCp8OUDRREKX3l6Ls1vZ02F34yHIt/7 +7IV/XSKG096bhW+icKBWV0IpcEsgTzPK1J1hMxgjhzIMxGboAeUU+kidthOob6Sd +XQxaORfgM//NzX9LhUPk +-----END CERTIFICATE----- +`) + +var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAxvA8skudfNdBrBsIFq+a4ySuzhNZE0y0jRBCmsfAY8zsoms/ +KwNiRpdlVBKYtlIw6/Qpu53fmCBFbPdSCATm+yxqjmwYdZBhd7uWmcS5LzSRPh6y +1b1IWg6GytjVPlom6nPxCNw31JLda0iDb7/X5cExbtah3ri1oaMkswyhKTs2MaRY +cI1UqJGSI0+EXndzrRc+4JdhOjgFntqqIeQsC2FXTFrSkdF7sNwZAz5PSKpECsd3 +6r/eyzxM4cmWIw0+Vrxq4GdVgwDd26KMVxd6ANR9xh0HOJv73C2rTrW/HpyOfYDE +CxG56dPHJfO92wLjbjPeRGYnl0xzW8FJ6uYsmwIDAQABAoIBACi0BKcyQ3HElSJC +kaAao+Uvnzh4yvPg8Nwf5JDIp/uDdTMyIEWLtrLczRWrjGVZYbsVROinP5VfnPTT +kYwkfKINj2u+gC6lsNuPnRuvHXikF8eO/mYvCTur1zZvsQnF5kp4GGwIqr+qoPUP +bB0UMndG1PdpoMryHe+JcrvTrLHDmCeH10TqOwMsQMLHYLkowvxwJWsmTY7/Qr5S +Wm3PPpOcW2i0uyPVuyuv4yD1368fqnqJ8QFsQp1K6QtYsNnJ71Hut1/IoxK/e6hj +5Z+byKtHVtmcLnABuoOT7BhleJNFBksX9sh83jid4tMBgci+zXNeGmgqo2EmaWAb +agQslkECgYEA8B1rzjOHVQx/vwSzDa4XOrpoHQRfyElrGNz9JVBvnoC7AorezBXQ +M9WTHQIFTGMjzD8pb+YJGi3gj93VN51r0SmJRxBaBRh1ZZI9kFiFzngYev8POgD3 +ygmlS3kTHCNxCK/CJkB+/jMBgtPj5ygDpCWVcTSuWlQFphePkW7jaaECgYEA1Blz +ulqgAyJHZaqgcbcCsI2q6m527hVr9pjzNjIVmkwu38yS9RTCgdlbEVVDnS0hoifl ++jVMEGXjF3xjyMvL50BKbQUH+KAa+V4n1WGlnZOxX9TMny8MBjEuSX2+362vQ3BX +4vOlX00gvoc+sY+lrzvfx/OdPCHQGVYzoKCxhLsCgYA07HcviuIAV/HsO2/vyvhp +xF5gTu+BqNUHNOZDDDid+ge+Jre2yfQLCL8VPLXIQW3Jff53IH/PGl+NtjphuLvj +7UDJvgvpZZuymIojP6+2c3gJ3CASC9aR3JBnUzdoE1O9s2eaoMqc4scpe+SWtZYf +3vzSZ+cqF6zrD/Rf/M35IQKBgHTU4E6ShPm09CcoaeC5sp2WK8OevZw/6IyZi78a +r5Oiy18zzO97U/k6xVMy6F+38ILl/2Rn31JZDVJujniY6eSkIVsUHmPxrWoXV1HO +y++U32uuSFiXDcSLarfIsE992MEJLSAynbF1Rsgsr3gXbGiuToJRyxbIeVy7gwzD +94TpAoGAY4/PejWQj9psZfAhyk5dRGra++gYRQ/gK1IIc1g+Dd2/BxbT/RHr05GK +6vwrfjsoRyMWteC1SsNs/CurjfQ/jqCfHNP5XPvxgd5Ec8sRJIiV7V5RTuWJsPu1 ++3K6cnKEyg+0ekYmLertRFIY6SwWmY1fyKgTvxudMcsBY7dC4xs= +-----END RSA PRIVATE KEY----- +`) + +func writeTempFile(byts []byte) (string, error) { + tmpf, err := os.CreateTemp(os.TempDir(), "rtsp-") + if err != nil { + return "", err + } + defer tmpf.Close() + + _, err = tmpf.Write(byts) + if err != nil { + return "", err + } + + return tmpf.Name(), nil +} + +func newInstance(conf string) (*core.Core, bool) { + if conf == "" { + return core.New([]string{}) + } + + tmpf, err := writeTempFile([]byte(conf)) + if err != nil { + return nil, false + } + defer os.Remove(tmpf) + + return core.New([]string{tmpf}) +} + +type container struct { + name string +} + +func newContainer(image string, name string, args []string) (*container, error) { + c := &container{ + name: name, + } + + exec.Command("docker", "kill", "rtsp-simple-server-test-"+name).Run() + exec.Command("docker", "wait", "rtsp-simple-server-test-"+name).Run() + + // --network=host is needed to test multicast + cmd := []string{ + "docker", "run", + "--network=host", + "--name=rtsp-simple-server-test-" + name, + "rtsp-simple-server-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", "rtsp-simple-server-test-"+c.name).Run() + exec.Command("docker", "wait", "rtsp-simple-server-test-"+c.name).Run() + exec.Command("docker", "rm", "rtsp-simple-server-test-"+c.name).Run() +} + +func (c *container) wait() int { + exec.Command("docker", "wait", "rtsp-simple-server-test-"+c.name).Run() + out, _ := exec.Command("docker", "inspect", "rtsp-simple-server-test-"+c.name, + "-f", "{{.State.ExitCode}}").Output() + code, _ := strconv.ParseInt(string(out[:len(out)-1]), 10, 64) + return int(code) +} diff --git a/scripts/test-highlevel.mk b/scripts/test-highlevel.mk new file mode 100644 index 00000000..5c8ac858 --- /dev/null +++ b/scripts/test-highlevel.mk @@ -0,0 +1,20 @@ +test-highlevel-nodocker: + go test -v -race -tags enable_highlevel_tests ./internal/highleveltests + +define DOCKERFILE_HIGHLEVEL_TEST +FROM $(BASE_IMAGE) +RUN apk add --no-cache make docker-cli gcc musl-dev +WORKDIR /s +COPY go.mod go.sum ./ +RUN go mod download +COPY . ./ +endef +export DOCKERFILE_HIGHLEVEL_TEST + +test-highlevel: + echo "$$DOCKERFILE_HIGHLEVEL_TEST" | docker build -q . -f - -t temp + docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock:ro \ + --network=host \ + temp \ + make test-highlevel-nodocker diff --git a/scripts/test.mk b/scripts/test.mk index ac8b5691..c43b87d1 100644 --- a/scripts/test.mk +++ b/scripts/test.mk @@ -1,7 +1,7 @@ define DOCKERFILE_TEST ARG ARCH FROM $$ARCH/$(BASE_IMAGE) -RUN apk add --no-cache make docker-cli gcc musl-dev +RUN apk add --no-cache make gcc musl-dev WORKDIR /s COPY go.mod go.sum ./ RUN go mod download @@ -11,8 +11,6 @@ export DOCKERFILE_TEST test: echo "$$DOCKERFILE_TEST" | docker build -q . -f - -t temp --build-arg ARCH=amd64 docker run --rm \ - --network=host \ - -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v $(PWD):/s \ temp \ make test-nodocker COVERAGE=1 @@ -20,8 +18,6 @@ test: test32: echo "$$DOCKERFILE_TEST" | docker build -q . -f - -t temp --build-arg ARCH=i386 docker run --rm \ - --network=host \ - -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v $(PWD):/s \ temp \ make test-nodocker COVERAGE=0 @@ -36,8 +32,6 @@ test-internal: $$(go list ./internal/... | grep -v /core) test-core: - $(foreach IMG,$(shell echo testimages/*/ | xargs -n1 basename), \ - docker build -q testimages/$(IMG) -t rtsp-simple-server-test-$(IMG)$(NL)) go test -v $(TEST_CORE_OPTS) ./internal/core test-nodocker: test-internal test-core