blob: 0abcd04e0bd8d46180991f3a432a584127aa2e46 [file] [log] [blame]
// Copyright 2023 Google LLC
//
// 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 keepsorted
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
// initZerolog initializes zerolog to log as part of the test.
// It returns a function that restores zerolog to its state before this function was called.
func initZerolog(t testing.TB) {
oldLogger := log.Logger
log.Logger = log.Output(zerolog.NewTestWriter(t))
t.Cleanup(func() { log.Logger = oldLogger })
}
func defaultMetadataWith(opts blockOptions) blockMetadata {
return blockMetadata{
startDirective: "keep-sorted-test start",
endDirective: "keep-sorted-test end",
opts: opts,
}
}
func defaultMetadataWithCommentMarker(marker string) blockMetadata {
var opts blockOptions
opts.setCommentMarker(marker)
return defaultMetadataWith(opts)
}
func TestFix(t *testing.T) {
for _, tc := range []struct {
name string
in string
want string
wantAlreadyFixed bool
}{
{
name: "Empty",
in: `
// keep-sorted-test start
// keep-sorted-test end`,
want: `
// keep-sorted-test start
// keep-sorted-test end`,
wantAlreadyFixed: true,
},
{
name: "AlreadySorted",
in: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end`,
want: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end`,
wantAlreadyFixed: true,
},
{
name: "UnorderedBlock",
in: `
// keep-sorted-test start
2
1
3
// keep-sorted-test end`,
want: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end`,
},
{
name: "UnmatchedStart",
in: `
// keep-sorted-test start
// keep-sorted-test start
1
2
3
// keep-sorted-test end`,
want: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end`,
},
{
name: "UnmatchedEnd",
in: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end
// keep-sorted-test end`,
want: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end
`,
},
{
name: "MultipleFixes",
in: `
// keep-sorted-test end
// keep-sorted-test start
// keep-sorted-test start
2
1
3
// keep-sorted-test end
// keep-sorted-test start
foo
bar
baz
// keep-sorted-test end`,
want: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end
// keep-sorted-test start
bar
baz
foo
// keep-sorted-test end`,
},
} {
t.Run(tc.name, func(t *testing.T) {
initZerolog(t)
got, gotAlreadyFixed := New("keep-sorted-test", BlockOptions{}).Fix(tc.in, nil)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("Fix diff (-want +got):\n%s", diff)
}
if gotAlreadyFixed != tc.wantAlreadyFixed {
t.Errorf("alreadyFixed diff: got %t want %t", gotAlreadyFixed, tc.wantAlreadyFixed)
}
})
}
}
func TestFindings(t *testing.T) {
filename := "test"
for _, tc := range []struct {
name string
in string
modifiedLines []int
considerLintOption bool
want []*Finding
}{
{
name: "AlreadySorted",
in: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end`,
want: nil,
},
{
name: "NotSorted",
in: `
// keep-sorted-test start
2
1
3
// keep-sorted-test end`,
want: []*Finding{finding(filename, 3, 5, errorUnordered, "1\n2\n3\n")},
},
{
name: "SkipLines",
in: `
// keep-sorted-test start skip_lines=2
5
4
3
2
1
// keep-sorted-test end`,
want: []*Finding{finding(filename, 5, 7, errorUnordered, "1\n2\n3\n")},
},
{
name: "MismatchedStart",
in: `
// keep-sorted-test start`,
want: []*Finding{finding(filename, 2, 2, "This instruction doesn't have matching 'keep-sorted-test end' line", "")},
},
{
name: "MismatchedEnd",
in: `
// keep-sorted-test end`,
want: []*Finding{finding(filename, 2, 2, "This instruction doesn't have matching 'keep-sorted-test start' line", "")},
},
{
name: "MultipleFindings",
in: `
// keep-sorted-test end
// keep-sorted-test start
// keep-sorted-test start
2
1
3
// keep-sorted-test end
// keep-sorted-test start
foo
bar
baz
// keep-sorted-test end
`,
want: []*Finding{
finding(filename, 2, 2, "This instruction doesn't have matching 'keep-sorted-test start' line", ""),
finding(filename, 3, 3, "This instruction doesn't have matching 'keep-sorted-test end' line", ""),
finding(filename, 5, 7, errorUnordered, "1\n2\n3\n"),
finding(filename, 10, 12, errorUnordered, "bar\nbaz\nfoo\n"),
},
},
{
name: "ModifiedLines",
in: `
// keep-sorted-test start
2
1
3
// keep-sorted-test end
// keep-sorted-test start
foo
bar
baz
// keep-sorted-test end`,
modifiedLines: []int{3},
want: []*Finding{finding(filename, 3, 5, errorUnordered, "1\n2\n3\n")},
},
{
name: "lint=no",
in: `
// keep-sorted-test start lint=no
2
1
3
// keep-sorted-test end`,
considerLintOption: true,
want: nil,
},
} {
t.Run(tc.name, func(t *testing.T) {
initZerolog(t)
var mod []LineRange
if tc.modifiedLines != nil {
for _, l := range tc.modifiedLines {
mod = append(mod, LineRange{l, l})
}
}
got := New("keep-sorted-test", BlockOptions{}).findings(filename, strings.Split(tc.in, "\n"), mod, tc.considerLintOption)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("Findings diff (-want +got):\n%s", diff)
}
})
}
}
func TestCreatingBlocks(t *testing.T) {
for _, tc := range []struct {
name string
in string
include func(start, end int) bool
wantBlocks []block
wantIncompleteBlocks []incompleteBlock
}{
{
name: "MultipleBlocks",
in: `
foo
bar
// keep-sorted-test start
c
b
a
// keep-sorted-test end
baz
// keep-sorted-test start
1
2
3
// keep-sorted-test end
dog
cat`,
wantBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 3,
end: 7,
lines: []string{
"c",
"b",
"a",
},
},
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 9,
end: 13,
lines: []string{
"1",
"2",
"3",
},
},
},
},
{
name: "IncompleteBlocks",
in: `
// keep-sorted-test end
// keep-sorted-test start
foo
bar
// keep-sorted-test start
baz
// keep-sorted-test end
dog
`,
wantBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 5,
end: 7,
lines: []string{
"baz",
},
},
},
wantIncompleteBlocks: []incompleteBlock{
{1, endDirective},
{2, startDirective},
},
},
{
name: "FilteredBlocks",
in: `
foo
bar
// keep-sorted-test start
c
b
a
// keep-sorted-test end
baz
// keep-sorted-test start
1
2
3
// keep-sorted-test end
dog
cat`,
include: func(start, end int) bool {
return start < 4 && 4 < end
},
wantBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 3,
end: 7,
lines: []string{
"c",
"b",
"a",
},
},
},
},
{
name: "TrailingNewlines",
in: `
// keep-sorted-test start
1
2
3
// keep-sorted-test end
`,
wantBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 1,
end: 6,
lines: []string{
"",
"1",
"2",
"3",
},
},
},
},
{
name: "NestedBlocks",
in: `
// keep-sorted-test start
a
b
c
// keep-sorted-test start
d
e
f
// keep-sorted-test end
g
h
i
// keep-sorted-test end
`,
wantBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 1,
end: 13,
lines: []string{
"a",
"b",
"c",
"// keep-sorted-test start",
"d",
"e",
"f",
"// keep-sorted-test end",
"g",
"h",
"i",
},
nestedBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 5,
end: 9,
lines: []string{
"d",
"e",
"f",
},
},
},
},
},
},
{
name: "NestedBlocks_DeeplyNested",
in: `
// keep-sorted-test start
0.1
0.2
0.3
// keep-sorted-test start
1.1
1.2
1.3
// keep-sorted-test start
2.1
2.2
2.3
// keep-sorted-test start
3.1
3.2
3.3
// keep-sorted-test end // 0:1:2:3
2.4
2.5
2.6
// keep-sorted-test end // 0:1:2
// keep-sorted-test start
4.1
4.2
4.3
// keep-sorted-test end // 0:1:4
1.4
1.5
1.6
// keep-sorted-test end // 0:1
0.4
0.5
0.6
// keep-sorted-test end // 0
// keep-sorted-test start
5.1
5.2
5.3
// keep-sorted-test end // 5
`,
wantBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 1,
end: 34,
lines: []string{
"0.1",
"0.2",
"0.3",
"// keep-sorted-test start",
"1.1",
"1.2",
"1.3",
"// keep-sorted-test start",
"2.1",
"2.2",
"2.3",
"// keep-sorted-test start",
"3.1",
"3.2",
"3.3",
"// keep-sorted-test end // 0:1:2:3",
"2.4",
"2.5",
"2.6",
"// keep-sorted-test end // 0:1:2",
"// keep-sorted-test start",
"4.1",
"4.2",
"4.3",
"// keep-sorted-test end // 0:1:4",
"1.4",
"1.5",
"1.6",
"// keep-sorted-test end // 0:1",
"0.4",
"0.5",
"0.6",
},
nestedBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 5,
end: 30,
lines: []string{
"1.1",
"1.2",
"1.3",
"// keep-sorted-test start",
"2.1",
"2.2",
"2.3",
"// keep-sorted-test start",
"3.1",
"3.2",
"3.3",
"// keep-sorted-test end // 0:1:2:3",
"2.4",
"2.5",
"2.6",
"// keep-sorted-test end // 0:1:2",
"// keep-sorted-test start",
"4.1",
"4.2",
"4.3",
"// keep-sorted-test end // 0:1:4",
"1.4",
"1.5",
"1.6",
},
nestedBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 9,
end: 21,
lines: []string{
"2.1",
"2.2",
"2.3",
"// keep-sorted-test start",
"3.1",
"3.2",
"3.3",
"// keep-sorted-test end // 0:1:2:3",
"2.4",
"2.5",
"2.6",
},
nestedBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 13,
end: 17,
lines: []string{
"3.1",
"3.2",
"3.3",
},
},
},
},
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 22,
end: 26,
lines: []string{
"4.1",
"4.2",
"4.3",
},
},
},
},
},
},
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 35,
end: 39,
lines: []string{
"5.1",
"5.2",
"5.3",
},
},
},
},
{
name: "NestedBlocks_MissingEnds",
in: `
// keep-sorted-test start
0
// keep-sorted-test start
1
// keep-sorted-test start
2
// keep-sorted-test end
`,
wantBlocks: []block{
{
metadata: defaultMetadataWithCommentMarker("//"),
start: 5,
end: 7,
lines: []string{"2"},
},
},
wantIncompleteBlocks: []incompleteBlock{
{1, startDirective},
{3, startDirective},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
initZerolog(t)
if tc.include == nil {
tc.include = func(start, end int) bool { return true }
}
gotBlocks, gotIncompleteBlocks := New("keep-sorted-test", BlockOptions{}).newBlocks(strings.Split(tc.in, "\n"), 0, tc.include)
if diff := cmp.Diff(tc.wantBlocks, gotBlocks, cmp.AllowUnexported(block{}, blockMetadata{}, blockOptions{})); diff != "" {
t.Errorf("blocks diff (-want +got):\n%s", diff)
}
if diff := cmp.Diff(tc.wantIncompleteBlocks, gotIncompleteBlocks, cmp.AllowUnexported(incompleteBlock{})); diff != "" {
t.Errorf("incompleteBlocks diff (-want +got):\n%s", diff)
}
})
}
}
func TestLineSorting(t *testing.T) {
for _, tc := range []struct {
name string
opts blockOptions
in []string
want []string
wantAlreadySorted bool
}{
{
name: "NothingToSort",
in: []string{},
want: []string{},
wantAlreadySorted: true,
},
{
name: "AlreadySorted",
in: []string{
"Bar",
"Baz",
"Foo",
"Qux",
},
want: []string{
"Bar",
"Baz",
"Foo",
"Qux",
},
wantAlreadySorted: true,
},
{
name: "AlreadySorted_ExceptForDuplicate",
opts: blockOptions{
RemoveDuplicates: true,
},
in: []string{
"Bar",
"Bar",
"Foo",
},
want: []string{
"Bar",
"Foo",
},
wantAlreadySorted: false,
},
{
name: "AlreadySorted_NewlineSeparated",
opts: blockOptions{
NewlineSeparated: true,
},
in: []string{
"Bar",
"",
"Baz",
"",
"Foo",
},
want: []string{
"Bar",
"",
"Baz",
"",
"Foo",
},
wantAlreadySorted: true,
},
{
name: "AlreadySorted_ExceptForNewlineSorted",
opts: blockOptions{
NewlineSeparated: true,
},
in: []string{
"Bar",
"Baz",
"Foo",
},
want: []string{
"Bar",
"",
"Baz",
"",
"Foo",
},
wantAlreadySorted: false,
},
{
name: "SimpleSorting",
in: []string{
"Baz",
"Foo",
"Bar",
"Qux",
},
want: []string{
"Bar",
"Baz",
"Foo",
"Qux",
},
},
{
name: "CommentOnlyBlock",
opts: func() blockOptions {
opts := blockOptions{
StickyComments: true,
}
opts.setCommentMarker("//")
return opts
}(),
in: []string{
"2",
"1",
"// trailing comment",
},
want: []string{
"1",
"2",
"// trailing comment",
},
},
{
name: "Prefix",
opts: blockOptions{
PrefixOrder: []string{"INIT_", "", "FINAL_"},
},
in: []string{
// keep-sorted start prefix_order=
"DO_SOMETHING_WITH_BAR",
"DO_SOMETHING_WITH_FOO",
"FINAL_BAR",
"FINAL_FOO",
"INIT_BAR",
"INIT_FOO",
// keep-sorted end
},
want: []string{
"INIT_BAR",
"INIT_FOO",
"DO_SOMETHING_WITH_BAR",
"DO_SOMETHING_WITH_FOO",
"FINAL_BAR",
"FINAL_FOO",
},
},
{
name: "RemoveDuplicates_ByDefault",
opts: defaultOptions,
in: []string{
"foo",
"foo",
"bar",
},
want: []string{
"bar",
"foo",
},
},
{
name: "RemoveDuplicates_ConsidersComments",
opts: func() blockOptions {
opts := blockOptions{
RemoveDuplicates: true,
StickyComments: true,
}
opts.setCommentMarker("//")
return opts
}(),
in: []string{
"// comment 1",
"foo",
"// comment 2",
"foo",
"// comment 1",
"foo",
"bar",
},
want: []string{
"bar",
"// comment 1",
"foo",
"// comment 2",
"foo",
},
},
{
name: "RemoveDuplicates_IgnoresTraliningCommas",
opts: blockOptions{
RemoveDuplicates: true,
},
in: []string{
"foo,",
"bar,",
"bar",
},
want: []string{
"bar,",
"foo",
},
},
{
name: "RemoveDuplicates_IgnoresTrailingCommas_RemovesCommaIfLastElement",
opts: blockOptions{
RemoveDuplicates: true,
},
in: []string{
"foo,",
"foo,",
"bar",
},
want: []string{
"bar,",
"foo",
},
},
{
name: "RemoveDuplicates_IgnoresTrailingCommas_RemovesCommaIfOnlyElement",
opts: blockOptions{
RemoveDuplicates: true,
},
in: []string{
"foo,",
"foo",
},
want: []string{
"foo",
},
},
{
name: "RemoveDuplicates_Keep",
opts: blockOptions{
RemoveDuplicates: false,
},
in: []string{
"foo",
"foo",
"bar",
},
want: []string{
"bar",
"foo",
"foo",
},
},
{
name: "TrailingCommas",
in: []string{
"foo,",
"baz,",
"bar",
},
want: []string{
"bar,",
"baz,",
"foo",
},
},
{
name: "IgnorePrefixes",
opts: blockOptions{
IgnorePrefixes: []string{"fs.setBoolFlag", "fs.setIntFlag"},
},
in: []string{
// keep-sorted start ignore_prefixes=
`fs.setBoolFlag("paws_with_cute_toebeans", true)`,
`fs.setBoolFlag("whiskered_adorable_dog", true)`,
`fs.setIntFlag("pretty_whiskered_kitten", 6)`,
// keep-sorted end
},
want: []string{
`fs.setBoolFlag("paws_with_cute_toebeans", true)`,
`fs.setIntFlag("pretty_whiskered_kitten", 6)`,
`fs.setBoolFlag("whiskered_adorable_dog", true)`,
},
},
{
name: "CaseInsensitive",
opts: blockOptions{
CaseSensitive: false,
},
in: []string{
// keep-sorted start case=yes
"Bravo",
"Echo",
"delta",
// keep-sorted end
},
want: []string{
"Bravo",
"delta",
"Echo",
},
},
{
name: "Numeric",
opts: blockOptions{
Numeric: true,
},
in: []string{
// keep-sorted start numeric=no
"PROGRESS_100_PERCENT",
"PROGRESS_10_PERCENT",
"PROGRESS_1_PERCENT",
"PROGRESS_50_PERCENT",
"PROGRESS_5_PERCENT",
// keep-sorted end
},
want: []string{
"PROGRESS_1_PERCENT",
"PROGRESS_5_PERCENT",
"PROGRESS_10_PERCENT",
"PROGRESS_50_PERCENT",
"PROGRESS_100_PERCENT",
},
},
{
name: "MultipleTransforms",
opts: blockOptions{
IgnorePrefixes: []string{"R2D2", "C3PO", "R4"},
Numeric: true,
},
in: []string{
// keep-sorted start ignore_prefixes= numeric=no
"C3PO_ARM_L",
"C3PO_ARM_R",
"C3PO_HEAD",
"R2D2_BOLTS_10_MM",
"R2D2_BOLTS_5_MM",
"R2D2_PROJECTOR",
"R4_MOTIVATOR",
// keep-sorted end
},
want: []string{
"C3PO_ARM_L",
"C3PO_ARM_R",
"R2D2_BOLTS_5_MM",
"R2D2_BOLTS_10_MM",
"C3PO_HEAD",
"R4_MOTIVATOR",
"R2D2_PROJECTOR",
},
},
{
name: "NewlineSeparated",
opts: blockOptions{
NewlineSeparated: true,
},
in: []string{
"B",
"",
"C",
"A",
},
want: []string{
"A",
"",
"B",
"",
"C",
},
},
{
name: "NewlineSeparated_Empty",
opts: blockOptions{
NewlineSeparated: true,
},
in: []string{},
want: []string{},
wantAlreadySorted: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
initZerolog(t)
got, gotAlreadySorted := block{lines: tc.in, metadata: defaultMetadataWith(tc.opts)}.sorted()
if gotAlreadySorted != tc.wantAlreadySorted {
t.Errorf("alreadySorted mismatch: got %t want %t", gotAlreadySorted, tc.wantAlreadySorted)
}
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("sorted() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestLineGrouping(t *testing.T) {
for _, tc := range []struct {
name string
opts blockOptions
// We set the input to be the concatenation of all the lineGroups.
want []lineGroup
}{
{
name: "Simple",
want: []lineGroup{
{nil, []string{"foo"}},
{nil, []string{"bar"}},
},
},
{
name: "StickyComments",
opts: func() blockOptions {
opts := blockOptions{
StickyComments: true,
}
opts.setCommentMarker("//")
return opts
}(),
want: []lineGroup{
{
[]string{
"// comment 1",
"// comment 2",
},
[]string{
"foo",
},
},
{
[]string{
"// comment 3",
}, []string{
"bar",
},
},
},
},
{
name: "CommentOnlyGroup",
opts: func() blockOptions {
opts := blockOptions{
StickyComments: true,
}
opts.setCommentMarker("//")
return opts
}(),
want: []lineGroup{
{
[]string{
"// comment 1",
},
[]string{
"foo",
},
},
{
[]string{
"// trailing comment",
},
nil,
},
},
},
{
name: "Group",
opts: blockOptions{
Group: true,
},
want: []lineGroup{
{nil, []string{
" foo",
" bar",
}},
{nil, []string{
" baz",
}},
},
},
{
name: "Group_Prefixes",
opts: blockOptions{
Group: true,
GroupPrefixes: map[string]bool{"and": true, "with": true},
},
want: []lineGroup{
{nil, []string{
"peanut butter",
"and jelly",
}},
{nil, []string{
"spaghetti",
"with meatballs",
}},
{nil, []string{
"hamburger",
" with lettuce",
" and tomatoes",
"and cheese",
}},
{nil, []string{
"dogs and cats",
}},
},
},
{
name: "Group_UnindentedNewlines",
opts: blockOptions{
Group: true,
},
want: []lineGroup{
{nil, []string{
" foo",
"", // Since the next non-empty line has the correct indent.
" bar",
}},
{nil, []string{
"", // Next non-empty line has the wrong indent.
}},
{nil, []string{
" baz",
}},
{nil, []string{
"", // There is no next non-empty line.
}},
},
},
{
name: "Group_NestedKeepSortedBlocksWithoutAnyIndentation",
opts: func() blockOptions {
opts := blockOptions{
Group: true,
StickyComments: true,
}
opts.setCommentMarker("//")
return opts
}(),
want: []lineGroup{
{[]string{
"// def",
}, []string{
"// keep-sorted-test start",
"3",
"1",
"2",
"// keep-sorted-test end",
}},
{[]string{
"// abc",
}, []string{
"// keep-sorted-test start",
"b",
"c",
"a",
"// keep-sorted-test end",
}},
},
},
{
name: "Block_Brackets",
opts: blockOptions{
Block: true,
},
want: []lineGroup{
{nil, []string{
"foo(",
"abcd",
"efgh",
")",
}},
{nil, []string{
"bar()",
}},
{nil, []string{
"baz",
}},
},
},
{
name: "Block_Quotes",
opts: blockOptions{
Block: true,
},
want: []lineGroup{
{nil, []string{
`foo"`,
"abcd",
"efgh",
`"`,
}},
{nil, []string{
`bar""`,
}},
{nil, []string{
"baz",
}},
},
},
{
name: "Block_EscapedQuote",
opts: blockOptions{
Block: true,
},
want: []lineGroup{
{nil, []string{
`foo"`,
`\"abcd`,
`efgh\"`,
`"`,
}},
{nil, []string{
`bar""`,
}},
{nil, []string{
"baz",
}},
},
},
{
name: "Block_IgnoresQuotesWithinQuotes",
opts: blockOptions{
Block: true,
},
want: []lineGroup{
{nil, []string{
`foo"`,
`ab'cd`,
`efgh`,
`"`,
}},
{nil, []string{
"bar'`'",
}},
{nil, []string{
"baz",
}},
},
},
{
name: "Block_IgnoresBracesWithinQuotes",
opts: blockOptions{
Block: true,
},
want: []lineGroup{
{nil, []string{
`foo"`,
`ab(cd`,
`ef[gh`,
`"`,
}},
{nil, []string{
`foo"`,
`ab)cd`,
`ef]gh`,
`"`,
}},
},
},
{
name: "Block_IgnoresSpecialCharactersWithinFullLineComments",
opts: func() blockOptions {
opts := blockOptions{
Block: true,
}
opts.setCommentMarker("//")
return opts
}(),
want: []lineGroup{
{nil, []string{
"foo(",
"// ignores quotes in a comment '",
"// ignores parenthesis in a comment )",
"abcd",
")",
}},
{nil, []string{
"'string literal",
"// does not ignore quotes here '",
}},
{nil, []string{
"abcd'",
}},
},
},
{
name: "Block_IgnoresSpecialCharactersWithinTrailingComments",
opts: func() blockOptions {
opts := blockOptions{
Block: true,
}
opts.setCommentMarker("//")
return opts
}(),
want: []lineGroup{
{nil, []string{
"foo(// ignores quotes in a comment '",
"abcd // ignores parenthesis in a comment )",
")",
}},
{nil, []string{
"'string literal",
"with line break // does not ignore quotes here '",
}},
{nil, []string{
`"another string literal`,
`with line break // does not ignore quote " here`,
}},
{nil, []string{
`"abcd"`,
}},
},
},
{
name: "Block_TripleQuotes",
opts: blockOptions{
Block: true,
},
want: []lineGroup{
{nil, []string{
`"""documentation`,
"ab'cd",
"efgh",
"abcd",
`"""`}},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
initZerolog(t)
var in []string
for _, lg := range tc.want {
in = append(in, lg.comment...)
in = append(in, lg.lines...)
}
got := groupLines(in, defaultMetadataWith(tc.opts))
if diff := cmp.Diff(tc.want, got, cmp.AllowUnexported(lineGroup{})); diff != "" {
t.Errorf("groupLines mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestBlockOptions(t *testing.T) {
for _, tc := range []struct {
name string
in string
defaultOptions blockOptions
want blockOptions
wantErr string
}{
{
name: "DefaultOptions",
in: "",
defaultOptions: defaultOptions,
want: defaultOptions,
},
{
name: "CommentMarker",
in: "// keep-sorted-test start",
want: blockOptions{
commentMarker: "//",
},
},
{
name: "StickyComments",
in: "// keep-sorted-test start sticky_comments=yes",
want: blockOptions{
StickyComments: true,
StickyPrefixes: map[string]bool{"//": true},
commentMarker: "//",
},
},
{
name: "SimpleSwitch",
in: "lint=yes",
want: blockOptions{Lint: true},
},
{
name: "SkipLines",
in: "skip_lines=10",
want: blockOptions{SkipLines: 10},
},
{
name: "ErrorSkipLinesIsNegative",
in: "skip_lines=-1",
wantErr: "skip_lines has invalid value: -1",
},
{
name: "ItemList",
in: "prefix_order=a,b,c,d",
want: blockOptions{
PrefixOrder: []string{"a", "b", "c", "d"},
},
},
{
name: "ItemSet",
in: "sticky_prefixes=a,b,c,d",
want: blockOptions{
StickyPrefixes: map[string]bool{
"a": true,
"b": true,
"c": true,
"d": true,
},
},
},
{
name: "ignore_prefixes",
in: "ignore_prefixes=a,b,c,d",
want: blockOptions{
IgnorePrefixes: []string{"a", "b", "c", "d"},
},
},
{
name: "ignore_prefixes_ChecksLongestPrefixesFirst",
in: "ignore_prefixes=DoSomething(,DoSomething({",
want: blockOptions{
IgnorePrefixes: []string{"DoSomething({", "DoSomething("},
},
},
{
name: "GroupPrefixesRequiresGrouping",
in: "group_prefixes=a,b,c group=no",
wantErr: "group_prefixes may not be used with group=no",
},
{
name: "OptionInTrailingComment",
in: "# keep-sorted-test start block=yes # lint=yes",
want: blockOptions{
Block: true,
Lint: true,
commentMarker: "#",
},
},
{
name: "ErrorDoesNotStopParsing",
in: "lint=nah case=no",
defaultOptions: blockOptions{
Lint: true,
CaseSensitive: true,
},
want: blockOptions{
Lint: true, // The default value should not change.
CaseSensitive: false,
},
wantErr: `option "lint" has unknown value "nah"`,
},
} {
t.Run(tc.name, func(t *testing.T) {
initZerolog(t)
got, err := parseBlockOptions(tc.in, tc.defaultOptions)
if err != nil {
if tc.wantErr == "" {
t.Errorf("parseBlockOptions(%q) = %v", tc.in, err)
} else if !strings.Contains(err.Error(), tc.wantErr) {
t.Errorf("parseBlockOptions(%q) = %v, expected to contain %q", tc.in, err, tc.wantErr)
}
}
if diff := cmp.Diff(tc.want, got, cmp.AllowUnexported(blockOptions{})); diff != "" {
t.Errorf("parseBlockOptions(%q) mismatch (-want +got):\n%s", tc.in, diff)
}
_ = got.String() // Make sure this doesn't panic.
})
}
}
func TestBlockOptions_ClonesDefaultOptions(t *testing.T) {
defaults := blockOptions{
StickyPrefixes: map[string]bool{},
}
_, err := parseBlockOptions("sticky_prefixes=//", defaults)
if err != nil {
t.Errorf("parseBlockOptions() = _, %v", err)
}
if diff := cmp.Diff(blockOptions{}, defaults, cmp.AllowUnexported(blockOptions{}), cmpopts.EquateEmpty()); diff != "" {
t.Errorf("defaults appear to have been modified (-want +got):\n%s", diff)
}
}
func TestBlockOptions_ClonesDefaultOptions_Reflection(t *testing.T) {
defaults := blockOptions{}
defaultOpts := reflect.ValueOf(&defaults).Elem()
var s []string
for i := 0; i < defaultOpts.NumField(); i++ {
val := defaultOpts.Field(i)
switch val.Kind() {
case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String:
continue
case reflect.Slice:
val.Set(reflect.MakeSlice(val.Type(), 0, 0))
s = append(s, fmt.Sprintf("%s=a,b,c", key(defaultOpts.Type().Field(i))))
case reflect.Map:
val.Set(reflect.MakeMap(val.Type()))
s = append(s, fmt.Sprintf("%s=a,b,c", key(defaultOpts.Type().Field(i))))
default:
t.Errorf("Option %q has unhandled type: %v", key(defaultOpts.Type().Field(i)), val.Type())
}
}
_, _ = parseBlockOptions(strings.Join(s, " "), defaults)
if diff := cmp.Diff(blockOptions{}, defaults, cmp.AllowUnexported(blockOptions{}), cmpopts.EquateEmpty()); diff != "" {
t.Errorf("defaults appear to have been modified (-want +got):\n%s", diff)
}
}