Add simulation screen tests

This commit is contained in:
Zachary Yedidia 2020-06-20 18:24:12 -04:00
parent a9ca57af6e
commit d0b75bc09f
9 changed files with 389 additions and 17 deletions

View file

@ -64,6 +64,7 @@ testgen:
test:
go test ./internal/...
go test ./cmd/...
bench:
for i in 1 2 3; do \

View file

@ -132,7 +132,7 @@ func DoPluginFlags() {
// LoadInput determines which files should be loaded into buffers
// based on the input stored in flag.Args()
func LoadInput() []*buffer.Buffer {
func LoadInput(args []string) []*buffer.Buffer {
// There are a number of ways micro should start given its input
// 1. If it is given a files in flag.Args(), it should open those
@ -147,7 +147,6 @@ func LoadInput() []*buffer.Buffer {
var filename string
var input []byte
var err error
args := flag.Args()
buffers := make([]*buffer.Buffer, 0, len(args))
btype := buffer.BTDefault
@ -262,7 +261,12 @@ func main() {
DoPluginFlags()
screen.Init()
err = screen.Init()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
}
defer func() {
if err := recover(); err != nil {
@ -291,7 +295,8 @@ func main() {
screen.TermMessage(err)
}
b := LoadInput()
args := flag.Args()
b := LoadInput(args)
if len(b) == 0 {
// No buffers to open

341
cmd/micro/micro_test.go Normal file
View file

@ -0,0 +1,341 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"github.com/go-errors/errors"
"github.com/stretchr/testify/assert"
"github.com/zyedidia/micro/v2/internal/action"
"github.com/zyedidia/micro/v2/internal/buffer"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
"github.com/zyedidia/tcell"
)
var tempDir string
var sim tcell.SimulationScreen
func init() {
events = make(chan tcell.Event, 8)
}
func startup(args []string) (tcell.SimulationScreen, error) {
var err error
tempDir, err = ioutil.TempDir("", "micro_test")
if err != nil {
return nil, err
}
err = config.InitConfigDir(tempDir)
if err != nil {
return nil, err
}
config.InitRuntimeFiles()
err = config.ReadSettings()
if err != nil {
return nil, err
}
err = config.InitGlobalSettings()
if err != nil {
return nil, err
}
s, err := screen.InitSimScreen()
if err != nil {
return nil, err
}
defer func() {
if err := recover(); err != nil {
screen.Screen.Fini()
fmt.Println("Micro encountered an error:", err)
// backup all open buffers
for _, b := range buffer.OpenBuffers {
b.Backup(false)
}
// Print the stack trace too
log.Fatalf(errors.Wrap(err, 2).ErrorStack())
os.Exit(1)
}
}()
err = config.LoadAllPlugins()
if err != nil {
screen.TermMessage(err)
}
action.InitBindings()
action.InitCommands()
err = config.InitColorscheme()
if err != nil {
return nil, err
}
b := LoadInput(args)
if len(b) == 0 {
return nil, errors.New("No buffers opened")
}
action.InitTabs(b)
action.InitGlobals()
err = config.RunPluginFn("init")
if err != nil {
return nil, err
}
s.InjectResize()
handleEvent()
return s, nil
}
func cleanup() {
os.RemoveAll(tempDir)
}
func handleEvent() {
screen.Lock()
e := screen.Screen.PollEvent()
screen.Unlock()
if e != nil {
events <- e
}
DoEvent()
}
func injectKey(key tcell.Key, r rune, mod tcell.ModMask) {
sim.InjectKey(key, r, mod)
handleEvent()
}
func injectMouse(x, y int, buttons tcell.ButtonMask, mod tcell.ModMask) {
sim.InjectMouse(x, y, buttons, mod)
handleEvent()
}
func injectString(str string) {
// the tcell simulation screen event channel can only handle
// 10 events at once, so we need to divide up the key events
// into chunks of 10 and handle the 10 events before sending
// another chunk of events
iters := len(str) / 10
extra := len(str) % 10
for i := 0; i < iters; i++ {
s := i * 10
e := i*10 + 10
sim.InjectKeyBytes([]byte(str[s:e]))
for i := 0; i < 10; i++ {
handleEvent()
}
}
sim.InjectKeyBytes([]byte(str[len(str)-extra:]))
for i := 0; i < extra; i++ {
handleEvent()
}
}
func openFile(file string) {
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("open %s", file))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
}
func createTestFile(name string, content string) (string, error) {
testf, err := ioutil.TempFile("", name)
if err != nil {
return "", err
}
if _, err := testf.Write([]byte(content)); err != nil {
return "", err
}
if err := testf.Close(); err != nil {
return "", err
}
return testf.Name(), nil
}
func TestMain(m *testing.M) {
var err error
sim, err = startup([]string{})
if err != nil {
log.Fatalln(err)
os.Exit(1)
}
retval := m.Run()
cleanup()
os.Exit(retval)
}
func TestSimpleEdit(t *testing.T) {
file, err := createTestFile("micro_simple_edit_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
var buf *buffer.Buffer
for _, b := range buffer.OpenBuffers {
if b.Path == file {
buf = b
}
}
if buf == nil {
t.Errorf("Could not find buffer %s", file)
return
}
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyUp, 0, tcell.ModNone)
injectString("first line")
// test both kinds of backspace
for i := 0; i < len("ne"); i++ {
injectKey(tcell.KeyBackspace, rune(tcell.KeyBackspace), tcell.ModNone)
}
for i := 0; i < len(" li"); i++ {
injectKey(tcell.KeyBackspace2, rune(tcell.KeyBackspace2), tcell.ModNone)
}
injectString("foobar")
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, "firstfoobar\nbase content\n", string(data))
}
func TestMouse(t *testing.T) {
file, err := createTestFile("micro_mouse_test", "base content")
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
// buffer:
// base content
// the selections need to happen at different locations to avoid a double click
injectMouse(3, 0, tcell.Button1, tcell.ModNone)
injectKey(tcell.KeyLeft, 0, tcell.ModNone)
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
injectString("secondline")
// buffer:
// secondlinebase content
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
// buffer:
// secondline
// base content
injectMouse(2, 0, tcell.Button1, tcell.ModNone)
injectMouse(0, 0, tcell.ButtonNone, tcell.ModNone)
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
// buffer:
//
// secondline
// base content
injectKey(tcell.KeyUp, 0, tcell.ModNone)
injectString("firstline")
// buffer:
// firstline
// secondline
// base content
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, "firstline\nsecondline\nbase content\n", string(data))
}
var srTestStart = `foo
foo
foofoofoo
Ernleȝe foo æðelen
`
var srTest2 = `test_string
test_string
test_stringtest_stringtest_string
Ernleȝe test_string æðelen
`
var srTest3 = `test_foo
test_string
test_footest_stringtest_foo
Ernleȝe test_string æðelen
`
func TestSearchAndReplace(t *testing.T) {
file, err := createTestFile("micro_search_replace_test", srTestStart)
if err != nil {
t.Error(err)
return
}
defer os.Remove(file)
openFile(file)
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replaceall %s %s", "foo", "test_string"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err := ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, srTest2, string(data))
injectKey(tcell.KeyCtrlE, rune(tcell.KeyCtrlE), tcell.ModCtrl)
injectString(fmt.Sprintf("replace %s %s", "string", "foo"))
injectKey(tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone)
injectString("ynyny")
injectKey(tcell.KeyEscape, 0, tcell.ModNone)
injectKey(tcell.KeyCtrlS, rune(tcell.KeyCtrlS), tcell.ModCtrl)
data, err = ioutil.ReadFile(file)
if err != nil {
t.Error(err)
return
}
assert.Equal(t, srTest3, string(data))
}
func TestMultiCursor(t *testing.T) {
// TODO
}
func TestSettingsPersistence(t *testing.T) {
// TODO
}
// more tests (rendering, tabs, plugins)?

2
go.mod
View file

@ -17,7 +17,7 @@ require (
github.com/zyedidia/highlight v0.0.0-20170330143449-201131ce5cf5
github.com/zyedidia/json5 v0.0.0-20200102012142-2da050b1a98d
github.com/zyedidia/pty v2.0.0+incompatible // indirect
github.com/zyedidia/tcell v1.4.7
github.com/zyedidia/tcell v1.4.8
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415
golang.org/x/text v0.3.2
gopkg.in/sourcemap.v1 v1.0.5 // indirect

4
go.sum
View file

@ -52,8 +52,8 @@ github.com/zyedidia/poller v1.0.1 h1:Tt9S3AxAjXwWGNiC2TUdRJkQDZSzCBNVQ4xXiQ7440s
github.com/zyedidia/poller v1.0.1/go.mod h1:vZXJOHGDcuK08GXhF6IAY0ZFd2WcgOR5DOTp84Uk5eE=
github.com/zyedidia/pty v2.0.0+incompatible h1:Ou5vXL6tvjst+RV8sUFISbuKDnUJPhnpygApMFGweqw=
github.com/zyedidia/pty v2.0.0+incompatible/go.mod h1:4y9l9yJZNxRa7GB/fB+mmDmGkG3CqmzLf4vUxGGotEA=
github.com/zyedidia/tcell v1.4.7 h1:bKXRjv8RglPyOFqofzUUJkrdsLs9p9mT89W2ShFFlco=
github.com/zyedidia/tcell v1.4.7/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/tcell v1.4.8 h1:s4zYGOyCNDK4cdrgNVME0SxGizuT/oKY3OyB4Ls2Qpg=
github.com/zyedidia/tcell v1.4.8/go.mod h1:HhlbMSCcGX15rFDB+Q1Lk3pKEOocsCUAQC3zhZ9sadA=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 h1:752dTQ5OatJ9M5ULK2+9lor+nzyZz+LYDo3WGngg3Rc=
github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415/go.mod h1:8leT8G0Cm8NoJHdrrKHyR9MirWoF4YW7pZh06B6H+1E=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View file

@ -82,7 +82,7 @@ modSearch:
case strings.HasPrefix(k, "-"):
// We optionally support dashes between modifiers
k = k[1:]
case strings.HasPrefix(k, "Ctrl") && k != "Ctrl-h" && k != "CtrlH" && k != "Ctrlh":
case strings.HasPrefix(k, "Ctrl") && k != "CtrlH":
// CtrlH technically does not have a 'Ctrl' modifier because it is really backspace
k = k[4:]
modifiers |= tcell.ModCtrl

View file

@ -30,8 +30,9 @@ func DefaultBindings() map[string]string {
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"Ctrl-h": "Backspace",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",

View file

@ -32,8 +32,9 @@ func DefaultBindings() map[string]string {
"Alt-{": "ParagraphPrevious",
"Alt-}": "ParagraphNext",
"Enter": "InsertNewline",
"Ctrl-h": "Backspace",
"CtrlH": "Backspace",
"Backspace": "Backspace",
"OldBackspace": "Backspace",
"Alt-CtrlH": "DeleteWordLeft",
"Alt-Backspace": "DeleteWordLeft",
"Tab": "Autocomplete|IndentSelection|InsertTab",

View file

@ -1,7 +1,7 @@
package screen
import (
"fmt"
"errors"
"os"
"sync"
"unicode"
@ -131,7 +131,7 @@ func TempStart(screenWasNil bool) {
}
// Init creates and initializes the tcell screen
func Init() {
func Init() error {
drawChan = make(chan bool, 8)
// Should we enable true color?
@ -151,13 +151,10 @@ func Init() {
var err error
Screen, err = tcell.NewScreen()
if err != nil {
fmt.Println(err)
fmt.Println("Fatal: Micro could not initialize a Screen.")
os.Exit(1)
return err
}
if err = Screen.Init(); err != nil {
fmt.Println(err)
os.Exit(1)
return err
}
// restore TERM
@ -168,4 +165,30 @@ func Init() {
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
return nil
}
// InitSimScreen initializes a simulation screen for testing purposes
func InitSimScreen() (tcell.SimulationScreen, error) {
drawChan = make(chan bool, 8)
// Initilize tcell
var err error
s := tcell.NewSimulationScreen("")
if s == nil {
return nil, errors.New("Failed to get a simulation screen")
}
if err = s.Init(); err != nil {
return nil, err
}
s.SetSize(80, 24)
Screen = s
if config.GetGlobalOption("mouse").(bool) {
Screen.EnableMouse()
}
return s, nil
}