2022-08-14 06:33:44 +02:00
package clients
2021-04-02 01:22:00 +02:00
import (
"fmt"
"log"
"os"
"path/filepath"
2022-08-14 06:33:44 +02:00
"strings"
2021-04-02 01:22:00 +02:00
2022-08-14 06:33:44 +02:00
"github.com/jesseduffield/generics/slices"
2021-04-02 01:22:00 +02:00
"github.com/jesseduffield/gocui"
2022-10-15 18:47:55 +02:00
"github.com/jesseduffield/lazycore/pkg/utils"
2021-11-01 00:35:54 +02:00
"github.com/jesseduffield/lazygit/pkg/gui"
2021-07-27 15:00:37 +02:00
"github.com/jesseduffield/lazygit/pkg/gui/style"
2022-08-12 01:24:39 +02:00
"github.com/jesseduffield/lazygit/pkg/integration/components"
2022-08-14 03:24:07 +02:00
"github.com/jesseduffield/lazygit/pkg/integration/tests"
2021-04-02 01:22:00 +02:00
"github.com/jesseduffield/lazygit/pkg/secureexec"
)
2022-08-13 04:56:04 +02:00
// This program lets you run integration tests from a TUI. See pkg/integration/README.md for more info.
2021-04-06 01:02:01 +02:00
2022-08-14 12:47:09 +02:00
var SLOW_KEY_PRESS_DELAY = 300
2022-08-14 06:33:44 +02:00
func RunTUI ( ) {
2022-10-15 18:47:55 +02:00
rootDir := utils . GetLazyRootDirectory ( )
2021-04-02 01:22:00 +02:00
testDir := filepath . Join ( rootDir , "test" , "integration" )
2022-08-14 06:33:44 +02:00
app := newApp ( testDir )
2021-04-02 01:22:00 +02:00
app . loadTests ( )
2021-11-01 00:35:54 +02:00
g , err := gocui . NewGui ( gocui . OutputTrue , false , gocui . NORMAL , false , gui . RuneReplacements )
2021-04-05 02:20:02 +02:00
if err != nil {
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
g . Cursor = false
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
app . g = g
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
g . SetManagerFunc ( app . layout )
2021-04-02 01:22:00 +02:00
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "list" , gocui . KeyArrowUp , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
2021-04-05 02:20:02 +02:00
if app . itemIdx > 0 {
app . itemIdx --
2021-04-02 01:22:00 +02:00
}
2021-10-30 09:17:32 +02:00
listView , err := g . View ( "list" )
if err != nil {
return err
}
listView . FocusPoint ( 0 , app . itemIdx )
2021-04-05 02:20:02 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2022-08-14 06:33:44 +02:00
if err := g . SetKeybinding ( "list" , gocui . KeyArrowDown , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
if app . itemIdx < len ( app . filteredTests ) - 1 {
app . itemIdx ++
}
listView , err := g . View ( "list" )
if err != nil {
return err
}
listView . FocusPoint ( 0 , app . itemIdx )
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "list" , gocui . KeyCtrlC , gocui . ModNone , quit ) ; err != nil {
2021-04-05 02:20:02 +02:00
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "list" , 'q' , gocui . ModNone , quit ) ; err != nil {
2021-04-05 02:20:02 +02:00
log . Panicln ( err )
}
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "list" , 's' , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
2022-01-15 11:24:19 +02:00
currentTest := app . getCurrentTest ( )
if currentTest == nil {
return nil
}
2022-12-20 13:40:14 +02:00
suspendAndRunTest ( currentTest , true , 0 )
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "list" , gocui . KeyEnter , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
2021-04-05 02:20:02 +02:00
currentTest := app . getCurrentTest ( )
if currentTest == nil {
return nil
2021-04-02 01:22:00 +02:00
}
2022-12-20 13:40:14 +02:00
suspendAndRunTest ( currentTest , false , 0 )
2021-04-05 12:49:32 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "list" , 't' , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
2022-01-26 00:07:56 +02:00
currentTest := app . getCurrentTest ( )
if currentTest == nil {
return nil
}
2022-12-20 13:40:14 +02:00
suspendAndRunTest ( currentTest , false , SLOW_KEY_PRESS_DELAY )
2022-01-26 00:07:56 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "list" , 'o' , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
2021-04-05 02:20:02 +02:00
currentTest := app . getCurrentTest ( )
if currentTest == nil {
return nil
2021-04-02 01:22:00 +02:00
}
2022-08-09 13:27:12 +02:00
cmd := secureexec . Command ( "sh" , "-c" , fmt . Sprintf ( "code -r pkg/integration/tests/%s" , currentTest . Name ( ) ) )
2021-04-05 02:20:02 +02:00
if err := cmd . Run ( ) ; err != nil {
return err
}
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2022-08-09 13:27:12 +02:00
if err := g . SetKeybinding ( "list" , 'O' , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
2021-04-05 02:20:02 +02:00
currentTest := app . getCurrentTest ( )
if currentTest == nil {
2021-04-02 01:22:00 +02:00
return nil
}
2022-08-09 13:27:12 +02:00
cmd := secureexec . Command ( "sh" , "-c" , fmt . Sprintf ( "code test/integration_new/%s" , currentTest . Name ( ) ) )
2021-04-05 02:20:02 +02:00
if err := cmd . Run ( ) ; err != nil {
return err
}
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2022-08-09 13:27:12 +02:00
if err := g . SetKeybinding ( "list" , '/' , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
app . filtering = true
2021-04-05 02:20:02 +02:00
if _ , err := g . SetCurrentView ( "editor" ) ; err != nil {
return err
}
editorView , err := g . View ( "editor" )
if err != nil {
return err
}
editorView . Clear ( )
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2022-08-09 13:27:12 +02:00
// not using the editor yet, but will use it to help filter the list
2022-06-13 03:01:26 +02:00
if err := g . SetKeybinding ( "editor" , gocui . KeyEsc , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
2022-08-09 13:27:12 +02:00
app . filtering = false
2021-04-05 02:20:02 +02:00
if _ , err := g . SetCurrentView ( "list" ) ; err != nil {
return err
2021-04-02 01:22:00 +02:00
}
2022-08-14 12:32:17 +02:00
app . filteredTests = app . allTests
2022-08-14 06:33:44 +02:00
app . renderTests ( )
app . editorView . TextArea . Clear ( )
app . editorView . Clear ( )
app . editorView . Reset ( )
return nil
} ) ; err != nil {
log . Panicln ( err )
}
if err := g . SetKeybinding ( "editor" , gocui . KeyEnter , gocui . ModNone , func ( * gocui . Gui , * gocui . View ) error {
app . filtering = false
if _ , err := g . SetCurrentView ( "list" ) ; err != nil {
return err
}
app . renderTests ( )
2021-04-05 02:20:02 +02:00
return nil
} ) ; err != nil {
log . Panicln ( err )
}
2021-04-02 01:22:00 +02:00
2021-04-05 02:20:02 +02:00
err = g . MainLoop ( )
g . Close ( )
2022-01-15 06:18:16 +02:00
switch err {
case gocui . ErrQuit :
return
default :
log . Panicln ( err )
2021-04-05 02:20:02 +02:00
}
}
2021-04-02 01:22:00 +02:00
2022-08-14 06:33:44 +02:00
type app struct {
2022-08-14 12:32:17 +02:00
allTests [ ] * components . IntegrationTest
2022-08-14 06:33:44 +02:00
filteredTests [ ] * components . IntegrationTest
itemIdx int
testDir string
filtering bool
g * gocui . Gui
listView * gocui . View
editorView * gocui . View
}
func newApp ( testDir string ) * app {
2022-08-14 12:32:17 +02:00
return & app { testDir : testDir , allTests : tests . GetTests ( ) }
2022-08-14 06:33:44 +02:00
}
func ( self * app ) getCurrentTest ( ) * components . IntegrationTest {
self . adjustCursor ( )
if len ( self . filteredTests ) > 0 {
return self . filteredTests [ self . itemIdx ]
}
return nil
}
func ( self * app ) loadTests ( ) {
2022-08-14 12:32:17 +02:00
self . filteredTests = self . allTests
2022-08-14 06:33:44 +02:00
self . adjustCursor ( )
}
func ( self * app ) adjustCursor ( ) {
self . itemIdx = utils . Clamp ( self . itemIdx , 0 , len ( self . filteredTests ) - 1 )
}
func ( self * app ) filterWithString ( needle string ) {
if needle == "" {
2022-08-14 12:32:17 +02:00
self . filteredTests = self . allTests
2022-08-14 06:33:44 +02:00
} else {
2022-08-14 12:32:17 +02:00
self . filteredTests = slices . Filter ( self . allTests , func ( test * components . IntegrationTest ) bool {
2022-08-14 06:33:44 +02:00
return strings . Contains ( test . Name ( ) , needle )
} )
}
self . renderTests ( )
self . g . Update ( func ( g * gocui . Gui ) error { return nil } )
}
func ( self * app ) renderTests ( ) {
self . listView . Clear ( )
for _ , test := range self . filteredTests {
fmt . Fprintln ( self . listView , test . Name ( ) )
}
}
func ( self * app ) wrapEditor ( f func ( v * gocui . View , key gocui . Key , ch rune , mod gocui . Modifier ) bool ) func ( v * gocui . View , key gocui . Key , ch rune , mod gocui . Modifier ) bool {
return func ( v * gocui . View , key gocui . Key , ch rune , mod gocui . Modifier ) bool {
matched := f ( v , key , ch , mod )
if matched {
self . filterWithString ( v . TextArea . GetContent ( ) )
}
return matched
}
}
2022-12-20 13:40:14 +02:00
func suspendAndRunTest ( test * components . IntegrationTest , sandbox bool , keyPressDelay int ) {
2021-04-05 04:19:45 +02:00
if err := gocui . Screen . Suspend ( ) ; err != nil {
panic ( err )
}
2021-04-05 02:20:02 +02:00
2022-12-20 13:40:14 +02:00
runTuiTest ( test , sandbox , keyPressDelay )
2021-04-05 02:20:02 +02:00
2021-07-27 15:00:37 +02:00
fmt . Fprintf ( os . Stdout , "\n%s" , style . FgGreen . Sprint ( "press enter to return" ) )
2021-04-05 02:20:02 +02:00
fmt . Scanln ( ) // wait for enter press
2021-04-05 04:19:45 +02:00
if err := gocui . Screen . Resume ( ) ; err != nil {
panic ( err )
}
2021-04-02 01:22:00 +02:00
}
2022-08-14 06:33:44 +02:00
func ( self * app ) layout ( g * gocui . Gui ) error {
2021-04-02 01:22:00 +02:00
maxX , maxY := g . Size ( )
descriptionViewHeight := 7
keybindingsViewHeight := 3
editorViewHeight := 3
2022-08-14 06:33:44 +02:00
if ! self . filtering {
2021-04-02 01:22:00 +02:00
editorViewHeight = 0
} else {
descriptionViewHeight = 0
keybindingsViewHeight = 0
}
2022-08-14 06:33:44 +02:00
g . Cursor = self . filtering
2021-04-02 01:22:00 +02:00
g . FgColor = gocui . ColorGreen
listView , err := g . SetView ( "list" , 0 , 0 , maxX - 1 , maxY - descriptionViewHeight - keybindingsViewHeight - editorViewHeight - 1 , 0 )
if err != nil {
if err . Error ( ) != "unknown view" {
return err
}
2022-08-14 06:33:44 +02:00
if self . listView == nil {
self . listView = listView
2021-04-02 01:22:00 +02:00
}
2022-08-14 06:33:44 +02:00
listView . Highlight = true
self . renderTests ( )
2021-04-02 01:22:00 +02:00
listView . Title = "Tests"
listView . FgColor = gocui . ColorDefault
if _ , err := g . SetCurrentView ( "list" ) ; err != nil {
return err
}
}
descriptionView , err := g . SetViewBeneath ( "description" , "list" , descriptionViewHeight )
if err != nil {
if err . Error ( ) != "unknown view" {
return err
}
descriptionView . Title = "Test description"
descriptionView . Wrap = true
descriptionView . FgColor = gocui . ColorDefault
}
keybindingsView , err := g . SetViewBeneath ( "keybindings" , "description" , keybindingsViewHeight )
if err != nil {
if err . Error ( ) != "unknown view" {
return err
}
keybindingsView . Title = "Keybindings"
keybindingsView . Wrap = true
keybindingsView . FgColor = gocui . ColorDefault
2022-08-09 13:27:12 +02:00
fmt . Fprintln ( keybindingsView , "up/down: navigate, enter: run test, t: run test slow, s: sandbox, o: open test file, shift+o: open test snapshot directory, forward-slash: filter" )
2021-04-02 01:22:00 +02:00
}
editorView , err := g . SetViewBeneath ( "editor" , "keybindings" , editorViewHeight )
if err != nil {
if err . Error ( ) != "unknown view" {
return err
}
2022-08-14 06:33:44 +02:00
if self . editorView == nil {
self . editorView = editorView
}
2022-08-09 13:27:12 +02:00
editorView . Title = "Filter"
2021-04-02 01:22:00 +02:00
editorView . FgColor = gocui . ColorDefault
editorView . Editable = true
2022-08-14 06:33:44 +02:00
editorView . Editor = gocui . EditorFunc ( self . wrapEditor ( gocui . SimpleEditor ) )
2021-04-02 01:22:00 +02:00
}
2022-08-14 06:33:44 +02:00
currentTest := self . getCurrentTest ( )
2021-04-02 01:22:00 +02:00
if currentTest == nil {
return nil
}
descriptionView . Clear ( )
2022-08-09 13:27:12 +02:00
fmt . Fprint ( descriptionView , currentTest . Description ( ) )
2021-04-02 01:22:00 +02:00
return nil
}
func quit ( g * gocui . Gui , v * gocui . View ) error {
return gocui . ErrQuit
}
2022-08-14 06:33:44 +02:00
2022-12-20 13:40:14 +02:00
func runTuiTest ( test * components . IntegrationTest , sandbox bool , keyPressDelay int ) {
2022-08-14 06:33:44 +02:00
err := components . RunTests (
[ ] * components . IntegrationTest { test } ,
log . Printf ,
runCmdInTerminal ,
runAndPrintError ,
2022-12-20 13:40:14 +02:00
sandbox ,
2022-08-14 06:33:44 +02:00
keyPressDelay ,
2022-09-17 07:31:46 +02:00
1 ,
2022-08-14 06:33:44 +02:00
)
if err != nil {
log . Println ( err . Error ( ) )
}
}
2022-08-22 10:51:34 +02:00
func runAndPrintError ( test * components . IntegrationTest , f func ( ) error ) {
if err := f ( ) ; err != nil {
log . Println ( err . Error ( ) )
}
}