drone/git/sharedrepo/sharedrepo_test.go

777 lines
17 KiB
Go

// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package sharedrepo
import (
"bytes"
"reflect"
"testing"
"github.com/harness/gitness/git/parser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_parsePatchTextFilePayloads(t *testing.T) {
tests := []struct {
name string
arg [][]byte
wantErr string
want []patchTextFileReplacement
}{
{
name: "test no payloads",
arg: nil,
want: []patchTextFileReplacement{},
},
{
name: "test no zero byte splitter",
arg: [][]byte{
[]byte("0:1"),
},
wantErr: "Payload format is missing the content separator",
},
{
name: "test line range wrong format",
arg: [][]byte{
[]byte("0\u0000"),
},
wantErr: "Payload is missing the line number separator",
},
{
name: "test start line error returned",
arg: [][]byte{
[]byte("0:1\u0000"),
},
wantErr: "Payload start line number is invalid",
},
{
name: "test end line error returned",
arg: [][]byte{
[]byte("1:a\u0000"),
},
wantErr: "Payload end line number is invalid",
},
{
name: "test end smaller than start",
arg: [][]byte{
[]byte("2:1\u0000"),
},
wantErr: "Payload end line has to be at least as big as start line",
},
{
name: "payload empty",
arg: [][]byte{
[]byte("1:2\u0000"),
},
want: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 2,
Content: []byte{},
},
},
},
{
name: "payload non-empty with zero byte and line endings",
arg: [][]byte{
[]byte("1:eof\u0000a\nb\r\nc\u0000d"),
},
want: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: lineNumberEOF,
Content: []byte("a\nb\r\nc\u0000d"),
},
},
},
{
name: "multiple payloads",
arg: [][]byte{
[]byte("1:3\u0000a"),
[]byte("2:eof\u0000b"),
},
want: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 3,
Content: []byte("a"),
},
{
OmitFrom: 2,
ContinueFrom: lineNumberEOF,
Content: []byte("b"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parsePatchTextFilePayloads(tt.arg)
if tt.wantErr != "" {
assert.ErrorContains(t, err, tt.wantErr, "error doesn't match expected.")
} else if !reflect.DeepEqual(got, tt.want) {
t.Errorf("parsePatchTextFilePayloads() = %s, want %s", got, tt.want)
}
})
}
}
func Test_patchTextFileWritePatchedFile(t *testing.T) {
type arg struct {
file []byte
replacements []patchTextFileReplacement
}
tests := []struct {
name string
arg arg
wantErr string
want []byte
wantLE string
}{
{
name: "test no replacements (empty file)",
arg: arg{
file: []byte(""),
replacements: nil,
},
wantLE: "\n",
want: nil,
},
{
name: "test no replacements (single line no line ending)",
arg: arg{
file: []byte("l1"),
replacements: nil,
},
wantLE: "\n",
want: []byte("l1"),
},
{
name: "test no replacements keeps final line ending (LF)",
arg: arg{
file: []byte("l1\n"),
replacements: nil,
},
wantLE: "\n",
want: []byte("l1\n"),
},
{
name: "test no replacements keeps final line ending (CRLF)",
arg: arg{
file: []byte("l1\r\n"),
replacements: nil,
},
wantLE: "\r\n",
want: []byte("l1\r\n"),
},
{
name: "test no replacements multiple line endings",
arg: arg{
file: []byte("l1\r\nl2\nl3"),
replacements: nil,
},
wantLE: "\r\n",
want: []byte("l1\r\nl2\nl3"),
},
{
name: "test line ending correction with replacements (LF)",
arg: arg{
file: []byte("l1\nl2\r\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte("rl1\nrl2\r\nrl3"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1\nrl2\nrl3\nl2\r\nl3"),
},
{
name: "test line ending correction with replacements (CRLF)",
arg: arg{
file: []byte("l1\r\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte("rl1\nrl2\r\nrl3"),
},
},
},
wantLE: "\r\n",
want: []byte("l1\r\nrl1\r\nrl2\r\nrl3\r\nl2\nl3"),
},
{
name: "test line ending with replacements at eof (file none, replacement none)",
arg: arg{
file: []byte("l1\nl2"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1"),
},
{
name: "test line ending with replacements at eof (file none, replacement yes)",
arg: arg{
file: []byte("l1\nl2"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1\r\n"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1\n"),
},
{
name: "test line ending with replacements at eof (file yes, replacement none)",
arg: arg{
file: []byte("l1\nl2\r\n"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1"),
},
{
name: "test line ending with replacements at eof (file yes, replacement yes)",
arg: arg{
file: []byte("l1\nl2\r\n"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1\r\n"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1\n"),
},
{
name: "test final line ending doesn't increase line count",
arg: arg{
file: []byte("l1\n"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 3,
ContinueFrom: 3,
Content: []byte("rl1\r\n"),
},
},
},
wantErr: "Patch action for [3,3) is exceeding end of file with 1 line(s)",
},
{
name: "test replacement out of bounds (start)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 3,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1\r\n"),
},
},
},
wantErr: "Patch action for [3,eof) is exceeding end of file with 1 line(s)",
},
{
name: "test replacement out of bounds (end)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 3,
Content: []byte("rl1\r\n"),
},
},
},
wantErr: "Patch action for [1,3) is exceeding end of file with 1 line(s)",
},
{
name: "test replacement out of bounds (after eof)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1\r\n"),
},
{
OmitFrom: 2,
ContinueFrom: 3,
Content: []byte("rl1\r\n"),
},
},
},
wantErr: "Patch action for [2,3) is exceeding end of file with 1 line(s)",
},
{
name: "test replacement out of bounds (after last line)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 2,
Content: []byte("rl1\r\n"),
},
{
OmitFrom: 3,
ContinueFrom: 4,
Content: []byte("rl1\r\n"),
},
},
},
wantErr: "Patch action for [3,4) is exceeding end of file with 1 line(s)",
},
{
name: "test overlap before eof (with empty)",
arg: arg{
file: []byte(""),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 3,
Content: []byte(""),
},
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte(""),
},
},
},
wantErr: "Patch actions have conflicting ranges [1,3)x[2,2)",
},
{
name: "test overlap before eof (non-empty + unordered)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: 3,
Content: []byte(""),
},
{
OmitFrom: 1,
ContinueFrom: 3,
Content: []byte(""),
},
},
},
wantErr: "Patch actions have conflicting ranges [1,3)x[2,3)",
},
{
name: "test overlap before eof (non-empty eof end)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 3,
Content: []byte(""),
},
{
OmitFrom: 2,
ContinueFrom: lineNumberEOF,
Content: []byte(""),
},
},
},
wantErr: "Patch actions have conflicting ranges [1,3)x[2,eof)",
},
{
name: "test overlap after eof (empty)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: lineNumberEOF,
Content: []byte(""),
},
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte(""),
},
},
},
wantErr: "Patch actions have conflicting ranges [1,eof)x[2,2) for file with 3 line(s)",
},
{
name: "test overlap after eof (non-empty + unordered)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: 3,
Content: []byte(""),
},
{
OmitFrom: 1,
ContinueFrom: lineNumberEOF,
Content: []byte(""),
},
},
},
wantErr: "Patch actions have conflicting ranges [1,eof)x[2,3) for file with 3 line(s)",
},
{
name: "test overlap after eof (none-empty eof end)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: lineNumberEOF,
Content: []byte(""),
},
{
OmitFrom: 1,
ContinueFrom: lineNumberEOF,
Content: []byte(""),
},
},
},
wantErr: "Patch actions have conflicting ranges [1,eof)x[2,eof) for file with 3 line(s)",
},
{
name: "test insert (empty)",
arg: arg{
file: nil,
replacements: []patchTextFileReplacement{
{
OmitFrom: lineNumberEOF,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1\r\nrl2"),
},
},
},
wantLE: "\n",
want: []byte("rl1\nrl2"),
},
{
name: "test insert (start)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 1,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("rl1\nl1"),
},
{
name: "test insert (middle)",
arg: arg{
file: []byte("l1\nl2"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1\nl2"),
},
{
name: "test insert (end)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1"),
},
{
name: "test insert (eof)",
arg: arg{
file: []byte("l1"),
replacements: []patchTextFileReplacement{
{
OmitFrom: lineNumberEOF,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1"),
},
{
name: "test inserts (multiple at start+middle+end(normal+eof))",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 1,
Content: []byte("r1l1\nr1l2"),
},
{
OmitFrom: 1,
ContinueFrom: 1,
Content: []byte("r2l1\nr2l2"),
},
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte("r3l1\nr3l2"),
},
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte("r4l1\nr4l2"),
},
{
OmitFrom: 4,
ContinueFrom: 4,
Content: []byte("r5l1\nr5l2"),
},
{
OmitFrom: 4,
ContinueFrom: 4,
Content: []byte("r6l1\nr6l2"),
},
{
OmitFrom: lineNumberEOF,
ContinueFrom: lineNumberEOF,
Content: []byte("r7l1\nr7l2"),
},
{
OmitFrom: lineNumberEOF,
ContinueFrom: lineNumberEOF,
Content: []byte("r8l1\nr8l2"),
},
},
},
wantLE: "\n",
want: []byte(
"r1l1\nr1l2\nr2l1\nr2l2\nl1\nr3l1\nr3l2\nr4l1\nr4l2\nl2\nl3\nr5l1\nr5l2\nr6l1\nr6l2\nr7l1\nr7l2\nr8l1\nr8l2"),
},
{
name: "test replace (head)",
arg: arg{
file: []byte("l1\nl2"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 2,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("rl1\nl2"),
},
{
name: "test replace (middle)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 2,
ContinueFrom: 3,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nrl1\nl3"),
},
{
name: "test replace (end)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 3,
ContinueFrom: 4,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nl2\nrl1"),
},
{
name: "test replace (eof)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 3,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("l1\nl2\nrl1"),
},
{
name: "test replace (1-end)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: 4,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("rl1"),
},
{
name: "test replace (1-eof)",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 1,
ContinueFrom: lineNumberEOF,
Content: []byte("rl1"),
},
},
},
wantLE: "\n",
want: []byte("rl1"),
},
{
name: "test sorting",
arg: arg{
file: []byte("l1\nl2\nl3"),
replacements: []patchTextFileReplacement{
{
OmitFrom: 4,
ContinueFrom: 4,
Content: []byte("r5l1\nr5l2\r\n"),
},
{
OmitFrom: 4,
ContinueFrom: lineNumberEOF,
Content: []byte("r7l1\nr7l2\r\n"),
},
{
OmitFrom: 1,
ContinueFrom: 1,
Content: []byte("r0l1\nr0l2\r\n"),
},
{
OmitFrom: 2,
ContinueFrom: 4,
Content: []byte("r4l1\nr4l2\r\n"),
},
{
OmitFrom: 4,
ContinueFrom: 4,
Content: []byte("r6l1\nr6l2\r\n"),
},
{
OmitFrom: 1,
ContinueFrom: 2,
Content: []byte("r2l1\nr2l2\r\n"),
},
{
OmitFrom: 1,
ContinueFrom: 1,
Content: []byte("r1l1\nr1l2\r\n"),
},
{
OmitFrom: lineNumberEOF,
ContinueFrom: lineNumberEOF,
Content: []byte("r9l1\nr9l2\r\n"),
},
{
OmitFrom: 4,
ContinueFrom: lineNumberEOF,
Content: []byte("r8l1\nr8l2\r\n"),
},
{
OmitFrom: 2,
ContinueFrom: 2,
Content: []byte("r3l1\nr3l2\r\n"),
},
{
OmitFrom: lineNumberEOF,
ContinueFrom: lineNumberEOF,
Content: []byte("r10l1\nr10l2\r\n"),
},
},
},
wantLE: "\n",
want: []byte("r0l1\nr0l2\nr1l1\nr1l2\nr2l1\nr2l2\nr3l1\nr3l2\nr4l1\nr4l2\nr5l1\nr5l2\nr6l1\nr6l2\nr7l1\nr7l2" +
"\nr8l1\nr8l2\nr9l1\nr9l2\nr10l1\nr10l2\n"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scanner, le, err := parser.ReadTextFile(bytes.NewReader(tt.arg.file), nil)
require.NoError(t, err, "failed to read input file")
writer := bytes.Buffer{}
err = patchTextFileWritePatchedFile(scanner, tt.arg.replacements, le, &writer)
got := writer.Bytes()
if tt.wantErr != "" {
assert.ErrorContains(t, err, tt.wantErr, "error doesn't match expected.")
} else {
assert.Equal(t, tt.wantLE, le, "line ending doesn't match")
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("patchTextFileWritePatchedFile() = %q, want %q", string(got), string(tt.want))
}
}
})
}
}