From f0cdc3cabbf4fa0cc1ab7d30f6e3812037e5571d Mon Sep 17 00:00:00 2001 From: Philipp Emanuel Weidmann Date: Sat, 22 Feb 2020 08:51:38 +0530 Subject: [PATCH] Add buffer test and benchmark suite (and tool to generate it) --- .gitignore | 1 + Makefile | 13 + go.mod | 2 + go.sum | 4 + internal/buffer/buffer_generated_test.go | 1889 ++++++++++++++++++++++ internal/buffer/buffer_test.go | 108 ++ tools/testgen.go | 267 +++ 7 files changed, 2284 insertions(+) create mode 100644 internal/buffer/buffer_generated_test.go create mode 100644 internal/buffer/buffer_test.go create mode 100644 tools/testgen.go diff --git a/.gitignore b/.gitignore index 32302828..51f9a223 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ tools/build-version tools/build-date tools/info-plist tools/bindata +tools/vscode-tests/ *.hdr diff --git a/Makefile b/Makefile index 4d536210..217963c2 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ ADDITIONAL_GO_LINKER_FLAGS = $(shell GOOS=$(shell go env GOHOSTOS) \ GOARCH=$(shell go env GOHOSTARCH)) GOBIN ?= $(shell go env GOPATH)/bin GOVARS = -X github.com/zyedidia/micro/internal/util.Version=$(VERSION) -X github.com/zyedidia/micro/internal/util.CommitHash=$(HASH) -X 'github.com/zyedidia/micro/internal/util.CompileDate=$(DATE)' -X github.com/zyedidia/micro/internal/util.Debug=OFF +VSCODE_TESTS_BASE_URL = 'https://raw.githubusercontent.com/microsoft/vscode/e6a45f4242ebddb7aa9a229f85555e8a3bd987e2/src/vs/editor/test/common/model/' # Builds micro after checking dependencies but without updating the runtime build: @@ -50,8 +51,20 @@ runtime: mv runtime.go internal/config gofmt -w internal/config/runtime.go +testgen: + mkdir -p tools/vscode-tests + cd tools/vscode-tests && \ + curl --remote-name-all $(VSCODE_TESTS_BASE_URL){editableTextModelAuto,editableTextModel,model.line}.test.ts + tsc tools/vscode-tests/*.ts > /dev/null; true + go run tools/testgen.go tools/vscode-tests/*.js > buffer_generated_test.go + mv buffer_generated_test.go internal/buffer + gofmt -w internal/buffer/buffer_generated_test.go + test: go test ./internal/... +bench: + go test -bench=. ./internal/... + clean: rm -f micro diff --git a/go.mod b/go.mod index 92c7c196..40d6b075 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/mattn/go-isatty v0.0.11 github.com/mattn/go-runewidth v0.0.7 github.com/mitchellh/go-homedir v1.1.0 + github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff github.com/sergi/go-diff v1.1.0 github.com/stretchr/testify v1.4.0 github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb @@ -19,6 +20,7 @@ require ( github.com/zyedidia/tcell v1.4.4 github.com/zyedidia/terminal v0.0.0-20180726154117-533c623e2415 golang.org/x/text v0.3.2 + gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/yaml.v2 v2.2.7 layeh.com/gopher-luar v1.0.7 ) diff --git a/go.sum b/go.sum index 570a1299..3a0bea39 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff h1:+6NUiITWwE5q1KO6SAfUX918c+Tab0+tGAM/mtdlUyA= +github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= @@ -67,6 +69,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= diff --git a/internal/buffer/buffer_generated_test.go b/internal/buffer/buffer_generated_test.go new file mode 100644 index 00000000..3302fd35 --- /dev/null +++ b/internal/buffer/buffer_generated_test.go @@ -0,0 +1,1889 @@ +// This file is generated from VSCode model tests by the testgen tool. +// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten! + +package buffer + +import "testing" + +func TestAuto1(t *testing.T) { + check( + t, + []string{ + "ioe", + "", + "yjct", + "", + "", + }, + []operation{ + operation{ + start: Loc{1, 0}, + end: Loc{1, 0}, + text: []string{ + "b", + "r", + "fq", + }, + }, + operation{ + start: Loc{3, 0}, + end: Loc{0, 1}, + text: []string{ + "", + "", + }, + }, + }, + []string{ + "ib", + "r", + "fqoe", + "", + "yjct", + "", + "", + }, + ) +} + +func TestAuto2(t *testing.T) { + check( + t, + []string{ + "f", + "littnhskrq", + "utxvsizqnk", + "lslqz", + "jxn", + "gmm", + }, + []operation{ + operation{ + start: Loc{1, 0}, + end: Loc{1, 0}, + text: []string{ + "", + "o", + }, + }, + operation{ + start: Loc{3, 1}, + end: Loc{3, 1}, + text: []string{ + "zaq", + "avb", + }, + }, + operation{ + start: Loc{4, 1}, + end: Loc{1, 5}, + text: []string{ + "jlr", + "zl", + "j", + }, + }, + }, + []string{ + "f", + "o", + "litzaq", + "avbtjlr", + "zl", + "jmm", + }, + ) +} + +func TestAuto3(t *testing.T) { + check( + t, + []string{ + "ofw", + "qsxmziuvzw", + "rp", + "qsnymek", + "elth", + "wmgzbwudxz", + "iwsdkndh", + "bujlbwb", + "asuouxfv", + "xuccnb", + }, + []operation{ + operation{ + start: Loc{2, 3}, + end: Loc{2, 3}, + text: []string{ + "", + }, + }, + }, + []string{ + "ofw", + "qsxmziuvzw", + "rp", + "qsnymek", + "elth", + "wmgzbwudxz", + "iwsdkndh", + "bujlbwb", + "asuouxfv", + "xuccnb", + }, + ) +} + +func TestAuto4(t *testing.T) { + check( + t, + []string{ + "fefymj", + "qum", + "vmiwxxaiqq", + "dz", + "lnqdgorosf", + }, + []operation{ + operation{ + start: Loc{2, 0}, + end: Loc{4, 0}, + text: []string{ + "hp", + }, + }, + operation{ + start: Loc{6, 0}, + end: Loc{0, 1}, + text: []string{ + "kcg", + "", + "mpx", + }, + }, + operation{ + start: Loc{1, 1}, + end: Loc{1, 1}, + text: []string{ + "", + "aw", + "", + }, + }, + operation{ + start: Loc{1, 1}, + end: Loc{1, 1}, + text: []string{ + "vqr", + "mo", + }, + }, + operation{ + start: Loc{1, 3}, + end: Loc{2, 4}, + text: []string{ + "xyc", + }, + }, + }, + []string{ + "fehpmjkcg", + "", + "mpxq", + "aw", + "vqr", + "moum", + "vmiwxxaiqq", + "dxycqdgorosf", + }, + ) +} + +func TestBug19872UndoIsFunky(t *testing.T) { + check( + t, + []string{ + "something", + " A", + "", + " B", + "something else", + }, + []operation{ + operation{ + start: Loc{0, 1}, + end: Loc{1, 1}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{0, 2}, + end: Loc{1, 3}, + text: []string{ + "", + }, + }, + }, + []string{ + "something", + "A", + "B", + "something else", + }, + ) +} + +func TestBug19872UndoIsFunky_2(t *testing.T) { + check( + t, + []string{ + "something", + "A", + "B", + "something else", + }, + []operation{ + operation{ + start: Loc{0, 1}, + end: Loc{0, 1}, + text: []string{ + " ", + }, + }, + operation{ + start: Loc{0, 2}, + end: Loc{0, 2}, + text: []string{ + "", + " ", + }, + }, + }, + []string{ + "something", + " A", + "", + " B", + "something else", + }, + ) +} + +func TestInsertEmptyText(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestLastOpIsNoOp(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{1, 0}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{0, 3}, + end: Loc{0, 3}, + text: []string{ + "", + }, + }, + }, + []string{ + "y First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestInsertTextWithoutNewline1(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "foo ", + }, + }, + }, + []string{ + "foo My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestInsertTextWithoutNewline2(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{2, 0}, + end: Loc{2, 0}, + text: []string{ + " foo", + }, + }, + }, + []string{ + "My foo First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestInsertOneNewline(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{3, 0}, + end: Loc{3, 0}, + text: []string{ + "", + "", + }, + }, + }, + []string{ + "My ", + "First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestInsertTextWithOneNewline(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{2, 0}, + end: Loc{2, 0}, + text: []string{ + " new line", + "No longer", + }, + }, + }, + []string{ + "My new line", + "No longer First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestInsertTextWithTwoNewlines(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{2, 0}, + end: Loc{2, 0}, + text: []string{ + " new line", + "One more line in the middle", + "No longer", + }, + }, + }, + []string{ + "My new line", + "One more line in the middle", + "No longer First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestInsertTextWithManyNewlines(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{2, 0}, + end: Loc{2, 0}, + text: []string{ + "", + "", + "", + "", + "", + }, + }, + }, + []string{ + "My", + "", + "", + "", + " First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestInsertMultipleNewlines(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{2, 0}, + end: Loc{2, 0}, + text: []string{ + "", + "", + "", + "", + "", + }, + }, + operation{ + start: Loc{14, 2}, + end: Loc{14, 2}, + text: []string{ + "a", + "b", + }, + }, + }, + []string{ + "My", + "", + "", + "", + " First Line", + "\t\tMy Second Line", + " Third Linea", + "b", + "", + "1", + }, + ) +} + +func TestDeleteEmptyText(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestDeleteTextFromOneLine(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{1, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "y First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestDeleteTextFromOneLine2(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{2, 0}, + text: []string{ + "a", + }, + }, + }, + []string{ + "a First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestDeleteAllTextFromALine(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{13, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestDeleteTextFromTwoLines(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{3, 0}, + end: Loc{5, 1}, + text: []string{ + "", + }, + }, + }, + []string{ + "My Second Line", + " Third Line", + "", + "1", + }, + ) +} + +func TestDeleteTextFromManyLines(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{3, 0}, + end: Loc{4, 2}, + text: []string{ + "", + }, + }, + }, + []string{ + "My Third Line", + "", + "1", + }, + ) +} + +func TestDeleteEverything(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "1", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{1, 4}, + text: []string{ + "", + }, + }, + }, + []string{ + "", + }, + ) +} + +func TestTwoUnrelatedEdits(t *testing.T) { + check( + t, + []string{ + "My First Line", + "\t\tMy Second Line", + " Third Line", + "", + "123", + }, + []operation{ + operation{ + start: Loc{0, 1}, + end: Loc{2, 1}, + text: []string{ + "\t", + }, + }, + operation{ + start: Loc{0, 2}, + end: Loc{4, 2}, + text: []string{ + "", + }, + }, + }, + []string{ + "My First Line", + "\tMy Second Line", + "Third Line", + "", + "123", + }, + ) +} + +func TestTwoEditsOnOneLine(t *testing.T) { + check( + t, + []string{ + "\t\tfirst\t ", + "\t\tsecond line", + "\tthird line", + "fourth line", + "\t\t\t\t", + }, + []operation{ + operation{ + start: Loc{2, 4}, + end: Loc{6, 4}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{11, 4}, + end: Loc{15, 4}, + text: []string{ + "", + }, + }, + }, + []string{ + "\t\tfirst\t ", + "\t\tsecond line", + "\tthird line", + "fourth line", + "\t\tfifth\t\t", + }, + ) +} + +func TestManyEdits(t *testing.T) { + check( + t, + []string{ + "{\"x\" : 1}", + }, + []operation{ + operation{ + start: Loc{1, 0}, + end: Loc{1, 0}, + text: []string{ + "\n ", + }, + }, + operation{ + start: Loc{4, 0}, + end: Loc{5, 0}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{8, 0}, + end: Loc{8, 0}, + text: []string{ + "\n", + }, + }, + }, + []string{ + "{", + " \"x\": 1", + "}", + }, + ) +} + +func TestManyEditsReversed(t *testing.T) { + check( + t, + []string{ + "{", + " \"x\": 1", + "}", + }, + []operation{ + operation{ + start: Loc{1, 0}, + end: Loc{2, 1}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{5, 1}, + end: Loc{5, 1}, + text: []string{ + " ", + }, + }, + operation{ + start: Loc{8, 1}, + end: Loc{0, 2}, + text: []string{ + "", + }, + }, + }, + []string{ + "{\"x\" : 1}", + }, + ) +} + +func TestReplacingNewlines1(t *testing.T) { + check( + t, + []string{ + "{", + "\"a\": true,", + "", + "\"b\": true", + "}", + }, + []operation{ + operation{ + start: Loc{1, 0}, + end: Loc{0, 1}, + text: []string{ + "", + "\t", + }, + }, + operation{ + start: Loc{10, 1}, + end: Loc{0, 3}, + text: []string{ + "", + "\t", + }, + }, + }, + []string{ + "{", + "\t\"a\": true,", + "\t\"b\": true", + "}", + }, + ) +} + +func TestReplacingNewlines2(t *testing.T) { + check( + t, + []string{ + "some text", + "some more text", + "now comes an empty line", + "", + "after empty line", + "and the last line", + }, + []operation{ + operation{ + start: Loc{4, 0}, + end: Loc{0, 2}, + text: []string{ + " text", + "some more text", + "some more text", + }, + }, + operation{ + start: Loc{1, 2}, + end: Loc{0, 3}, + text: []string{ + "o more lines", + "asd", + "asd", + "asd", + }, + }, + operation{ + start: Loc{0, 4}, + end: Loc{5, 4}, + text: []string{ + "zzzzzzzz", + }, + }, + operation{ + start: Loc{10, 4}, + end: Loc{15, 5}, + text: []string{ + "1", + "2", + "3", + "4", + }, + }, + }, + []string{ + "some text", + "some more text", + "some more textno more lines", + "asd", + "asd", + "asd", + "zzzzzzzz empt1", + "2", + "3", + "4ne", + }, + ) +} + +func TestAdvanced1(t *testing.T) { + check( + t, + []string{ + " { \"d\": [", + " null", + " ] /*comment*/", + " ,\"e\": /*comment*/ [null] }", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{1, 0}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{2, 0}, + end: Loc{9, 0}, + text: []string{ + "", + " ", + }, + }, + operation{ + start: Loc{15, 0}, + end: Loc{13, 1}, + text: []string{ + "", + " ", + }, + }, + operation{ + start: Loc{17, 1}, + end: Loc{8, 2}, + text: []string{ + "", + " ", + }, + }, + operation{ + start: Loc{21, 2}, + end: Loc{8, 3}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{9, 3}, + end: Loc{9, 3}, + text: []string{ + "", + " ", + }, + }, + operation{ + start: Loc{27, 3}, + end: Loc{27, 3}, + text: []string{ + "", + " ", + }, + }, + operation{ + start: Loc{31, 3}, + end: Loc{31, 3}, + text: []string{ + "", + " ", + }, + }, + operation{ + start: Loc{32, 3}, + end: Loc{33, 3}, + text: []string{ + "", + "", + }, + }, + }, + []string{ + "{", + " \"d\": [", + " null", + " ] /*comment*/,", + " \"e\": /*comment*/ [", + " null", + " ]", + "}", + }, + ) +} + +func TestAdvancedSimplified(t *testing.T) { + check( + t, + []string{ + " abc", + " ,def", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{3, 0}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{6, 0}, + end: Loc{1, 1}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{2, 1}, + end: Loc{2, 1}, + text: []string{ + "", + "", + }, + }, + }, + []string{ + "abc,", + "def", + }, + ) +} + +func TestIssue144(t *testing.T) { + check( + t, + []string{ + "package caddy", + "", + "func main() {", + "\tfmt.Println(\"Hello World! :)\")", + "}", + "", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 5}, + text: []string{ + "package caddy", + "", + "import \"fmt\"", + "", + "func main() {", + "\tfmt.Println(\"Hello World! :)\")", + "}", + "", + }, + }, + }, + []string{ + "package caddy", + "", + "import \"fmt\"", + "", + "func main() {", + "\tfmt.Println(\"Hello World! :)\")", + "}", + "", + }, + ) +} + +func TestIssue2586ReplacingSelectedEndOfLineWithNewlineLocksUpTheDocument(t *testing.T) { + check( + t, + []string{ + "something", + "interesting", + }, + []operation{ + operation{ + start: Loc{9, 0}, + end: Loc{0, 1}, + text: []string{ + "", + "", + }, + }, + }, + []string{ + "something", + "interesting", + }, + ) +} + +func TestIssue3980(t *testing.T) { + check( + t, + []string{ + "class A {", + " someProperty = false;", + " someMethod() {", + " this.someMethod();", + " }", + "}", + }, + []operation{ + operation{ + start: Loc{7, 0}, + end: Loc{8, 0}, + text: []string{ + "", + "", + }, + }, + operation{ + start: Loc{16, 2}, + end: Loc{17, 2}, + text: []string{ + "", + "", + }, + }, + operation{ + start: Loc{17, 2}, + end: Loc{17, 2}, + text: []string{ + " ", + }, + }, + operation{ + start: Loc{4, 3}, + end: Loc{4, 3}, + text: []string{ + " ", + }, + }, + }, + []string{ + "class A", + "{", + " someProperty = false;", + " someMethod()", + " {", + " this.someMethod();", + " }", + "}", + }, + ) +} + +func TestTouchingEditsTwoInsertsAtTheSamePosition(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "a", + }, + }, + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "b", + }, + }, + }, + []string{ + "abhello world", + }, + ) +} + +func TestTouchingEditsInsertAndReplaceTouching(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "b", + }, + }, + operation{ + start: Loc{0, 0}, + end: Loc{2, 0}, + text: []string{ + "ab", + }, + }, + }, + []string{ + "babllo world", + }, + ) +} + +func TestTouchingEditsTwoTouchingReplaces(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{1, 0}, + text: []string{ + "H", + }, + }, + operation{ + start: Loc{1, 0}, + end: Loc{2, 0}, + text: []string{ + "E", + }, + }, + }, + []string{ + "HEllo world", + }, + ) +} + +func TestTouchingEditsTwoTouchingDeletes(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{1, 0}, + text: []string{ + "", + }, + }, + operation{ + start: Loc{1, 0}, + end: Loc{2, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "llo world", + }, + ) +} + +func TestTouchingEditsInsertAndReplace(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "H", + }, + }, + operation{ + start: Loc{0, 0}, + end: Loc{2, 0}, + text: []string{ + "e", + }, + }, + }, + []string{ + "Hello world", + }, + ) +} + +func TestTouchingEditsReplaceAndInsert(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{2, 0}, + text: []string{ + "H", + }, + }, + operation{ + start: Loc{2, 0}, + end: Loc{2, 0}, + text: []string{ + "e", + }, + }, + }, + []string{ + "Hello world", + }, + ) +} + +func TestSingleDelete1(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{1, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "ello world", + }, + ) +} + +func TestSingleDelete2(t *testing.T) { + check( + t, + []string{ + "helloworld", + }, + []operation{ + operation{ + start: Loc{2, 0}, + end: Loc{7, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "herld", + }, + ) +} + +func TestSingleDelete3(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{5, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + " world", + }, + ) +} + +func TestSingleDelete4(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{1, 0}, + end: Loc{6, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "hworld", + }, + ) +} + +func TestSingleDelete5(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{11, 0}, + text: []string{ + "", + }, + }, + }, + []string{ + "", + }, + ) +} + +func TestMultiDelete6(t *testing.T) { + check( + t, + []string{ + "hello world", + "hello world", + "hello world", + }, + []operation{ + operation{ + start: Loc{5, 0}, + end: Loc{5, 2}, + text: []string{ + "", + }, + }, + }, + []string{ + "hello world", + }, + ) +} + +func TestMultiDelete7(t *testing.T) { + check( + t, + []string{ + "hello world", + "hello world", + "hello world", + }, + []operation{ + operation{ + start: Loc{11, 0}, + end: Loc{11, 2}, + text: []string{ + "", + }, + }, + }, + []string{ + "hello world", + }, + ) +} + +func TestMultiDelete8(t *testing.T) { + check( + t, + []string{ + "hello world", + "hello world", + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 2}, + text: []string{ + "", + }, + }, + }, + []string{ + "hello world", + }, + ) +} + +func TestMultiDelete9(t *testing.T) { + check( + t, + []string{ + "hello world", + "hello world", + "hello world", + }, + []operation{ + operation{ + start: Loc{11, 0}, + end: Loc{0, 2}, + text: []string{ + "", + }, + }, + }, + []string{ + "hello worldhello world", + }, + ) +} + +func TestSingleInsert1(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "xx", + }, + }, + }, + []string{ + "xxhello world", + }, + ) +} + +func TestSingleInsert2(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{1, 0}, + end: Loc{1, 0}, + text: []string{ + "xx", + }, + }, + }, + []string{ + "hxxello world", + }, + ) +} + +func TestSingleInsert3(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{5, 0}, + end: Loc{5, 0}, + text: []string{ + "xx", + }, + }, + }, + []string{ + "helloxx world", + }, + ) +} + +func TestSingleInsert4(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{6, 0}, + end: Loc{6, 0}, + text: []string{ + "xx", + }, + }, + }, + []string{ + "hello xxworld", + }, + ) +} + +func TestSingleInsert5(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{11, 0}, + end: Loc{11, 0}, + text: []string{ + "xx", + }, + }, + }, + []string{ + "hello worldxx", + }, + ) +} + +func TestMultiInsert6(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{0, 0}, + end: Loc{0, 0}, + text: []string{ + "\n", + }, + }, + }, + []string{ + "", + "hello world", + }, + ) +} + +func TestMultiInsert7(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{11, 0}, + end: Loc{11, 0}, + text: []string{ + "\n", + }, + }, + }, + []string{ + "hello world", + "", + }, + ) +} + +func TestMultiInsert8(t *testing.T) { + check( + t, + []string{ + "hello world", + }, + []operation{ + operation{ + start: Loc{6, 0}, + end: Loc{6, 0}, + text: []string{ + "\n", + }, + }, + }, + []string{ + "hello ", + "world", + }, + ) +} + +func TestMultiInsert9(t *testing.T) { + check( + t, + []string{ + "hello world", + "hello world", + }, + []operation{ + operation{ + start: Loc{6, 0}, + end: Loc{6, 0}, + text: []string{ + "xx\nyy", + }, + }, + }, + []string{ + "hello xx", + "yyworld", + "hello world", + }, + ) +} + +func BenchmarkBuffer(b *testing.B) { + for i := 0; i < b.N; i++ { + TestAuto1(nil) + TestAuto2(nil) + TestAuto3(nil) + TestAuto4(nil) + TestBug19872UndoIsFunky(nil) + TestBug19872UndoIsFunky_2(nil) + TestInsertEmptyText(nil) + TestLastOpIsNoOp(nil) + TestInsertTextWithoutNewline1(nil) + TestInsertTextWithoutNewline2(nil) + TestInsertOneNewline(nil) + TestInsertTextWithOneNewline(nil) + TestInsertTextWithTwoNewlines(nil) + TestInsertTextWithManyNewlines(nil) + TestInsertMultipleNewlines(nil) + TestDeleteEmptyText(nil) + TestDeleteTextFromOneLine(nil) + TestDeleteTextFromOneLine2(nil) + TestDeleteAllTextFromALine(nil) + TestDeleteTextFromTwoLines(nil) + TestDeleteTextFromManyLines(nil) + TestDeleteEverything(nil) + TestTwoUnrelatedEdits(nil) + TestTwoEditsOnOneLine(nil) + TestManyEdits(nil) + TestManyEditsReversed(nil) + TestReplacingNewlines1(nil) + TestReplacingNewlines2(nil) + TestAdvanced1(nil) + TestAdvancedSimplified(nil) + TestIssue144(nil) + TestIssue2586ReplacingSelectedEndOfLineWithNewlineLocksUpTheDocument(nil) + TestIssue3980(nil) + TestTouchingEditsTwoInsertsAtTheSamePosition(nil) + TestTouchingEditsInsertAndReplaceTouching(nil) + TestTouchingEditsTwoTouchingReplaces(nil) + TestTouchingEditsTwoTouchingDeletes(nil) + TestTouchingEditsInsertAndReplace(nil) + TestTouchingEditsReplaceAndInsert(nil) + TestSingleDelete1(nil) + TestSingleDelete2(nil) + TestSingleDelete3(nil) + TestSingleDelete4(nil) + TestSingleDelete5(nil) + TestMultiDelete6(nil) + TestMultiDelete7(nil) + TestMultiDelete8(nil) + TestMultiDelete9(nil) + TestSingleInsert1(nil) + TestSingleInsert2(nil) + TestSingleInsert3(nil) + TestSingleInsert4(nil) + TestSingleInsert5(nil) + TestMultiInsert6(nil) + TestMultiInsert7(nil) + TestMultiInsert8(nil) + TestMultiInsert9(nil) + } +} diff --git a/internal/buffer/buffer_test.go b/internal/buffer/buffer_test.go new file mode 100644 index 00000000..1e941d13 --- /dev/null +++ b/internal/buffer/buffer_test.go @@ -0,0 +1,108 @@ +package buffer + +import ( + "strings" + "testing" + + testifyAssert "github.com/stretchr/testify/assert" + lua "github.com/yuin/gopher-lua" + + ulua "github.com/zyedidia/micro/internal/lua" +) + +type operation struct { + start Loc + end Loc + text []string +} + +type asserter interface { + Equal(interface{}, interface{}, ...interface{}) bool + NotEqual(interface{}, interface{}, ...interface{}) bool +} + +type noOpAsserter struct { +} + +func (a *noOpAsserter) Equal(interface{}, interface{}, ...interface{}) bool { + return true +} + +func (a *noOpAsserter) NotEqual(interface{}, interface{}, ...interface{}) bool { + return true +} + +func init() { + ulua.L = lua.NewState() +} + +func check(t *testing.T, before []string, operations []operation, after []string) { + var assert asserter + if t == nil { + // Benchmark mode; don't perform assertions + assert = &noOpAsserter{} + } else { + assert = testifyAssert.New(t) + } + + b := NewBufferFromString(strings.Join(before, "\n"), "", BTDefault) + + assert.NotEqual(b.GetName(), "") + assert.Equal(b.ExternallyModified(), false) + assert.Equal(b.Modified(), false) + assert.Equal(b.NumCursors(), 1) + + checkText := func(lines []string) { + assert.Equal(b.Bytes(), []byte(strings.Join(lines, "\n"))) + assert.Equal(b.LinesNum(), len(lines)) + for i, s := range lines { + assert.Equal(b.Line(i), s) + assert.Equal(b.LineBytes(i), []byte(s)) + } + } + + checkText(before) + + var cursors []*Cursor + + for _, op := range operations { + cursor := NewCursor(b, op.start) + cursor.SetSelectionStart(op.start) + cursor.SetSelectionEnd(op.end) + b.AddCursor(cursor) + cursors = append(cursors, cursor) + } + + assert.Equal(b.NumCursors(), 1+len(operations)) + + for i, op := range operations { + cursor := cursors[i] + cursor.DeleteSelection() + b.Insert(cursor.Loc, strings.Join(op.text, "\n")) + } + + checkText(after) + + for _ = range operations { + b.UndoOneEvent() + b.UndoOneEvent() + } + + checkText(before) + + for i, op := range operations { + cursor := cursors[i] + assert.Equal(cursor.Loc, op.start) + assert.Equal(cursor.CurSelection[0], op.start) + assert.Equal(cursor.CurSelection[1], op.end) + } + + for _ = range operations { + b.RedoOneEvent() + b.RedoOneEvent() + } + + checkText(after) + + b.Close() +} diff --git a/tools/testgen.go b/tools/testgen.go new file mode 100644 index 00000000..6d53fa57 --- /dev/null +++ b/tools/testgen.go @@ -0,0 +1,267 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + + "github.com/robertkrimen/otto/ast" + "github.com/robertkrimen/otto/parser" +) + +type walker struct { + nodes []ast.Node +} + +func (w *walker) Enter(node ast.Node) ast.Visitor { + w.nodes = append(w.nodes, node) + return w +} + +func (w *walker) Exit(node ast.Node) { +} + +func getAllNodes(node ast.Node) []ast.Node { + w := &walker{} + ast.Walk(w, node) + return w.nodes +} + +func getCalls(node ast.Node, name string) []*ast.CallExpression { + nodes := []*ast.CallExpression{} + for _, n := range getAllNodes(node) { + if ce, ok := n.(*ast.CallExpression); ok { + var calleeName string + switch callee := ce.Callee.(type) { + case *ast.Identifier: + calleeName = callee.Name + case *ast.DotExpression: + calleeName = callee.Identifier.Name + default: + continue + } + if calleeName == name { + nodes = append(nodes, ce) + } + } + } + return nodes +} + +func getPropertyValue(node ast.Node, key string) ast.Expression { + for _, p := range node.(*ast.ObjectLiteral).Value { + if p.Key == key { + return p.Value + } + } + return nil +} + +type operation struct { + startLine int + startColumn int + endLine int + endColumn int + text []string +} + +type check struct { + before []string + operations []operation + after []string +} + +type test struct { + description string + checks []check +} + +func stringSliceToGoSource(slice []string) string { + var b strings.Builder + b.WriteString("[]string{\n") + for _, s := range slice { + b.WriteString(fmt.Sprintf("%#v,\n", s)) + } + b.WriteString("}") + return b.String() +} + +func testToGoTest(test test, name string) string { + var b strings.Builder + + b.WriteString("func Test") + b.WriteString(name) + b.WriteString("(t *testing.T) {\n") + + for _, c := range test.checks { + b.WriteString("check(\n") + b.WriteString("t,\n") + b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.before))) + b.WriteString("[]operation{\n") + for _, op := range c.operations { + b.WriteString("operation{\n") + b.WriteString(fmt.Sprintf("start: Loc{%v, %v},\n", op.startColumn, op.startLine)) + b.WriteString(fmt.Sprintf("end: Loc{%v, %v},\n", op.endColumn, op.endLine)) + b.WriteString(fmt.Sprintf("text: %v,\n", stringSliceToGoSource(op.text))) + b.WriteString("},\n") + } + b.WriteString("},\n") + b.WriteString(fmt.Sprintf("%v,\n", stringSliceToGoSource(c.after))) + b.WriteString(")\n") + } + + b.WriteString("}\n") + + return b.String() +} + +func nodeToStringSlice(node ast.Node) []string { + var result []string + for _, s := range node.(*ast.ArrayLiteral).Value { + result = append(result, s.(*ast.StringLiteral).Value) + } + return result +} + +func nodeToStringSlice2(node ast.Node) []string { + var result []string + for _, o := range node.(*ast.ArrayLiteral).Value { + result = append(result, getPropertyValue(o, "text").(*ast.StringLiteral).Value) + } + return result +} + +func nodeToInt(node ast.Node) int { + return int(node.(*ast.NumberLiteral).Value.(int64)) +} + +func getChecks(node ast.Node) []check { + checks := []check{} + + for _, ce := range getCalls(node, "testApplyEdits") { + if len(ce.ArgumentList) != 3 { + // Wrong function + continue + } + + before := nodeToStringSlice2(ce.ArgumentList[0]) + after := nodeToStringSlice2(ce.ArgumentList[2]) + + var operations []operation + for _, op := range ce.ArgumentList[1].(*ast.ArrayLiteral).Value { + args := getPropertyValue(op, "range").(*ast.NewExpression).ArgumentList + operations = append(operations, operation{ + startLine: nodeToInt(args[0]) - 1, + startColumn: nodeToInt(args[1]) - 1, + endLine: nodeToInt(args[2]) - 1, + endColumn: nodeToInt(args[3]) - 1, + text: []string{getPropertyValue(op, "text").(*ast.StringLiteral).Value}, + }) + } + + checks = append(checks, check{before, operations, after}) + } + + for _, ce := range getCalls(node, "testApplyEditsWithSyncedModels") { + if len(ce.ArgumentList) > 3 && ce.ArgumentList[3].(*ast.BooleanLiteral).Value { + // inputEditsAreInvalid == true + continue + } + + before := nodeToStringSlice(ce.ArgumentList[0]) + after := nodeToStringSlice(ce.ArgumentList[2]) + + var operations []operation + for _, op := range getCalls(ce.ArgumentList[1], "editOp") { + operations = append(operations, operation{ + startLine: nodeToInt(op.ArgumentList[0]) - 1, + startColumn: nodeToInt(op.ArgumentList[1]) - 1, + endLine: nodeToInt(op.ArgumentList[2]) - 1, + endColumn: nodeToInt(op.ArgumentList[3]) - 1, + text: nodeToStringSlice(op.ArgumentList[4]), + }) + } + + checks = append(checks, check{before, operations, after}) + } + + return checks +} + +func getTests(node ast.Node) []test { + tests := []test{} + for _, ce := range getCalls(node, "test") { + description := ce.ArgumentList[0].(*ast.StringLiteral).Value + body := ce.ArgumentList[1].(*ast.FunctionLiteral).Body + checks := getChecks(body) + if len(checks) > 0 { + tests = append(tests, test{description, checks}) + } + } + return tests +} + +func main() { + var tests []test + + for _, filename := range os.Args[1:] { + source, err := ioutil.ReadFile(filename) + if err != nil { + log.Fatalln(err) + } + + program, err := parser.ParseFile(nil, "", source, parser.IgnoreRegExpErrors) + if err != nil { + log.Fatalln(err) + } + + tests = append(tests, getTests(program)...) + } + + if len(tests) == 0 { + log.Fatalln("no tests found!") + } + + fmt.Println("// This file is generated from VSCode model tests by the testgen tool.") + fmt.Println("// DO NOT EDIT THIS FILE BY HAND; your changes will be overwritten!\n") + fmt.Println("package buffer") + fmt.Println(`import "testing"`) + + re := regexp.MustCompile(`[^\w]`) + usedNames := map[string]bool{} + + var b strings.Builder + + for _, test := range tests { + name := strings.Title(strings.ToLower(test.description)) + name = re.ReplaceAllLiteralString(name, "") + if name == "" { + name = "Unnamed" + } + if usedNames[name] { + for i := 2; ; i++ { + newName := fmt.Sprintf("%v_%v", name, i) + if !usedNames[newName] { + name = newName + break + } + } + } + usedNames[name] = true + + fmt.Println(testToGoTest(test, name)) + + b.WriteString("Test") + b.WriteString(name) + b.WriteString("(nil)\n") + } + + fmt.Println("func BenchmarkBuffer(b *testing.B) {") + fmt.Println("for i := 0; i < b.N; i++ {") + fmt.Print(b.String()) + fmt.Println("}") + fmt.Println("}") +}