//go:build integration package app_test import ( "context" "sync" "testing" "time" "git.netflux.io/rob/octoplex/internal/app" "git.netflux.io/rob/octoplex/internal/config" "git.netflux.io/rob/octoplex/internal/domain" "git.netflux.io/rob/octoplex/internal/terminal" "git.netflux.io/rob/octoplex/internal/testhelpers" dockerclient "github.com/docker/docker/client" "github.com/gdamore/tcell/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestIntegration(t *testing.T) { ctx, cancel := context.WithTimeout(t.Context(), 2*time.Minute) defer cancel() logger := testhelpers.NewTestLogger().With("component", "integration") dockerClient, err := dockerclient.NewClientWithOpts(dockerclient.FromEnv) require.NoError(t, err) // Fetching the screen contents is tricky at this level of the test pyramid, // because we need to: // // 1. Somehow capture the screen contents, which is only available via the // tcell.SimulationScreen, and... // 2. Do so without triggering data races. // // We can achieve this by passing a channel into the terminal actor, which // will send screen captures after each render. This can be stored locally // and asserted against when needed. var ( screenCells []tcell.SimCell screenWidth int screenMu sync.Mutex ) getContents := func() []string { screenMu.Lock() defer screenMu.Unlock() var lines []string for n, _ := range screenCells { y := n / screenWidth if y > len(lines)-1 { lines = append(lines, "") } lines[y] += string(screenCells[n].Runes[0]) } return lines } screen := tcell.NewSimulationScreen("") screenCaptureC := make(chan terminal.ScreenCapture, 1) go func() { for { select { case <-ctx.Done(): return case capture := <-screenCaptureC: screenMu.Lock() screenCells = capture.Cells screenWidth = capture.Width screenMu.Unlock() } } }() done := make(chan struct{}) go func() { err := app.Run(ctx, app.RunParams{ Config: config.Config{ // We use the mediaserver as the destination server, just because it is // reachable from the docker network via mediaserver:1935. Destinations: []config.Destination{ { Name: "Local server 1", URL: "rtmp://mediaserver:1935/live/dest1", }, { Name: "Local server 2", URL: "rtmp://mediaserver:1935/live/dest2", }, }, }, DockerClient: dockerClient, Screen: &terminal.Screen{ Screen: screen, Width: 160, Height: 25, CaptureC: screenCaptureC, }, ClipboardAvailable: false, BuildInfo: domain.BuildInfo{Version: "0.0.1", GoVersion: "go1.16.3"}, Logger: logger, }) require.NoError(t, err) done <- struct{}{} }() // Wait for mediaserver container to start: time.Sleep(5 * time.Second) // Start streaming a test video to the app: testhelpers.StreamFLV(t, "rtmp://localhost:1935/live") time.Sleep(10 * time.Second) // Start destinations: screen.PostEvent(tcell.NewEventKey(tcell.KeyRune, ' ', tcell.ModNone)) screen.PostEvent(tcell.NewEventKey(tcell.KeyDown, ' ', tcell.ModNone)) screen.PostEvent(tcell.NewEventKey(tcell.KeyRune, ' ', tcell.ModNone)) time.Sleep(15 * time.Second) contents := getContents() require.True(t, len(contents) > 2, "expected at least 3 lines of output") require.Contains(t, contents[2], "Status receiving", "expected mediaserver status to be receiving") require.Contains(t, contents[3], "Tracks H264", "expected mediaserver tracks to be H264") require.Contains(t, contents[4], "Health healthy", "expected mediaserver to be healthy") require.Contains(t, contents[2], "Local server 1", "expected local server 1 to be present") assert.Contains(t, contents[2], "sending", "expected local server 1 to be sending") assert.Contains(t, contents[2], "healthy", "expected local server 1 to be healthy") require.Contains(t, contents[3], "Local server 2", "expected local server 2 to be present") assert.Contains(t, contents[3], "sending", "expected local server 2 to be sending") assert.Contains(t, contents[3], "healthy", "expected local server 2 to be healthy") // Stop destinations: screen.PostEvent(tcell.NewEventKey(tcell.KeyRune, ' ', tcell.ModNone)) screen.PostEvent(tcell.NewEventKey(tcell.KeyUp, ' ', tcell.ModNone)) screen.PostEvent(tcell.NewEventKey(tcell.KeyRune, ' ', tcell.ModNone)) time.Sleep(10 * time.Second) contents = getContents() require.True(t, len(contents) > 2, "expected at least 3 lines of output") require.Contains(t, contents[2], "Local server 1", "expected local server 1 to be present") assert.Contains(t, contents[2], "exited", "expected local server 1 to have exited") require.Contains(t, contents[3], "Local server 2", "expected local server 2 to be present") assert.Contains(t, contents[3], "exited", "expected local server 2 to have exited") // TODO: // - Source error // - Destination error // - Additional features (copy URL, etc.) cancel() <-done }