Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[deps] tview-command latest, make main more testable, add tests #63

Merged
merged 4 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ require (
)

require (
github.com/spezifisch/tview-command v0.0.0-20240828122932-69552353924a
github.com/spezifisch/tview-command v0.0.0-20241013143719-94366d6323e2
github.com/stretchr/testify v1.9.0
github.com/supersonic-app/go-mpv v0.1.0
)

require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
Expand All @@ -26,6 +28,7 @@ require (
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spezifisch/tview-command v0.0.0-20240828122932-69552353924a h1:xgFDwjebiXdBQwBd9mBvAgw6OHC/FPLwXouYrDCgDMc=
github.com/spezifisch/tview-command v0.0.0-20240828122932-69552353924a/go.mod h1:BmHPVRuS00KaY6eP3VAoPJVlfN0Fulajx3Dw9CwKfFw=
github.com/spezifisch/tview-command v0.0.0-20241013143719-94366d6323e2 h1:rhNWDM0v9HbwuF5I8wvOW3bsCdiZ1KRnp7uvhp3Jw+Y=
github.com/spezifisch/tview-command v0.0.0-20241013143719-94366d6323e2/go.mod h1:BmHPVRuS00KaY6eP3VAoPJVlfN0Fulajx3Dw9CwKfFw=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
Expand Down
132 changes: 83 additions & 49 deletions stmps.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,42 @@ import (
"github.com/spezifisch/stmps/mpvplayer"
"github.com/spezifisch/stmps/remote"
"github.com/spezifisch/stmps/subsonic"
"github.com/spezifisch/tview-command/keybinding"
tviewcommand "github.com/spezifisch/tview-command"
"github.com/spf13/viper"
)

func readConfig() {
var osExit = os.Exit // A variable to allow mocking os.Exit in tests
var headlessMode bool // This can be set to true during tests
var testMode bool // This can be set to true during tests, too

func readConfig(configFile *string) error {
required_properties := []string{"auth.username", "auth.password", "server.host"}

viper.SetConfigName("stmp")
viper.SetConfigType("toml")
viper.AddConfigPath("$HOME/.config/stmp")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if configFile != nil {
// use custom config file
viper.SetConfigFile(*configFile)
} else {
// lookup default dirs
viper.SetConfigName("stmp") // TODO this should be stmps
viper.SetConfigType("toml")
viper.AddConfigPath("$HOME/.config/stmp") // TODO this should be stmps
viper.AddConfigPath(".")
}

// read it
err := viper.ReadInConfig()
if err != nil {
fmt.Printf("Config file error: %s \n", err)
os.Exit(1)
return fmt.Errorf("Config file error: %s\n", err)
}

// validate
for _, prop := range required_properties {
if !viper.IsSet(prop) {
fmt.Printf("Config property %s is required\n", prop)
return fmt.Errorf("Config property %s is required\n", prop)
}
}

return nil
}

// parseConfig takes the first non-flag arguments from flags and parses it
Expand All @@ -57,23 +70,22 @@ func parseConfig() {
u.User = nil
viper.Set("server.host", u.String())
} else {
fmt.Printf("Invalid server format; must be a valid URL: http[s]://[user:pass@]server:port")
fmt.Printf("USAGE: %s <args> [http[s]://[user:pass@]server:port]\n", os.Args[0])
flag.Usage()
os.Exit(1)
fmt.Printf("Invalid server format; must be a valid URL!")
fmt.Printf("Usage: %s <args> [http[s]://[user:pass@]server:port]\n", os.Args[0])
osExit(1)
}
}

// initCommandHandler sets up tview-command as main input handler
func initCommandHandler(logger *logger.Logger) {
keybinding.SetLogHandler(func(msg string) {
tviewcommand.SetLogHandler(func(msg string) {
logger.Print(msg)
})

configPath := "HACK.commands.toml"

// Load the configuration file
config, err := keybinding.LoadConfig(configPath)
config, err := tviewcommand.LoadConfig(configPath)
if err != nil || config == nil {
logger.PrintError("Failed to load command-shortcut config", err)
}
Expand All @@ -83,17 +95,19 @@ func initCommandHandler(logger *logger.Logger) {
}

func main() {
// parse flags and config
help := flag.Bool("help", false, "Print usage")
enableMpris := flag.Bool("mpris", false, "Enable MPRIS2")
list := flag.Bool("list", false, "list server data")
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to `file`")
memprofile := flag.String("memprofile", "", "write memory profile to `file`")
configFile := flag.String("config", "c", "use config `file`")

flag.Parse()
if *help {
fmt.Printf("USAGE: %s <args> [[user:pass@]server:port]\n", os.Args[0])
flag.Usage()
os.Exit(0)
osExit(0)
}

// cpu/memprofile code straight from https://pkg.go.dev/runtime/pprof
Expand All @@ -109,15 +123,58 @@ func main() {
defer pprof.StopCPUProfile()
}

// config gathering
if len(flag.Args()) > 0 {
parseConfig()
} else {
readConfig()
}

if err := readConfig(configFile); err != nil {
if configFile == nil {
fmt.Fprintf(os.Stderr, "Failed to read configuration: configuration file is nil\n")
} else {
fmt.Fprintf(os.Stderr, "Failed to read configuration from file '%s': %v\n", *configFile, err)
}
osExit(1)
}

logger := logger.Init()
initCommandHandler(logger)

// init mpv engine
player, err := mpvplayer.NewPlayer(logger)
if err != nil {
fmt.Println("Unable to initialize mpv. Is mpv installed?")
osExit(1)
}

var mprisPlayer *remote.MprisPlayer
// init mpris2 player control (linux only but fails gracefully on other systems)
if *enableMpris {
mprisPlayer, err = remote.RegisterMprisPlayer(player, logger)
if err != nil {
fmt.Printf("Unable to register MPRIS with DBUS: %s\n", err)
fmt.Println("Try running without MPRIS")
osExit(1)
}
defer mprisPlayer.Close()
}

// init macos mediaplayer control
if runtime.GOOS == "darwin" {
if err = remote.RegisterMPMediaHandler(player, logger); err != nil {
fmt.Printf("Unable to initialize MediaPlayer bindings: %s\n", err)
osExit(1)
} else {
logger.Print("MacOS MediaPlayer registered")
}
}

if testMode {
fmt.Println("Running in test mode for testing.")
osExit(0)
return
}

connection := subsonic.Init(logger)
connection.SetClientInfo(clientName, clientVersion)
connection.Username = viper.GetString("auth.username")
Expand All @@ -130,7 +187,7 @@ func main() {
indexResponse, err := connection.GetIndexes()
if err != nil {
fmt.Printf("Error fetching playlists from server: %s\n", err)
os.Exit(1)
osExit(1)
}

if *list {
Expand All @@ -151,7 +208,7 @@ func main() {
playlistResponse, err := connection.GetPlaylists()
if err != nil {
fmt.Printf("Error fetching indexes from server: %s\n", err)
os.Exit(1)
osExit(1)
}
fmt.Printf(" Directory: %s\n", playlistResponse.Directory.Name)
fmt.Printf(" Status: %s\n", playlistResponse.Status)
Expand All @@ -166,36 +223,13 @@ func main() {
fmt.Printf(" %s\n", pl.Name)
}

os.Exit(0)
}

// init mpv engine
player, err := mpvplayer.NewPlayer(logger)
if err != nil {
fmt.Println("Unable to initialize mpv. Is mpv installed?")
os.Exit(1)
}

var mprisPlayer *remote.MprisPlayer
// init mpris2 player control (linux only but fails gracefully on other systems)
if *enableMpris {
mprisPlayer, err = remote.RegisterMprisPlayer(player, logger)
if err != nil {
fmt.Printf("Unable to register MPRIS with DBUS: %s\n", err)
fmt.Println("Try running without MPRIS")
os.Exit(1)
}
defer mprisPlayer.Close()
osExit(0)
}

// init macos mediaplayer control
if runtime.GOOS == "darwin" {
if err = remote.RegisterMPMediaHandler(player, logger); err != nil {
fmt.Printf("Unable to initialize MediaPlayer bindings: %s\n", err)
os.Exit(1)
} else {
logger.Print("MacOS MediaPlayer registered")
}
if headlessMode {
fmt.Println("Running in headless mode for testing.")
osExit(0)
return
}

ui := InitGui(&indexResponse.Indexes.Index,
Expand Down
56 changes: 56 additions & 0 deletions stmps_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"os"
"runtime"
"testing"

"github.com/spezifisch/stmps/logger"
"github.com/spezifisch/stmps/mpvplayer"
"github.com/stretchr/testify/assert"
)

// Test initialization of the player
func TestPlayerInitialization(t *testing.T) {
logger := logger.Init()
player, err := mpvplayer.NewPlayer(logger)
assert.NoError(t, err, "Player initialization should not return an error")
assert.NotNil(t, player, "Player should be initialized")
}

func TestMainWithoutTUI(t *testing.T) {
// Mock osExit to prevent actual exit during test
exitCalled := false
osExit = func(code int) {
exitCalled = true

if code != 0 {
// Capture and print the stack trace
stackBuf := make([]byte, 1024)
stackSize := runtime.Stack(stackBuf, false)
stackTrace := string(stackBuf[:stackSize])

// Print the stack trace with new lines only
t.Fatalf("Unexpected exit with code: %d\nStack trace:\n%s\n", code, stackTrace)
}
// Since we don't abort execution here, we will run main() until the end or a panic.
}
headlessMode = true
testMode = true

// Restore patches after the test
defer func() {
osExit = os.Exit
headlessMode = false
testMode = false
}()

// Set command-line arguments to trigger the help flag
os.Args = []string{"cmd", "--config=stmp-example.toml", "--help"}

main()

if !exitCalled {
t.Fatalf("osExit was not called")
}
}