Files
bluekeyes-go-gitdiff/gitdiff/parser_test.go
T
Billy Keyes cbd3915448 Add final tests for git header parsing functions
This gets 100% test coverage for the functions involved in parsing
Git-style file headers. Whether that actually makes it correct remains
to be seen.
2019-03-19 21:50:50 -07:00

518 lines
11 KiB
Go

package gitdiff
import (
"bufio"
"os"
"reflect"
"strings"
"testing"
)
func TestLineOperations(t *testing.T) {
content := `the first line
the second line
the third line
`
newParser := func() *parser {
return &parser{r: bufio.NewReader(strings.NewReader(content))}
}
t.Run("readLine", func(t *testing.T) {
p := newParser()
line, err := p.Line()
if err != nil {
t.Fatalf("error reading first line: %v", err)
}
if line != "the first line\n" {
t.Fatalf("incorrect first line: %s", line)
}
line, err = p.Line()
if err != nil {
t.Fatalf("error reading second line: %v", err)
}
if line != "the second line\n" {
t.Fatalf("incorrect second line: %s", line)
}
})
t.Run("peekLine", func(t *testing.T) {
p := newParser()
line, err := p.PeekLine()
if err != nil {
t.Fatalf("error peeking line: %v", err)
}
if line != "the first line\n" {
t.Fatalf("incorrect peek line: %s", line)
}
// test that a second peek returns the same value
line, err = p.PeekLine()
if err != nil {
t.Fatalf("error peeking line: %v", err)
}
if line != "the first line\n" {
t.Fatalf("incorrect peek line: %s", line)
}
// test that reading the line returns the same value
line, err = p.Line()
if err != nil {
t.Fatalf("error reading line: %v", err)
}
if line != "the first line\n" {
t.Fatalf("incorrect line: %s", line)
}
})
}
func TestParseFragmentHeader(t *testing.T) {
tests := map[string]struct {
Input string
Output *Fragment
Err bool
}{
"shortest": {
Input: "@@ -0,0 +1 @@\n",
Output: &Fragment{
OldPosition: 0,
OldLines: 0,
NewPosition: 1,
NewLines: 1,
},
},
"standard": {
Input: "@@ -21,5 +28,9 @@\n",
Output: &Fragment{
OldPosition: 21,
OldLines: 5,
NewPosition: 28,
NewLines: 9,
},
},
"trailingWhitespace": {
Input: "@@ -21,5 +28,9 @@ \r\n",
Output: &Fragment{
OldPosition: 21,
OldLines: 5,
NewPosition: 28,
NewLines: 9,
},
},
"incomplete": {
Input: "@@ -12,3 +2\n",
Err: true,
},
"badNumbers": {
Input: "@@ -1a,2b +3c,4d @@\n",
Err: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var frag Fragment
err := parseFragmentHeader(&frag, test.Input)
if test.Err {
if err == nil {
t.Fatalf("expected error parsing header, but got nil")
}
return
}
if err != nil {
t.Fatalf("error parsing header: %v", err)
}
if !reflect.DeepEqual(*test.Output, frag) {
t.Fatalf("incorrect fragment\nexpected: %+v\nactual: %+v", *test.Output, frag)
}
})
}
}
func TestCleanName(t *testing.T) {
tests := map[string]struct {
Input string
Drop int
Output string
}{
"alreadyClean": {
Input: "a/b/c.txt", Output: "a/b/c.txt",
},
"doubleSlashes": {
Input: "a//b/c.txt", Output: "a/b/c.txt",
},
"tripleSlashes": {
Input: "a///b/c.txt", Output: "a/b/c.txt",
},
"dropPrefix": {
Input: "a/b/c.txt", Drop: 2, Output: "c.txt",
},
"removeDoublesBeforeDrop": {
Input: "a//b/c.txt", Drop: 1, Output: "b/c.txt",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
output := cleanName(test.Input, test.Drop)
if output != test.Output {
t.Fatalf("incorrect output: expected %q, actual %q", test.Output, output)
}
})
}
}
func TestParseName(t *testing.T) {
tests := map[string]struct {
Input string
Term rune
Drop int
Output string
N int
Err bool
}{
"singleUnquoted": {
Input: "dir/file.txt", Output: "dir/file.txt", N: 12,
},
"singleQuoted": {
Input: `"dir/file.txt"`, Output: "dir/file.txt", N: 14,
},
"quotedWithEscape": {
Input: `"dir/\"quotes\".txt"`, Output: `dir/"quotes".txt`, N: 20,
},
"quotedWithSpaces": {
Input: `"dir/space file.txt"`, Output: "dir/space file.txt", N: 20,
},
"tabTerminator": {
Input: "dir/space file.txt\tfile2.txt", Term: '\t', Output: "dir/space file.txt", N: 18,
},
"dropPrefix": {
Input: "a/dir/file.txt", Drop: 1, Output: "dir/file.txt", N: 14,
},
"multipleNames": {
Input: "dir/a.txt dir/b.txt", Term: -1, Output: "dir/a.txt", N: 9,
},
"devNull": {
Input: "/dev/null", Term: '\t', Drop: 1, Output: "/dev/null", N: 9,
},
"emptyString": {
Input: "", Err: true,
},
"emptyQuotedString": {
Input: `""`, Err: true,
},
"unterminatedQuotes": {
Input: `"dir/file.txt`, Err: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
output, n, err := parseName(test.Input, test.Term, test.Drop)
if test.Err {
if err == nil {
t.Fatalf("expected error parsing name, but got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error parsing name: %v", err)
}
if output != test.Output {
t.Errorf("incorect output: expected %q, actual: %q", test.Output, output)
}
if n != test.N {
t.Errorf("incorrect next position: expected %d, actual %d", test.N, n)
}
})
}
}
func TestParseGitHeaderData(t *testing.T) {
tests := map[string]struct {
InputFile *File
Line string
DefaultName string
OutputFile *File
End bool
Err bool
}{
"fragementEndsParsing": {
Line: "@@ -12,3 +12,2 @@\n",
End: true,
},
"unknownEndsParsing": {
Line: "GIT binary file\n",
End: true,
},
"oldFileName": {
Line: "--- a/dir/file.txt\n",
OutputFile: &File{
OldName: "dir/file.txt",
},
},
"oldFileNameDevNull": {
InputFile: &File{
IsNew: true,
},
Line: "--- /dev/null\n",
OutputFile: &File{
IsNew: true,
},
},
"oldFileNameInconsistent": {
InputFile: &File{
OldName: "dir/foo.txt",
},
Line: "--- a/dir/bar.txt\n",
Err: true,
},
"oldFileNameExistingCreateMismatch": {
InputFile: &File{
OldName: "dir/foo.txt",
IsNew: true,
},
Line: "--- /dev/null\n",
Err: true,
},
"oldFileNameParsedCreateMismatch": {
InputFile: &File{
IsNew: true,
},
Line: "--- a/dir/file.txt\n",
Err: true,
},
"oldFileNameMissing": {
Line: "--- \n",
Err: true,
},
"newFileName": {
Line: "+++ b/dir/file.txt\n",
OutputFile: &File{
NewName: "dir/file.txt",
},
},
"newFileNameDevNull": {
InputFile: &File{
IsDelete: true,
},
Line: "+++ /dev/null\n",
OutputFile: &File{
IsDelete: true,
},
},
"newFileNameInconsistent": {
InputFile: &File{
NewName: "dir/foo.txt",
},
Line: "+++ b/dir/bar.txt\n",
Err: true,
},
"newFileNameExistingDeleteMismatch": {
InputFile: &File{
NewName: "dir/foo.txt",
IsDelete: true,
},
Line: "+++ /dev/null\n",
Err: true,
},
"newFileNameParsedDeleteMismatch": {
InputFile: &File{
IsDelete: true,
},
Line: "+++ b/dir/file.txt\n",
Err: true,
},
"newFileNameMissing": {
Line: "+++ \n",
Err: true,
},
"oldMode": {
Line: "old mode 100644\n",
OutputFile: &File{
OldMode: os.FileMode(0100644),
},
},
"invalidOldMode": {
Line: "old mode rw\n",
Err: true,
},
"newMode": {
Line: "new mode 100755\n",
OutputFile: &File{
NewMode: os.FileMode(0100755),
},
},
"invalidNewMode": {
Line: "new mode rwx\n",
Err: true,
},
"deletedFileMode": {
Line: "deleted file mode 100644\n",
DefaultName: "dir/file.txt",
OutputFile: &File{
OldName: "dir/file.txt",
OldMode: os.FileMode(0100644),
IsDelete: true,
},
},
"newFileMode": {
Line: "new file mode 100755\n",
DefaultName: "dir/file.txt",
OutputFile: &File{
NewName: "dir/file.txt",
NewMode: os.FileMode(0100755),
IsNew: true,
},
},
"copyFrom": {
Line: "copy from dir/file.txt\n",
OutputFile: &File{
OldName: "dir/file.txt",
IsCopy: true,
},
},
"copyTo": {
Line: "copy to dir/file.txt\n",
OutputFile: &File{
NewName: "dir/file.txt",
IsCopy: true,
},
},
"renameFrom": {
Line: "rename from dir/file.txt\n",
OutputFile: &File{
OldName: "dir/file.txt",
IsRename: true,
},
},
"renameTo": {
Line: "rename to dir/file.txt\n",
OutputFile: &File{
NewName: "dir/file.txt",
IsRename: true,
},
},
"similarityIndex": {
Line: "similarity index 88\n",
OutputFile: &File{
Score: 88,
},
},
"similarityIndexTooBig": {
Line: "similarity index 9001\n",
OutputFile: &File{
Score: 0,
},
},
"similarityIndexInvalid": {
Line: "similarity index 12ab\n",
Err: true,
},
"indexFullSHA1AndMode": {
Line: "index 79c6d7f7b7e76c75b3d238f12fb1323f2333ba14..04fab916d8f938173cbb8b93469855f0e838f098 100644\n",
OutputFile: &File{
OldOID: "79c6d7f7b7e76c75b3d238f12fb1323f2333ba14",
NewOID: "04fab916d8f938173cbb8b93469855f0e838f098",
OldMode: os.FileMode(0100644),
},
},
"indexFullSHA1NoMode": {
Line: "index 79c6d7f7b7e76c75b3d238f12fb1323f2333ba14..04fab916d8f938173cbb8b93469855f0e838f098\n",
OutputFile: &File{
OldOID: "79c6d7f7b7e76c75b3d238f12fb1323f2333ba14",
NewOID: "04fab916d8f938173cbb8b93469855f0e838f098",
},
},
"indexAbbrevSHA1AndMode": {
Line: "index 79c6d7..04fab9 100644\n",
OutputFile: &File{
OldOID: "79c6d7",
NewOID: "04fab9",
OldMode: os.FileMode(0100644),
},
},
"indexInvalid": {
Line: "index 79c6d7f7b7e76c75b3d238f12fb1323f2333ba14\n",
Err: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
var f File
if test.InputFile != nil {
f = *test.InputFile
}
end, err := parseGitHeaderData(&f, test.Line, test.DefaultName)
if test.Err {
if err == nil {
t.Fatalf("expected error parsing header data, but got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error parsing header data: %v", err)
}
if test.OutputFile != nil && !reflect.DeepEqual(test.OutputFile, &f) {
t.Errorf("incorrect output:\nexpected: %+v\nactual: %+v", test.OutputFile, &f)
}
if end != test.End {
t.Errorf("incorrect end state, expected %t, actual %t", test.End, end)
}
})
}
}
func TestParseGitHeaderName(t *testing.T) {
tests := map[string]struct {
Input string
Output string
Err bool
}{
"twoMatchingNames": {
Input: "a/dir/file.txt b/dir/file.txt",
Output: "dir/file.txt",
},
"twoDifferentNames": {
Input: "a/dir/foo.txt b/dir/bar.txt",
Output: "",
},
"missingSecondName": {
Input: "a/dir/foo.txt",
Err: true,
},
"invalidName": {
Input: `"a/dir/file.txt b/dir/file.txt`,
Err: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
output, err := parseGitHeaderName(test.Input)
if test.Err {
if err == nil {
t.Fatalf("expected error parsing header name, but got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error parsing header name: %v", err)
}
if output != test.Output {
t.Errorf("incorrect output: expected %q, actual %q", test.Output, output)
}
})
}
}