263 lines
7.1 KiB
Go
263 lines
7.1 KiB
Go
package engine
|
|
|
|
import (
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestYnBool(t *testing.T) {
|
|
if got := ynBool(true); got != "YES" {
|
|
t.Errorf("ynBool(true) = %q, want YES", got)
|
|
}
|
|
if got := ynBool(false); got != "NO" {
|
|
t.Errorf("ynBool(false) = %q, want NO", got)
|
|
}
|
|
}
|
|
|
|
func TestBitrateForQuality(t *testing.T) {
|
|
cases := map[string]int{
|
|
"2160p": 25_000_000,
|
|
"1080p": 6_000_000,
|
|
"720p": 3_500_000,
|
|
"480p": 1_500_000,
|
|
"unknown": 6_000_000,
|
|
"": 6_000_000,
|
|
}
|
|
for q, want := range cases {
|
|
if got := bitrateForQuality(q); got != want {
|
|
t.Errorf("bitrateForQuality(%q) = %d, want %d", q, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQualityHeight(t *testing.T) {
|
|
cases := map[string]int{
|
|
"2160p": 2160,
|
|
"1080p": 1080,
|
|
"720p": 720,
|
|
"480p": 480,
|
|
"": 0,
|
|
"unknown": 0,
|
|
}
|
|
for q, want := range cases {
|
|
if got := qualityHeight(q); got != want {
|
|
t.Errorf("qualityHeight(%q) = %d, want %d", q, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestScaledDimensions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
srcW, srcH, capH int
|
|
wantW, wantH int
|
|
}{
|
|
{"no_cap_returns_source", 1920, 1080, 0, 1920, 1080},
|
|
{"under_cap_returns_source", 1280, 720, 1080, 1280, 720},
|
|
{"4k_capped_to_1080", 3840, 2160, 1080, 1920, 1080},
|
|
{"even_width_stays_even", 1003, 750, 720, 962, 720},
|
|
{"odd_width_bumps_up", 1001, 700, 500, 716, 500},
|
|
{"invalid_returns_default", 0, 0, 0, 1920, 1080},
|
|
{"negative_returns_default", -10, 100, 0, 1920, 1080},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotW, gotH := scaledDimensions(tt.srcW, tt.srcH, tt.capH)
|
|
if gotW != tt.wantW || gotH != tt.wantH {
|
|
t.Errorf("scaledDimensions(%d,%d,%d) = (%d,%d), want (%d,%d)",
|
|
tt.srcW, tt.srcH, tt.capH, gotW, gotH, tt.wantW, tt.wantH)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShortHLSID(t *testing.T) {
|
|
if got := shortHLSID("abcdef1234567890"); got != "abcdef12" {
|
|
t.Errorf("got %q, want abcdef12", got)
|
|
}
|
|
if got := shortHLSID("short"); got != "short" {
|
|
t.Errorf("got %q, want short", got)
|
|
}
|
|
if got := shortHLSID(""); got != "" {
|
|
t.Errorf("got %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
func TestHlsTmpDirRoot(t *testing.T) {
|
|
root := hlsTmpDirRoot()
|
|
if root == "" {
|
|
t.Fatal("hlsTmpDirRoot returned empty")
|
|
}
|
|
if !strings.Contains(root, "hls-sessions") && !strings.Contains(root, "unarr-hls-sessions") {
|
|
t.Errorf("expected path to contain hls-sessions, got %q", root)
|
|
}
|
|
}
|
|
|
|
func TestRenderVideoPlaylist(t *testing.T) {
|
|
out := renderVideoPlaylist(10.0, 3)
|
|
required := []string{
|
|
"#EXTM3U",
|
|
"#EXT-X-VERSION:7",
|
|
"#EXT-X-PLAYLIST-TYPE:VOD",
|
|
`#EXT-X-MAP:URI="init.mp4"`,
|
|
"seg-0.m4s",
|
|
"seg-1.m4s",
|
|
"seg-2.m4s",
|
|
"#EXT-X-ENDLIST",
|
|
}
|
|
for _, want := range required {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("playlist missing %q\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRenderVideoPlaylistShortFinalSegment(t *testing.T) {
|
|
// 9.5s total, 4s segments → 3 segs of 4/4/1.5
|
|
out := renderVideoPlaylist(9.5, 3)
|
|
if !strings.Contains(out, "#EXTINF:1.500,") {
|
|
t.Errorf("expected final segment 1.5s in playlist, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestRenderMasterPlaylist(t *testing.T) {
|
|
probe := &StreamProbe{
|
|
Width: 1920,
|
|
Height: 1080,
|
|
SubtitleTracks: []ProbeSubtitleTrack{
|
|
{Index: 0, Lang: "es", Codec: "subrip", Title: "Spanish"},
|
|
{Index: 1, Lang: "en", Codec: "subrip", Title: "English", Forced: true},
|
|
{Index: 2, Lang: "ja", Codec: "hdmv_pgs_subtitle"}, // bitmap, skipped
|
|
},
|
|
}
|
|
out := renderMasterPlaylist(probe, "1080p")
|
|
|
|
if !strings.HasPrefix(out, "#EXTM3U") {
|
|
t.Errorf("must start with #EXTM3U, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "BANDWIDTH=6000000") {
|
|
t.Errorf("expected 1080p bandwidth, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "RESOLUTION=1920x1080") {
|
|
t.Errorf("expected 1920x1080 resolution, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, `SUBTITLES="subs"`) {
|
|
t.Errorf("expected subtitles group attached, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, `LANGUAGE="es"`) || !strings.Contains(out, `LANGUAGE="en"`) {
|
|
t.Errorf("expected text subs included, got:\n%s", out)
|
|
}
|
|
if strings.Contains(out, "hdmv_pgs") || strings.Contains(out, `LANGUAGE="ja"`) {
|
|
t.Errorf("bitmap subs should be excluded, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "(forced)") {
|
|
t.Errorf("expected forced suffix on English track, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestRenderMasterPlaylistNoSubs(t *testing.T) {
|
|
probe := &StreamProbe{Width: 1280, Height: 720}
|
|
out := renderMasterPlaylist(probe, "720p")
|
|
if strings.Contains(out, "SUBTITLES=") {
|
|
t.Errorf("no subs should produce no SUBTITLES attr, got:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "BANDWIDTH=3500000") {
|
|
t.Errorf("expected 720p bandwidth, got:\n%s", out)
|
|
}
|
|
}
|
|
|
|
func TestHLSSessionRegistry(t *testing.T) {
|
|
r := NewHLSSessionRegistry()
|
|
if r.Get("missing") != nil {
|
|
t.Error("Get on empty registry should return nil")
|
|
}
|
|
|
|
s1 := &HLSSession{cfg: HLSSessionConfig{SessionID: "a"}, lastTouch: time.Now()}
|
|
r.Register(s1)
|
|
if got := r.Get("a"); got != s1 {
|
|
t.Errorf("Get(a) = %v, want %v", got, s1)
|
|
}
|
|
|
|
// Registering a different session evicts (and Closes) the previous one.
|
|
s2 := &HLSSession{cfg: HLSSessionConfig{SessionID: "b"}, lastTouch: time.Now()}
|
|
r.Register(s2)
|
|
if r.Get("a") != nil {
|
|
t.Error("registering different session should evict prior entries")
|
|
}
|
|
if r.Get("b") != s2 {
|
|
t.Error("Get(b) should return s2")
|
|
}
|
|
|
|
r.Remove("b")
|
|
if r.Get("b") != nil {
|
|
t.Error("Remove should drop the session")
|
|
}
|
|
}
|
|
|
|
func TestHLSSessionAccessors(t *testing.T) {
|
|
probe := &StreamProbe{VideoCodec: "h264", Width: 1280, Height: 720}
|
|
s := &HLSSession{
|
|
cfg: HLSSessionConfig{SessionID: "abcdef1234"},
|
|
probe: probe,
|
|
manifestRoot: "MASTER",
|
|
manifestVideo: "VIDEO",
|
|
durationSec: 42.5,
|
|
lastTouch: time.Now().Add(-1 * time.Hour),
|
|
}
|
|
if s.MasterPlaylist() != "MASTER" {
|
|
t.Errorf("MasterPlaylist mismatch")
|
|
}
|
|
if s.VideoPlaylist() != "VIDEO" {
|
|
t.Errorf("VideoPlaylist mismatch")
|
|
}
|
|
if s.DurationSeconds() != 42.5 {
|
|
t.Errorf("DurationSeconds mismatch")
|
|
}
|
|
if s.Probe() != probe {
|
|
t.Errorf("Probe mismatch")
|
|
}
|
|
|
|
old := s.lastTouch
|
|
s.Touch()
|
|
if !s.lastTouch.After(old) {
|
|
t.Errorf("Touch did not advance lastTouch")
|
|
}
|
|
|
|
info := s.ProbeInfo()
|
|
if info["videoCodec"] != "h264" || info["width"] != 1280 {
|
|
t.Errorf("ProbeInfo missing fields: %v", info)
|
|
}
|
|
}
|
|
|
|
func TestHLSSessionProbeInfoNil(t *testing.T) {
|
|
s := &HLSSession{}
|
|
info := s.ProbeInfo()
|
|
if len(info) != 0 {
|
|
t.Errorf("nil probe should produce empty info, got %v", info)
|
|
}
|
|
}
|
|
|
|
func TestSweepIdle(t *testing.T) {
|
|
r := NewHLSSessionRegistry()
|
|
idleSession := &HLSSession{
|
|
cfg: HLSSessionConfig{SessionID: "old"},
|
|
lastTouch: time.Now().Add(-2 * hlsSessionTTL),
|
|
}
|
|
r.Register(idleSession)
|
|
if got := r.SweepIdle(); got != 1 {
|
|
t.Errorf("SweepIdle = %d, want 1", got)
|
|
}
|
|
if r.Get("old") != nil {
|
|
t.Errorf("idle session should have been removed")
|
|
}
|
|
}
|
|
|
|
func TestCleanupHLSOrphanDirsMissingRoot(t *testing.T) {
|
|
// Directory does not exist — should not error.
|
|
t.Setenv("XDG_CACHE_HOME", filepath.Join(t.TempDir(), "nonexistent"))
|
|
if err := CleanupHLSOrphanDirs(); err != nil {
|
|
t.Errorf("CleanupHLSOrphanDirs on missing root = %v, want nil", err)
|
|
}
|
|
}
|