mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-05 00:59:19 +02:00
support tcell simulation screen
This commit is contained in:
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"github.com/creack/pty"
|
"github.com/creack/pty"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// To run an integration test, e.g. for test 'commit', go:
|
// To run an integration test, e.g. for test 'commit', go:
|
||||||
@ -209,19 +211,19 @@ func Test() error {
|
|||||||
speeds := getTestSpeeds(test.Speed, updateSnapshots)
|
speeds := getTestSpeeds(test.Speed, updateSnapshots)
|
||||||
|
|
||||||
for i, speed := range speeds {
|
for i, speed := range speeds {
|
||||||
// t.Logf("%s: attempting test at speed %d\n", test.Name, speed)
|
log.Printf("%s: attempting test at speed %d\n", test.Name, speed)
|
||||||
|
|
||||||
testPath := filepath.Join(testDir, test.Name)
|
testPath := filepath.Join(testDir, test.Name)
|
||||||
actualDir := filepath.Join(testPath, "actual")
|
actualDir := filepath.Join(testPath, "actual")
|
||||||
expectedDir := filepath.Join(testPath, "expected")
|
expectedDir := filepath.Join(testPath, "expected")
|
||||||
// t.Logf("testPath: %s, actualDir: %s, expectedDir: %s", testPath, actualDir, expectedDir)
|
log.Printf("testPath: %s, actualDir: %s, expectedDir: %s", testPath, actualDir, expectedDir)
|
||||||
findOrCreateDir(testPath)
|
findOrCreateDir(testPath)
|
||||||
|
|
||||||
prepareIntegrationTestDir(actualDir)
|
prepareIntegrationTestDir(actualDir)
|
||||||
|
|
||||||
err := createFixture(testPath, actualDir)
|
err := createFixture(testPath, actualDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
}
|
||||||
|
|
||||||
runLazygit(testPath, rootDir, record, speed)
|
runLazygit(testPath, rootDir, record, speed)
|
||||||
@ -268,13 +270,14 @@ func Test() error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if expected == actual {
|
if expected == actual {
|
||||||
// t.Logf("%s: success at speed %d\n", test.Name, speed)
|
log.Printf("%s: success at speed %d\n", test.Name, speed)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the snapshots and we haven't tried all playback speeds different we'll retry at a slower speed
|
// if the snapshots and we haven't tried all playback speeds different we'll retry at a slower speed
|
||||||
if i == len(speeds)-1 {
|
if i == len(speeds)-1 {
|
||||||
// assert.Equal(t, expected, actual, fmt.Sprintf("expected:\n%s\nactual:\n%s\n", expected, actual))
|
assert.Equal(MockTestingT{}, expected, actual, fmt.Sprintf("expected:\n%s\nactual:\n%s\n", expected, actual))
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,6 +285,12 @@ func Test() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MockTestingT struct{}
|
||||||
|
|
||||||
|
func (t MockTestingT) Errorf(format string, args ...interface{}) {
|
||||||
|
fmt.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
func createFixture(testPath, actualDir string) error {
|
func createFixture(testPath, actualDir string) error {
|
||||||
osCommand := oscommands.NewDummyOSCommand()
|
osCommand := oscommands.NewDummyOSCommand()
|
||||||
bashScriptPath := filepath.Join(testPath, "setup.sh")
|
bashScriptPath := filepath.Join(testPath, "setup.sh")
|
||||||
|
@ -458,7 +458,7 @@ func (gui *Gui) Run() error {
|
|||||||
playMode = gocui.REPLAYING
|
playMode = gocui.REPLAYING
|
||||||
}
|
}
|
||||||
|
|
||||||
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode)
|
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, playMode, headless())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -468,7 +468,7 @@ func (gui *Gui) Run() error {
|
|||||||
if replaying() {
|
if replaying() {
|
||||||
g.RecordingConfig = gocui.RecordingConfig{
|
g.RecordingConfig = gocui.RecordingConfig{
|
||||||
Speed: getRecordingSpeed(),
|
Speed: getRecordingSpeed(),
|
||||||
Leeway: 0,
|
Leeway: 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Recording, err = gui.loadRecording()
|
g.Recording, err = gui.loadRecording()
|
||||||
|
@ -22,6 +22,10 @@ func replaying() bool {
|
|||||||
return os.Getenv("REPLAY_EVENTS_FROM") != ""
|
return os.Getenv("REPLAY_EVENTS_FROM") != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func headless() bool {
|
||||||
|
return os.Getenv("HEADLESS") != ""
|
||||||
|
}
|
||||||
|
|
||||||
func getRecordingSpeed() int {
|
func getRecordingSpeed() int {
|
||||||
// humans are slow so this speeds things up.
|
// humans are slow so this speeds things up.
|
||||||
speed := 1
|
speed := 1
|
||||||
|
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
0000000000000000000000000000000000000000 55bf9e2babc63fb5bae4b42c79fb1ddd1f4205fa CI <CI@example.com> 1617579407 +1000 commit (initial): myfile1
|
0000000000000000000000000000000000000000 47912ceb0ffa9b205290e75103d9dd6b1878e87b CI <CI@example.com> 1617580570 +1000 commit (initial): myfile1
|
||||||
55bf9e2babc63fb5bae4b42c79fb1ddd1f4205fa e68c1143545a09cac1db6e6f20db13b82b5dc43e CI <CI@example.com> 1617579407 +1000 commit: myfile2
|
47912ceb0ffa9b205290e75103d9dd6b1878e87b 6bd1486e023f9853f9e6f611cac5ccbc8960ce57 CI <CI@example.com> 1617580570 +1000 commit: myfile2
|
||||||
e68c1143545a09cac1db6e6f20db13b82b5dc43e ba001a041f64109b238f03550d6a21209bd5d4fc CI <CI@example.com> 1617579407 +1000 commit: myfile3
|
6bd1486e023f9853f9e6f611cac5ccbc8960ce57 980224a1dd75d91fdcab11edc8d25c3ff8f751ba CI <CI@example.com> 1617580570 +1000 commit: myfile3
|
||||||
ba001a041f64109b238f03550d6a21209bd5d4fc 6f31cc35ebabdf3fb71759e3d267677e712f737f CI <CI@example.com> 1617579407 +1000 commit: myfile4
|
980224a1dd75d91fdcab11edc8d25c3ff8f751ba f4e779d1bd2ad074259ad763210f5b911337054f CI <CI@example.com> 1617580570 +1000 commit: myfile4
|
||||||
6f31cc35ebabdf3fb71759e3d267677e712f737f ecc083de4d216847c184c72a639743bba6520d88 CI <CI@example.com> 1617579409 +1000 commit: commit
|
f4e779d1bd2ad074259ad763210f5b911337054f d0cab53ed70fc66096575c2ccd7ef150b4b470e8 CI <CI@example.com> 1617580572 +1000 commit: commit
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
0000000000000000000000000000000000000000 55bf9e2babc63fb5bae4b42c79fb1ddd1f4205fa CI <CI@example.com> 1617579407 +1000 commit (initial): myfile1
|
0000000000000000000000000000000000000000 47912ceb0ffa9b205290e75103d9dd6b1878e87b CI <CI@example.com> 1617580570 +1000 commit (initial): myfile1
|
||||||
55bf9e2babc63fb5bae4b42c79fb1ddd1f4205fa e68c1143545a09cac1db6e6f20db13b82b5dc43e CI <CI@example.com> 1617579407 +1000 commit: myfile2
|
47912ceb0ffa9b205290e75103d9dd6b1878e87b 6bd1486e023f9853f9e6f611cac5ccbc8960ce57 CI <CI@example.com> 1617580570 +1000 commit: myfile2
|
||||||
e68c1143545a09cac1db6e6f20db13b82b5dc43e ba001a041f64109b238f03550d6a21209bd5d4fc CI <CI@example.com> 1617579407 +1000 commit: myfile3
|
6bd1486e023f9853f9e6f611cac5ccbc8960ce57 980224a1dd75d91fdcab11edc8d25c3ff8f751ba CI <CI@example.com> 1617580570 +1000 commit: myfile3
|
||||||
ba001a041f64109b238f03550d6a21209bd5d4fc 6f31cc35ebabdf3fb71759e3d267677e712f737f CI <CI@example.com> 1617579407 +1000 commit: myfile4
|
980224a1dd75d91fdcab11edc8d25c3ff8f751ba f4e779d1bd2ad074259ad763210f5b911337054f CI <CI@example.com> 1617580570 +1000 commit: myfile4
|
||||||
6f31cc35ebabdf3fb71759e3d267677e712f737f ecc083de4d216847c184c72a639743bba6520d88 CI <CI@example.com> 1617579409 +1000 commit: commit
|
f4e779d1bd2ad074259ad763210f5b911337054f d0cab53ed70fc66096575c2ccd7ef150b4b470e8 CI <CI@example.com> 1617580572 +1000 commit: commit
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
x��A
|
||||||
|
�0@Q�9�����I��#M&X��R"����~��̖�r��*�J��d7��Y)J�4$��g�ᚲ\z�W�a��>NO�$�V��f ��#��p&DtG=&]��ξuY��16,�
|
@ -1,2 +0,0 @@
|
|||||||
x��A
|
|
||||||
�0@Ѯs��ʌ'J\y��L���")���#t�y�S5[˥���*�`��2�R���T</|��%��s��^u�i��4��������=��|�FWBDw�s��O��W�M�3�,�
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,4 +0,0 @@
|
|||||||
x��A
|
|
||||||
1�a�=E��4M�� "�j���)
|
|
||||||
��
|
|
||||||
�.<������kk���wU +���k�L�O�:Q�5�+��O��l���C��9ӤIR�4������U&�F����0/p���~�mO=��]��p�6���f�#������e);a
|
|
@ -0,0 +1,2 @@
|
|||||||
|
x��K
|
||||||
|
�0@]��2�d��"BW=�4�`���D���#�}<x/o�-]cr�~�hS=6������{�l�K��Y�L �·��N�q��* k�<#Jɱʶ�X�̊���z��0N�p�W����5�I�@�@�������ڷ.�8�#�9}
|
@ -1 +1 @@
|
|||||||
ecc083de4d216847c184c72a639743bba6520d88
|
d0cab53ed70fc66096575c2ccd7ef150b4b470e8
|
||||||
|
@ -1 +1 @@
|
|||||||
{"KeyEvents":[{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":32},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":99},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":99},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":111},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":109},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":109},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":105},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":116},{"Timestamp":9223372036854,"Mod":0,"Key":13,"Ch":13},{"Timestamp":9223372036854,"Mod":0,"Key":256,"Ch":113}]}
|
{"KeyEvents":[{"Timestamp":527,"Mod":0,"Key":256,"Ch":32},{"Timestamp":830,"Mod":0,"Key":256,"Ch":99},{"Timestamp":1127,"Mod":0,"Key":256,"Ch":99},{"Timestamp":1190,"Mod":0,"Key":256,"Ch":111},{"Timestamp":1335,"Mod":0,"Key":256,"Ch":109},{"Timestamp":1447,"Mod":0,"Key":256,"Ch":109},{"Timestamp":1583,"Mod":0,"Key":256,"Ch":105},{"Timestamp":1606,"Mod":0,"Key":256,"Ch":116},{"Timestamp":1935,"Mod":0,"Key":13,"Ch":13},{"Timestamp":2353,"Mod":0,"Key":27,"Ch":0}],"ResizeEvents":[{"Timestamp":0,"Width":127,"Height":35}]}
|
||||||
|
@ -1 +1 @@
|
|||||||
{ "description": "stage a file and commit the change", "speed": 20 }
|
{ "description": "stage a file and commit the change", "speed": 15 }
|
||||||
|
@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -16,15 +15,12 @@ import (
|
|||||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errSubProcess = errors.New("subprocess")
|
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
tests []*IntegrationTest
|
tests []*IntegrationTest
|
||||||
itemIdx int
|
itemIdx int
|
||||||
subProcess *exec.Cmd
|
testDir string
|
||||||
testDir string
|
editing bool
|
||||||
editing bool
|
g *gocui.Gui
|
||||||
g *gocui.Gui
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *App) getCurrentTest() *IntegrationTest {
|
func (app *App) getCurrentTest() *IntegrationTest {
|
||||||
@ -70,217 +66,217 @@ func main() {
|
|||||||
app := &App{testDir: testDir}
|
app := &App{testDir: testDir}
|
||||||
app.loadTests()
|
app.loadTests()
|
||||||
|
|
||||||
Loop:
|
g, err := gocui.NewGui(gocui.OutputTrue, false, gocui.NORMAL, false)
|
||||||
for {
|
if err != nil {
|
||||||
g, err := gocui.NewGui(gocui.OutputTrue, false, gocui.NORMAL)
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Cursor = false
|
||||||
|
|
||||||
|
app.g = g
|
||||||
|
|
||||||
|
g.SetManagerFunc(app.layout)
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
if app.itemIdx > 0 {
|
||||||
|
app.itemIdx--
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, 'q', gocui.ModNone, quit); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, 'r', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
currentTest := app.getCurrentTest()
|
||||||
|
if currentTest == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("RECORD_EVENTS=true go run integration/main.go %s", currentTest.Name))
|
||||||
|
app.runSubprocess(cmd)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
currentTest := app.getCurrentTest()
|
||||||
|
if currentTest == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("go run integration/main.go %s", currentTest.Name))
|
||||||
|
app.runSubprocess(cmd)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
currentTest := app.getCurrentTest()
|
||||||
|
if currentTest == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r %s/%s/test.json", app.testDir, currentTest.Name))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, 'n', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
currentTest := app.getCurrentTest()
|
||||||
|
if currentTest == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to duplicate that folder and then re-fetch our tests.
|
||||||
|
dir := app.testDir + "/" + app.getCurrentTest().Name
|
||||||
|
newDir := dir + "_Copy"
|
||||||
|
|
||||||
|
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("cp -r %s %s", dir, newDir))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
app.loadTests()
|
||||||
|
|
||||||
|
app.refreshTests()
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("list", nil, 'm', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
currentTest := app.getCurrentTest()
|
||||||
|
if currentTest == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
app.editing = true
|
||||||
|
if _, err := g.SetCurrentView("editor"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
editorView, err := g.View("editor")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panicln(err)
|
return err
|
||||||
}
|
}
|
||||||
|
editorView.Clear()
|
||||||
|
fmt.Fprint(editorView, currentTest.Name)
|
||||||
|
|
||||||
g.Cursor = false
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
app.g = g
|
if err := g.SetKeybinding("list", nil, 'd', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
currentTest := app.getCurrentTest()
|
||||||
g.SetManagerFunc(app.layout)
|
if currentTest == nil {
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
|
||||||
if app.itemIdx > 0 {
|
|
||||||
app.itemIdx--
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
|
dir := app.testDir + "/" + app.getCurrentTest().Name
|
||||||
log.Panicln(err)
|
|
||||||
|
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("rm -rf %s", dir))
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, 'q', gocui.ModNone, quit); err != nil {
|
app.refreshTests()
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, 'r', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
return nil
|
||||||
currentTest := app.getCurrentTest()
|
}); err != nil {
|
||||||
if currentTest == nil {
|
log.Panicln(err)
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
|
|
||||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("RECORD_EVENTS=true go test pkg/gui/gui_test.go -run /%s", currentTest.Name))
|
|
||||||
app.subProcess = cmd
|
|
||||||
|
|
||||||
return errSubProcess
|
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
|
||||||
currentTest := app.getCurrentTest()
|
|
||||||
if currentTest == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("go test pkg/gui/gui_test.go -run /%s", currentTest.Name))
|
|
||||||
app.subProcess = cmd
|
|
||||||
|
|
||||||
return errSubProcess
|
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, 'o', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
|
||||||
currentTest := app.getCurrentTest()
|
|
||||||
if currentTest == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("code -r %s/%s/test.json", app.testDir, currentTest.Name))
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("editor", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
currentTest := app.getCurrentTest()
|
||||||
|
if currentTest == nil {
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, 'n', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
app.editing = false
|
||||||
currentTest := app.getCurrentTest()
|
if _, err := g.SetCurrentView("list"); err != nil {
|
||||||
if currentTest == nil {
|
return err
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// need to duplicate that folder and then re-fetch our tests.
|
|
||||||
dir := app.testDir + "/" + app.getCurrentTest().Name
|
|
||||||
newDir := dir + "_Copy"
|
|
||||||
|
|
||||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("cp -r %s %s", dir, newDir))
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
app.loadTests()
|
|
||||||
|
|
||||||
app.refreshTests()
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, 'm', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
editorView, err := g.View("editor")
|
||||||
currentTest := app.getCurrentTest()
|
|
||||||
if currentTest == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
app.editing = true
|
|
||||||
if _, err := g.SetCurrentView("editor"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
editorView, err := g.View("editor")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
editorView.Clear()
|
|
||||||
fmt.Fprint(editorView, currentTest.Name)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.SetKeybinding("list", nil, 'd', gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
|
||||||
currentTest := app.getCurrentTest()
|
|
||||||
if currentTest == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := app.testDir + "/" + app.getCurrentTest().Name
|
|
||||||
|
|
||||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("rm -rf %s", dir))
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
app.refreshTests()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.SetKeybinding("editor", nil, gocui.KeyEnter, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
|
||||||
currentTest := app.getCurrentTest()
|
|
||||||
if currentTest == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
app.editing = false
|
|
||||||
if _, err := g.SetCurrentView("list"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
editorView, err := g.View("editor")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := app.testDir + "/" + app.getCurrentTest().Name
|
|
||||||
newDir := app.testDir + "/" + editorView.Buffer()
|
|
||||||
|
|
||||||
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("mv %s %s", dir, newDir))
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
editorView.Clear()
|
|
||||||
|
|
||||||
app.refreshTests()
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := g.SetKeybinding("editor", nil, gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
|
||||||
app.editing = false
|
|
||||||
if _, err := g.SetCurrentView("list"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
log.Panicln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = g.MainLoop()
|
|
||||||
g.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
return err
|
||||||
case gocui.ErrQuit:
|
}
|
||||||
break Loop
|
|
||||||
|
|
||||||
case errSubProcess:
|
dir := app.testDir + "/" + app.getCurrentTest().Name
|
||||||
cmd := app.subProcess
|
newDir := app.testDir + "/" + editorView.Buffer()
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
log.Println(err.Error())
|
|
||||||
}
|
|
||||||
cmd.Stdin = nil
|
|
||||||
cmd.Stderr = nil
|
|
||||||
cmd.Stdout = nil
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stdout, "\n%s", coloredString("press enter to return", color.FgGreen))
|
cmd := secureexec.Command("sh", "-c", fmt.Sprintf("mv %s %s", dir, newDir))
|
||||||
fmt.Scanln() // wait for enter press
|
if err := cmd.Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
editorView.Clear()
|
||||||
log.Panicln(err)
|
|
||||||
}
|
app.refreshTests()
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.SetKeybinding("editor", nil, gocui.KeyEsc, gocui.ModNone, func(*gocui.Gui, *gocui.View) error {
|
||||||
|
app.editing = false
|
||||||
|
if _, err := g.SetCurrentView("list"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
log.Panicln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.MainLoop()
|
||||||
|
g.Close()
|
||||||
|
if err != nil {
|
||||||
|
switch err {
|
||||||
|
case gocui.ErrQuit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
log.Panicln(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *App) runSubprocess(cmd *exec.Cmd) {
|
||||||
|
gocui.Screen.Suspend()
|
||||||
|
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
}
|
||||||
|
cmd.Stdin = nil
|
||||||
|
cmd.Stderr = nil
|
||||||
|
cmd.Stdout = nil
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stdout, "\n%s", coloredString("press enter to return", color.FgGreen))
|
||||||
|
fmt.Scanln() // wait for enter press
|
||||||
|
|
||||||
|
gocui.Screen.Resume()
|
||||||
|
}
|
||||||
|
|
||||||
func (app *App) layout(g *gocui.Gui) error {
|
func (app *App) layout(g *gocui.Gui) error {
|
||||||
maxX, maxY := g.Size()
|
maxX, maxY := g.Size()
|
||||||
descriptionViewHeight := 7
|
descriptionViewHeight := 7
|
||||||
|
127
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
127
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
@ -85,11 +85,13 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Recording struct {
|
type Recording struct {
|
||||||
KeyEvents []*TcellKeyEventWrapper
|
KeyEvents []*TcellKeyEventWrapper
|
||||||
|
ResizeEvents []*TcellResizeEventWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
type replayedEvents struct {
|
type replayedEvents struct {
|
||||||
keys chan *TcellKeyEventWrapper
|
keys chan *TcellKeyEventWrapper
|
||||||
|
resizes chan *TcellResizeEventWrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordingConfig struct {
|
type RecordingConfig struct {
|
||||||
@ -159,14 +161,19 @@ type Gui struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewGui returns a new Gui object with a given output mode.
|
// NewGui returns a new Gui object with a given output mode.
|
||||||
func NewGui(mode OutputMode, supportOverlaps bool, playMode PlayMode) (*Gui, error) {
|
func NewGui(mode OutputMode, supportOverlaps bool, playMode PlayMode, headless bool) (*Gui, error) {
|
||||||
err := tcellInit()
|
g := &Gui{}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if headless {
|
||||||
|
err = tcellInitSimulation()
|
||||||
|
} else {
|
||||||
|
err = tcellInit()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
g := &Gui{}
|
|
||||||
|
|
||||||
g.outputMode = mode
|
g.outputMode = mode
|
||||||
|
|
||||||
g.stop = make(chan struct{})
|
g.stop = make(chan struct{})
|
||||||
@ -176,11 +183,13 @@ func NewGui(mode OutputMode, supportOverlaps bool, playMode PlayMode) (*Gui, err
|
|||||||
|
|
||||||
if playMode == RECORDING {
|
if playMode == RECORDING {
|
||||||
g.Recording = &Recording{
|
g.Recording = &Recording{
|
||||||
KeyEvents: []*TcellKeyEventWrapper{},
|
KeyEvents: []*TcellKeyEventWrapper{},
|
||||||
|
ResizeEvents: []*TcellResizeEventWrapper{},
|
||||||
}
|
}
|
||||||
} else if playMode == REPLAYING {
|
} else if playMode == REPLAYING {
|
||||||
g.ReplayedEvents = replayedEvents{
|
g.ReplayedEvents = replayedEvents{
|
||||||
keys: make(chan *TcellKeyEventWrapper),
|
keys: make(chan *TcellKeyEventWrapper),
|
||||||
|
resizes: make(chan *TcellResizeEventWrapper),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -562,8 +571,10 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
|
|||||||
// MainLoop runs the main loop until an error is returned. A successful
|
// MainLoop runs the main loop until an error is returned. A successful
|
||||||
// finish should return ErrQuit.
|
// finish should return ErrQuit.
|
||||||
func (g *Gui) MainLoop() error {
|
func (g *Gui) MainLoop() error {
|
||||||
|
|
||||||
|
g.StartTime = time.Now()
|
||||||
if g.PlayMode == REPLAYING {
|
if g.PlayMode == REPLAYING {
|
||||||
g.replayRecording()
|
go g.replayRecording()
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
@ -1182,38 +1193,80 @@ func IsQuit(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *Gui) replayRecording() {
|
func (g *Gui) replayRecording() {
|
||||||
ticker := time.NewTicker(time.Millisecond)
|
waitGroup := sync.WaitGroup{}
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
// The playback could be paused at any time because integration tests run concurrently.
|
waitGroup.Add(2)
|
||||||
// Therefore we can't just check for a given event whether we've passed its timestamp,
|
|
||||||
// or else we'll have an explosion of keypresses after the test is resumed.
|
go func() {
|
||||||
// We need to check if we've waited long enough since the last event was replayed.
|
ticker := time.NewTicker(time.Millisecond)
|
||||||
// Only handling key events for now.
|
defer ticker.Stop()
|
||||||
for i, event := range g.Recording.KeyEvents {
|
|
||||||
var prevEventTimestamp int64 = 0
|
// The playback could be paused at any time because integration tests run concurrently.
|
||||||
if i > 0 {
|
// Therefore we can't just check for a given event whether we've passed its timestamp,
|
||||||
prevEventTimestamp = g.Recording.KeyEvents[i-1].Timestamp
|
// or else we'll have an explosion of keypresses after the test is resumed.
|
||||||
}
|
// We need to check if we've waited long enough since the last event was replayed.
|
||||||
timeToWait := (event.Timestamp - prevEventTimestamp) / int64(g.RecordingConfig.Speed)
|
for i, event := range g.Recording.KeyEvents {
|
||||||
if i == 0 {
|
var prevEventTimestamp int64 = 0
|
||||||
timeToWait += int64(g.RecordingConfig.Leeway)
|
if i > 0 {
|
||||||
}
|
prevEventTimestamp = g.Recording.KeyEvents[i-1].Timestamp
|
||||||
var timeWaited int64 = 0
|
}
|
||||||
middle:
|
timeToWait := (event.Timestamp - prevEventTimestamp) / int64(g.RecordingConfig.Speed)
|
||||||
for {
|
if i == 0 {
|
||||||
select {
|
timeToWait += int64(g.RecordingConfig.Leeway)
|
||||||
case <-ticker.C:
|
}
|
||||||
timeWaited += 1
|
var timeWaited int64 = 0
|
||||||
if g != nil && timeWaited >= timeToWait {
|
middle:
|
||||||
g.ReplayedEvents.keys <- event
|
for {
|
||||||
break middle
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
timeWaited += 1
|
||||||
|
if timeWaited >= timeToWait {
|
||||||
|
g.ReplayedEvents.keys <- event
|
||||||
|
break middle
|
||||||
|
}
|
||||||
|
case <-g.stop:
|
||||||
|
return
|
||||||
}
|
}
|
||||||
case <-g.stop:
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
waitGroup.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
// duplicating until Go gets generics
|
||||||
|
for i, event := range g.Recording.ResizeEvents {
|
||||||
|
var prevEventTimestamp int64 = 0
|
||||||
|
if i > 0 {
|
||||||
|
prevEventTimestamp = g.Recording.ResizeEvents[i-1].Timestamp
|
||||||
|
}
|
||||||
|
timeToWait := (event.Timestamp - prevEventTimestamp) / int64(g.RecordingConfig.Speed)
|
||||||
|
if i == 0 {
|
||||||
|
timeToWait += int64(g.RecordingConfig.Leeway)
|
||||||
|
}
|
||||||
|
var timeWaited int64 = 0
|
||||||
|
middle2:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
timeWaited += 1
|
||||||
|
if timeWaited >= timeToWait {
|
||||||
|
g.ReplayedEvents.resizes <- event
|
||||||
|
break middle2
|
||||||
|
}
|
||||||
|
case <-g.stop:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
waitGroup.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
waitGroup.Wait()
|
||||||
|
|
||||||
// leaving some time for any handlers to execute before quitting
|
// leaving some time for any handlers to execute before quitting
|
||||||
time.Sleep(time.Second * 1)
|
time.Sleep(time.Second * 1)
|
||||||
|
39
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
39
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
@ -38,6 +38,17 @@ func tcellInit() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tcellInitSimulation initializes tcell screen for use.
|
||||||
|
func tcellInitSimulation() error {
|
||||||
|
s := tcell.NewSimulationScreen("")
|
||||||
|
if e := s.Init(); e != nil {
|
||||||
|
return e
|
||||||
|
} else {
|
||||||
|
Screen = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// tcellSetCell sets the character cell at a given location to the given
|
// tcellSetCell sets the character cell at a given location to the given
|
||||||
// content (rune) and attributes using provided OutputMode
|
// content (rune) and attributes using provided OutputMode
|
||||||
func tcellSetCell(x, y int, ch rune, fg, bg Attribute, outputMode OutputMode) {
|
func tcellSetCell(x, y int, ch rune, fg, bg Attribute, outputMode OutputMode) {
|
||||||
@ -166,6 +177,26 @@ func (wrapper TcellKeyEventWrapper) toTcellEvent() tcell.Event {
|
|||||||
return tcell.NewEventKey(wrapper.Key, wrapper.Ch, wrapper.Mod)
|
return tcell.NewEventKey(wrapper.Key, wrapper.Ch, wrapper.Mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TcellResizeEventWrapper struct {
|
||||||
|
Timestamp int64
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTcellResizeEventWrapper(event *tcell.EventResize, timestamp int64) *TcellResizeEventWrapper {
|
||||||
|
w, h := event.Size()
|
||||||
|
|
||||||
|
return &TcellResizeEventWrapper{
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Width: w,
|
||||||
|
Height: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wrapper TcellResizeEventWrapper) toTcellEvent() tcell.Event {
|
||||||
|
return tcell.NewEventResize(wrapper.Width, wrapper.Height)
|
||||||
|
}
|
||||||
|
|
||||||
func (g *Gui) timeSinceStart() int64 {
|
func (g *Gui) timeSinceStart() int64 {
|
||||||
return time.Since(g.StartTime).Nanoseconds() / 1e6
|
return time.Since(g.StartTime).Nanoseconds() / 1e6
|
||||||
}
|
}
|
||||||
@ -177,6 +208,8 @@ func (g *Gui) pollEvent() GocuiEvent {
|
|||||||
select {
|
select {
|
||||||
case ev := <-g.ReplayedEvents.keys:
|
case ev := <-g.ReplayedEvents.keys:
|
||||||
tev = (ev).toTcellEvent()
|
tev = (ev).toTcellEvent()
|
||||||
|
case ev := <-g.ReplayedEvents.resizes:
|
||||||
|
tev = (ev).toTcellEvent()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tev = Screen.PollEvent()
|
tev = Screen.PollEvent()
|
||||||
@ -186,6 +219,12 @@ func (g *Gui) pollEvent() GocuiEvent {
|
|||||||
case *tcell.EventInterrupt:
|
case *tcell.EventInterrupt:
|
||||||
return GocuiEvent{Type: eventInterrupt}
|
return GocuiEvent{Type: eventInterrupt}
|
||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
|
if g.PlayMode == RECORDING {
|
||||||
|
g.Recording.ResizeEvents = append(
|
||||||
|
g.Recording.ResizeEvents, NewTcellResizeEventWrapper(tev, g.timeSinceStart()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
w, h := tev.Size()
|
w, h := tev.Size()
|
||||||
return GocuiEvent{Type: eventResize, Width: w, Height: h}
|
return GocuiEvent{Type: eventResize, Width: w, Height: h}
|
||||||
case *tcell.EventKey:
|
case *tcell.EventKey:
|
||||||
|
Reference in New Issue
Block a user