Files
go-conventionalcommit/line_test.go
Jim Myhrberg 758deef7e9 refactor(parser): rework RawMessage into Buffer
The old RawMessage implementation effectively brute forced the initial
processing of a comment message by breaking it down into lines, and
grouping them into paragraphs.

This is useful, but, we actually only need the first paragraph, the last
paragraph, and then everything between. So there's no need to break down
the message into each paragraph.

In theory, the Buffer implementation is more performant than RawMessage
was, but most importantly I think it will be easier to work with it.
2021-08-15 19:57:13 +01:00

898 lines
16 KiB
Go

package conventionalcommit
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLine_Empty(t *testing.T) {
tests := []struct {
name string
line *Line
want bool
}{
{
name: "nil",
line: &Line{},
want: true,
},
{
name: "empty",
line: &Line{
Number: 1,
Content: []byte(""),
Break: []byte{},
},
want: true,
},
{
name: "space only",
line: &Line{
Number: 1,
Content: []byte(" "),
Break: []byte{},
},
want: false,
},
{
name: "tab only",
line: &Line{
Number: 1,
Content: []byte("\t\t"),
Break: []byte{},
},
want: false,
},
{
name: "spaces and tabs",
line: &Line{
Number: 1,
Content: []byte(" \t "),
Break: []byte{},
},
want: false,
},
{
name: "text",
line: &Line{
Number: 1,
Content: []byte("foobar"),
Break: []byte{},
},
want: false,
},
{
name: "text with surrounding white space",
line: &Line{
Number: 1,
Content: []byte(" foobar "),
Break: []byte{},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.line.Empty()
assert.Equal(t, tt.want, got)
})
}
}
func TestLine_Blank(t *testing.T) {
tests := []struct {
name string
line *Line
want bool
}{
{
name: "nil",
line: &Line{},
want: true,
},
{
name: "empty",
line: &Line{
Number: 1,
Content: []byte(""),
Break: []byte{},
},
want: true,
},
{
name: "space only",
line: &Line{
Number: 1,
Content: []byte(" "),
Break: []byte{},
},
want: true,
},
{
name: "tab only",
line: &Line{
Number: 1,
Content: []byte("\t\t"),
Break: []byte{},
},
want: true,
},
{
name: "spaces and tabs",
line: &Line{
Number: 1,
Content: []byte(" \t "),
Break: []byte{},
},
want: true,
},
{
name: "text",
line: &Line{
Number: 1,
Content: []byte("foobar"),
Break: []byte{},
},
want: false,
},
{
name: "text with surrounding white space",
line: &Line{
Number: 1,
Content: []byte(" foobar "),
Break: []byte{},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := tt.line.Blank()
assert.Equal(t, tt.want, got)
})
}
}
func TestNewLines(t *testing.T) {
tests := []struct {
name string
content []byte
want Lines
}{
{
name: "nil",
content: nil,
want: Lines{},
},
{
name: "empty",
content: []byte{},
want: Lines{},
},
{
name: "single line without trailing linebreak",
content: []byte("hello world"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte{},
},
},
},
{
name: "single line with trailing LF",
content: []byte("hello world\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "single line with trailing CRLF",
content: []byte("hello world\r\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "single line with trailing CR",
content: []byte("hello world\r"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by LF",
content: []byte("hello world\nfoo\nbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by LF with trailing LF",
content: []byte("hello world\nfoo\nbar\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte("\n"),
},
{
Number: 4,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CRLF",
content: []byte("hello world\r\nfoo\r\nbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CRLF with trailing CRLF",
content: []byte("hello world\r\nfoo\r\nbar\r\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte("\r\n"),
},
{
Number: 4,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CR",
content: []byte("hello world\rfoo\rbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by CR with trailing CR",
content: []byte("hello world\rfoo\rbar\r"),
want: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 3,
Content: []byte("bar"),
Break: []byte("\r"),
},
{
Number: 4,
Content: []byte(""),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by mixed break types",
content: []byte("hello\nworld\r\nfoo\rbar"),
want: Lines{
{
Number: 1,
Content: []byte("hello"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("world"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 4,
Content: []byte("bar"),
Break: []byte{},
},
},
},
{
name: "multiple lines separated by mixed break types with " +
"trailing LF",
content: []byte("hello\nworld\r\nfoo\rbar\n"),
want: Lines{
{
Number: 1,
Content: []byte("hello"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("world"),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("foo"),
Break: []byte("\r"),
},
{
Number: 4,
Content: []byte("bar"),
Break: []byte("\n"),
},
{
Number: 5,
Content: []byte(""),
Break: []byte{},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewLines(tt.content)
assert.Equal(t, tt.want, got)
})
}
}
var linesTestCases = []struct {
name string
lines Lines
bytes []byte
firstTextIndex int
lastTextIndex int
}{
{
name: "no lines",
lines: Lines{},
bytes: []byte(""),
firstTextIndex: -1,
lastTextIndex: -1,
},
{
name: "empty line",
lines: Lines{
{
Number: 1,
Content: []byte(""),
},
},
bytes: []byte(""),
firstTextIndex: -1,
lastTextIndex: -1,
},
{
name: "whitespace line",
lines: Lines{
{
Number: 1,
Content: []byte(" "),
},
},
bytes: []byte(" "),
firstTextIndex: -1,
lastTextIndex: -1,
},
{
name: "multiple whitespace lines",
lines: Lines{
{
Number: 1,
Content: []byte(" "),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("\t"),
Break: []byte("\n"),
},
{
Number: 3,
Content: []byte(" "),
Break: []byte{},
},
},
bytes: []byte(" \n\t\n "),
firstTextIndex: -1,
lastTextIndex: -1,
},
{
name: "single line",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
},
},
bytes: []byte("hello world"),
firstTextIndex: 0,
lastTextIndex: 0,
},
{
name: "single line with trailing LF",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
bytes: []byte("hello world\n"),
firstTextIndex: 0,
lastTextIndex: 0,
},
{
name: "single line with trailing CRLF",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
bytes: []byte("hello world\r\n"),
firstTextIndex: 0,
lastTextIndex: 0,
},
{
name: "single line with trailing CR",
lines: Lines{
{
Number: 1,
Content: []byte("hello world"),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte(""),
Break: []byte{},
},
},
bytes: []byte("hello world\r"),
firstTextIndex: 0,
lastTextIndex: 0,
},
{
name: "multi-line separated by LF",
lines: Lines{
{
Number: 1,
Content: []byte(""),
Break: []byte("\n"),
},
{
Number: 2,
Content: []byte("Aliquam feugiat tellus ut neque."),
Break: []byte("\n"),
},
{
Number: 3,
Content: []byte("Sed bibendum."),
Break: []byte("\n"),
},
{
Number: 4,
Content: []byte("Nullam libero mauris, consequat."),
Break: []byte("\n"),
},
{
Number: 5,
Content: []byte(""),
Break: []byte("\n"),
},
{
Number: 6,
Content: []byte("Integer placerat tristique nisl."),
Break: []byte("\n"),
},
{
Number: 7,
Content: []byte("Etiam vel neque nec dui bibendum."),
Break: []byte("\n"),
},
{
Number: 8,
Content: []byte(""),
Break: []byte("\n"),
},
{
Number: 9,
Content: []byte(""),
Break: []byte("\n"),
},
{
Number: 10,
Content: []byte("Nullam libero mauris, dictum id, arcu."),
Break: []byte("\n"),
},
{
Number: 11,
Content: []byte(""),
Break: []byte{},
},
},
bytes: []byte(
"\n" +
"Aliquam feugiat tellus ut neque.\n" +
"Sed bibendum.\n" +
"Nullam libero mauris, consequat.\n" +
"\n" +
"Integer placerat tristique nisl.\n" +
"Etiam vel neque nec dui bibendum.\n" +
"\n" +
"\n" +
"Nullam libero mauris, dictum id, arcu.\n",
),
firstTextIndex: 1,
lastTextIndex: 9,
},
{
name: "multi-line separated by CRLF",
lines: Lines{
{
Number: 1,
Content: []byte(""),
Break: []byte("\r\n"),
},
{
Number: 2,
Content: []byte("Aliquam feugiat tellus ut neque."),
Break: []byte("\r\n"),
},
{
Number: 3,
Content: []byte("Sed bibendum."),
Break: []byte("\r\n"),
},
{
Number: 4,
Content: []byte("Nullam libero mauris, consequat."),
Break: []byte("\r\n"),
},
{
Number: 5,
Content: []byte(""),
Break: []byte("\r\n"),
},
{
Number: 6,
Content: []byte("Integer placerat tristique nisl."),
Break: []byte("\r\n"),
},
{
Number: 7,
Content: []byte("Etiam vel neque nec dui bibendum."),
Break: []byte("\r\n"),
},
{
Number: 8,
Content: []byte(""),
Break: []byte("\r\n"),
},
{
Number: 9,
Content: []byte(""),
Break: []byte("\r\n"),
},
{
Number: 10,
Content: []byte("Nullam libero mauris, dictum id, arcu."),
Break: []byte("\r\n"),
},
{
Number: 11,
Content: []byte(""),
Break: []byte{},
},
},
bytes: []byte(
"\r\n" +
"Aliquam feugiat tellus ut neque.\r\n" +
"Sed bibendum.\r\n" +
"Nullam libero mauris, consequat.\r\n" +
"\r\n" +
"Integer placerat tristique nisl.\r\n" +
"Etiam vel neque nec dui bibendum.\r\n" +
"\r\n" +
"\r\n" +
"Nullam libero mauris, dictum id, arcu.\r\n",
),
firstTextIndex: 1,
lastTextIndex: 9,
},
{
name: "multi-line separated by CR",
lines: Lines{
{
Number: 1,
Content: []byte(""),
Break: []byte("\r"),
},
{
Number: 2,
Content: []byte("Aliquam feugiat tellus ut neque."),
Break: []byte("\r"),
},
{
Number: 3,
Content: []byte("Sed bibendum."),
Break: []byte("\r"),
},
{
Number: 4,
Content: []byte("Nullam libero mauris, consequat."),
Break: []byte("\r"),
},
{
Number: 5,
Content: []byte(""),
Break: []byte("\r"),
},
{
Number: 6,
Content: []byte("Integer placerat tristique nisl."),
Break: []byte("\r"),
},
{
Number: 7,
Content: []byte("Etiam vel neque nec dui bibendum."),
Break: []byte("\r"),
},
{
Number: 8,
Content: []byte(""),
Break: []byte("\r"),
},
{
Number: 9,
Content: []byte(""),
Break: []byte("\r"),
},
{
Number: 10,
Content: []byte("Nullam libero mauris, dictum id, arcu."),
Break: []byte("\r"),
},
{
Number: 11,
Content: []byte(""),
Break: []byte{},
},
},
bytes: []byte(
"\r" +
"Aliquam feugiat tellus ut neque.\r" +
"Sed bibendum.\r" +
"Nullam libero mauris, consequat.\r" +
"\r" +
"Integer placerat tristique nisl.\r" +
"Etiam vel neque nec dui bibendum.\r" +
"\r" +
"\r" +
"Nullam libero mauris, dictum id, arcu.\r",
),
firstTextIndex: 1,
lastTextIndex: 9,
},
}
func TestLines_FirstTextIndex(t *testing.T) {
for _, tt := range linesTestCases {
t.Run(tt.name, func(t *testing.T) {
got := tt.lines.FirstTextIndex()
assert.Equal(t, tt.firstTextIndex, got)
})
}
}
func BenchmarkLines_FirstTextIndex(b *testing.B) {
for _, tt := range linesTestCases {
b.Run(tt.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = tt.lines.FirstTextIndex()
}
})
}
}
func TestLines_LastTextIndex(t *testing.T) {
for _, tt := range linesTestCases {
t.Run(tt.name, func(t *testing.T) {
got := tt.lines.LastTextIndex()
assert.Equal(t, tt.lastTextIndex, got)
})
}
}
func BenchmarkLines_LastTextIndex(b *testing.B) {
for _, tt := range linesTestCases {
b.Run(tt.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = tt.lines.LastTextIndex()
}
})
}
}
func TestLines_Trim(t *testing.T) {
for _, tt := range linesTestCases {
t.Run(tt.name, func(t *testing.T) {
want := Lines{}
if tt.firstTextIndex != -1 {
want = tt.lines[tt.firstTextIndex : tt.lastTextIndex+1]
}
got := tt.lines.Trim()
assert.Equal(t, want, got)
})
}
}
func BenchmarkLines_Trim(b *testing.B) {
for _, tt := range linesTestCases {
b.Run(tt.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = tt.lines.Trim()
}
})
}
}
func TestLines_Bytes(t *testing.T) {
for _, tt := range linesTestCases {
t.Run(tt.name, func(t *testing.T) {
got := tt.lines.Bytes()
assert.Equal(t, tt.bytes, got)
})
}
}
func BenchmarkLines_Bytes(b *testing.B) {
for _, tt := range linesTestCases {
b.Run(tt.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = tt.lines.Bytes()
}
})
}
}
func TestLines_String(t *testing.T) {
for _, tt := range linesTestCases {
t.Run(tt.name, func(t *testing.T) {
got := tt.lines.String()
assert.Equal(t, string(tt.bytes), got)
})
}
}
func BenchmarkLines_String(b *testing.B) {
for _, tt := range linesTestCases {
b.Run(tt.name, func(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = tt.lines.String()
}
})
}
}