// Copyright 2017 Google 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 searchset

import (
	"reflect"
	"testing"

	"github.com/google/licenseclassifier/stringclassifier/searchset/tokenizer"
)

const (
	shortPostmodernThesis = "In the works of Joyce, a predominant concept is the concept of semioticist"
	postmodernThesis      = `1. Joyce and neotextual modernist theory

In the works of Joyce, a predominant concept is the concept of semioticist
culture. The without/within distinction intrinsic to Finnegan's Wake emerges
again in Ulysses, although in a more neomodern sense.
`
)

func TestSearchSet_New(t *testing.T) {
	tests := []struct {
		description string
		text        string
		granularity int
		want        *SearchSet
	}{
		{
			description: "Empty string",
			text:        "",
			granularity: DefaultGranularity,
			want: &SearchSet{
				Tokens:         nil,
				Hashes:         make(tokenizer.Hash),
				Checksums:      nil,
				ChecksumRanges: nil,
			},
		},
		{
			description: "Small string",
			text:        "Hello world",
			granularity: 4,
			want: &SearchSet{
				Tokens: tokenizer.Tokens{
					{Text: "Hello", Offset: 0},
					{Text: "world", Offset: 6},
				},
				Hashes:         tokenizer.Hash{2346098258: tokenizer.TokenRanges{{Start: 0, End: 2}}},
				Checksums:      []uint32{2346098258},
				ChecksumRanges: tokenizer.TokenRanges{{Start: 0, End: 2}},
				nodes:          []*node{{2346098258, &tokenizer.TokenRange{Start: 0, End: 2}}},
			},
		},
	}

	for _, tt := range tests {
		if got := New(tt.text, tt.granularity); !reflect.DeepEqual(got, tt.want) {
			t.Errorf("New(%q) = %+v, want %+v", tt.description, got, tt.want)
		}
	}
}

func TestSearchSet_NodeConstruction(t *testing.T) {
	s := New(shortPostmodernThesis, DefaultGranularity)
	want := []string{
		"[0:3]", "[1:4]", "[2:5]", "[3:6]", "[4:7]", "[5:8]", "[6:9]",
		"[7:10]", "[8:11]", "[9:12]", "[10:13]", "[11:14]",
	}

	if len(s.nodes) != len(want) {
		t.Errorf("Number of nodes %v, want %v", len(s.nodes), len(want))
		return
	}

	for i := 0; i < len(s.nodes); i++ {
		if got := s.nodes[i].String(); got != want[i] {
			t.Errorf("Nodes = got:\n%s\nwant:\n%s", got, want[i])
		}
	}
}

func TestSearchSet_CoalesceTokenRanges(t *testing.T) {
	tests := []struct {
		description string
		mr          MatchRanges
		want        MatchRanges
	}{
		{
			description: "Non-overlapping Ranges",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 27, TargetStart: 0, TargetEnd: 27},
				{SrcStart: 37, SrcEnd: 927, TargetStart: 37, TargetEnd: 927},
			},
			want: MatchRanges{
				{SrcStart: 0, SrcEnd: 27, TargetStart: 0, TargetEnd: 27},
				{SrcStart: 37, SrcEnd: 927, TargetStart: 37, TargetEnd: 927},
			},
		},
		{
			description: "Identical Ranges",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37},
				{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37},
			},
			want: MatchRanges{{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37}},
		},
		{
			description: "Sequential Ranges",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37},
				{SrcStart: 37, SrcEnd: 927, TargetStart: 37, TargetEnd: 927},
			},
			want: MatchRanges{{SrcStart: 0, SrcEnd: 927, TargetStart: 0, TargetEnd: 927}},
		},
		{
			description: "Overlapping Ranges - Same Start",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37},
				{SrcStart: 0, SrcEnd: 927, TargetStart: 0, TargetEnd: 927},
			},
			want: MatchRanges{{SrcStart: 0, SrcEnd: 927, TargetStart: 0, TargetEnd: 927}},
		},
		{
			description: "Overlapping Ranges - Different Start",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37},
				{SrcStart: 27, SrcEnd: 927, TargetStart: 27, TargetEnd: 927},
			},
			want: MatchRanges{{SrcStart: 0, SrcEnd: 927, TargetStart: 0, TargetEnd: 927}},
		},
		{
			description: "Overlapping Ranges - Same End",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37},
				{SrcStart: 27, SrcEnd: 37, TargetStart: 27, TargetEnd: 37},
			},
			want: MatchRanges{{SrcStart: 0, SrcEnd: 37, TargetStart: 0, TargetEnd: 37}},
		},
		{
			description: "Completely Overlapping Ranges",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 42, TargetStart: 0, TargetEnd: 42},
				{SrcStart: 27, SrcEnd: 37, TargetStart: 27, TargetEnd: 37},
			},
			want: MatchRanges{{SrcStart: 0, SrcEnd: 42, TargetStart: 0, TargetEnd: 42}},
		},
	}

	for _, tt := range tests {
		got := coalesceMatchRanges(tt.mr)
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("CoalesceTokenRanges(%q) = %+v, want %+v", tt.description, got, tt.want)
		}
	}
}

func TestSearchSet_FindPotentialMatches(t *testing.T) {
	known := New(postmodernThesis, DefaultGranularity)

	size := len(postmodernThesis)
	modified := "hello world "
	modified += postmodernThesis[:size/3] + " hello world "
	modified += postmodernThesis[size/3 : 2*size/3-4]
	modified += postmodernThesis[2*size/3+7:]
	unknown := New(modified, DefaultGranularity)

	want := []MatchRanges{{
		{SrcStart: 0, SrcEnd: 15, TargetStart: 2, TargetEnd: 17},
		{SrcStart: 16, SrcEnd: 28, TargetStart: 21, TargetEnd: 33},
		{SrcStart: 31, SrcEnd: 46, TargetStart: 34, TargetEnd: 49},
	}}

	got := FindPotentialMatches(known, unknown)
	if len(got) != len(want) {
		t.Errorf("Number of matches %v, want %v", len(got), len(want))
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Offsets = %+v, want %+v", got, want)
	}

	known = New(`again in Ulysses, although in a more neomodern sense.
culture. The without/within distinction intrinsic to Finnegan's Wake emerges
`, DefaultGranularity)

	want = []MatchRanges{
		{
			{SrcStart: 11, SrcEnd: 18, TargetStart: 26, TargetEnd: 33},
			{SrcStart: 21, SrcEnd: 25, TargetStart: 34, TargetEnd: 38},
		},
		{{SrcStart: 0, SrcEnd: 11, TargetStart: 38, TargetEnd: 49}},
	}

	got = FindPotentialMatches(known, unknown)
	if len(got) != len(want) {
		t.Errorf("Number of matches %v, want %v", len(got), len(want))
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Offsets = %+v, want %+v", got, want)
	}
}

func TestSearchSet_GetMatchedRanges(t *testing.T) {
	const (
		source = "a b c d e f g c d e h i j"
		target = "a b c _ _ c d e _ f g h _ c d  e _ h i j"
	)

	src := New(source, DefaultGranularity)
	tar := New(target, DefaultGranularity)

	want := []MatchRanges{
		{
			{SrcStart: 0, SrcEnd: 3, TargetStart: 0, TargetEnd: 3},
			{SrcStart: 2, SrcEnd: 5, TargetStart: 5, TargetEnd: 8},
			{SrcStart: 7, SrcEnd: 10, TargetStart: 13, TargetEnd: 16},
			{SrcStart: 10, SrcEnd: 13, TargetStart: 17, TargetEnd: 20},
		},
	}

	got := getMatchedRanges(src, tar)
	if len(got) != len(want) {
		t.Errorf("Number of matches %v, want %v", len(got), len(want))
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Match Ranges = %+v, want %+v", got, want)
	}
}

func TestSearchSet_TargetMatchedRanges(t *testing.T) {
	const (
		source = "a b c d e f g c d e h i j"
		target = "a b c d e _ _ c d e _ f g h _ c d  e _ h i j"
	)

	src := New(source, DefaultGranularity)
	tar := New(target, DefaultGranularity)

	want := MatchRanges{
		{SrcStart: 0, SrcEnd: 3, TargetStart: 0, TargetEnd: 3},
		{SrcStart: 1, SrcEnd: 4, TargetStart: 1, TargetEnd: 4},
		{SrcStart: 2, SrcEnd: 5, TargetStart: 2, TargetEnd: 5},
		{SrcStart: 7, SrcEnd: 10, TargetStart: 2, TargetEnd: 5},
		{SrcStart: 2, SrcEnd: 5, TargetStart: 7, TargetEnd: 10},
		{SrcStart: 7, SrcEnd: 10, TargetStart: 7, TargetEnd: 10},
		{SrcStart: 2, SrcEnd: 5, TargetStart: 15, TargetEnd: 18},
		{SrcStart: 7, SrcEnd: 10, TargetStart: 15, TargetEnd: 18},
		{SrcStart: 10, SrcEnd: 13, TargetStart: 19, TargetEnd: 22},
	}

	got := targetMatchedRanges(src, tar)
	if len(got) != len(want) {
		t.Errorf("Number of matches %v, want %v", len(got), len(want))
	}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("Match Range = %+v, want %+v", got, want)
	}
}

func TestSearchSet_UntangleSourceRanges(t *testing.T) {
	tests := []struct {
		description string
		mr          MatchRanges
		want        MatchRanges
	}{
		{
			description: "Single Match - In Order",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
			},
			want: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
			},
		},
		{
			description: "Single Match - Out of Order",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 24, TargetEnd: 19},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 24, TargetEnd: 19},
				{SrcStart: 23, SrcEnd: 29, TargetStart: 30, TargetEnd: 37},
			},
			want: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 24, TargetEnd: 19},
				{SrcStart: 23, SrcEnd: 29, TargetStart: 30, TargetEnd: 37},
			},
		},
		{
			description: "Multiple Match - In Order",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
				{SrcStart: 0, SrcEnd: 3, TargetStart: 110, TargetEnd: 113},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 114, TargetEnd: 119},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 115, TargetEnd: 120},
			},
			want: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
				{SrcStart: 0, SrcEnd: 3, TargetStart: 110, TargetEnd: 113},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 114, TargetEnd: 119},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 115, TargetEnd: 120},
			},
		},
		{
			description: "Multiple Match - Out of Order",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 24, TargetEnd: 19},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 24, TargetEnd: 19},
				{SrcStart: 23, SrcEnd: 29, TargetStart: 30, TargetEnd: 37},
				{SrcStart: 0, SrcEnd: 3, TargetStart: 110, TargetEnd: 113},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 114, TargetEnd: 119},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 114, TargetEnd: 119},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 115, TargetEnd: 120},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 124, TargetEnd: 119},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 124, TargetEnd: 119},
				{SrcStart: 23, SrcEnd: 29, TargetStart: 130, TargetEnd: 137},
			},
			want: MatchRanges{
				{SrcStart: 0, SrcEnd: 3, TargetStart: 10, TargetEnd: 13},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 14, TargetEnd: 19},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 15, TargetEnd: 20},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 24, TargetEnd: 19},
				{SrcStart: 23, SrcEnd: 29, TargetStart: 30, TargetEnd: 37},
				{SrcStart: 0, SrcEnd: 3, TargetStart: 110, TargetEnd: 113},
				{SrcStart: 5, SrcEnd: 10, TargetStart: 114, TargetEnd: 119},
				{SrcStart: 6, SrcEnd: 11, TargetStart: 115, TargetEnd: 120},
				{SrcStart: 15, SrcEnd: 20, TargetStart: 124, TargetEnd: 119},
				{SrcStart: 23, SrcEnd: 29, TargetStart: 130, TargetEnd: 137},
			},
		},
	}

	for _, tt := range tests {
		got := untangleSourceRanges(tt.mr)
		if len(got) != len(tt.want) {
			t.Errorf("Number of matches %v, want %v", len(got), len(tt.want))
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("UntangleSourceRanges(%q) = %v, want %v", tt.description, got, tt.want)
		}
	}
}

func TestSearchSet_SplitRanges(t *testing.T) {
	tests := []struct {
		description string
		mr          MatchRanges
		want        []MatchRanges
	}{
		{
			description: "Single Match Range",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
				{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
			},
		},
		{
			description: "Two Match Ranges",
			mr: MatchRanges{
				{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
				{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				{SrcStart: 3, SrcEnd: 10, TargetStart: 108, TargetEnd: 115},
				{SrcStart: 23, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 108, TargetEnd: 115},
					{SrcStart: 23, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
			},
		},
	}

	for _, tt := range tests {
		got := splitRanges(tt.mr)
		if len(got) != len(tt.want) {
			t.Errorf("Number of matches %v, want %v", len(got), len(tt.want))
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("SplitRanges(%q) = %v, want %v", tt.description, got, tt.want)
		}
	}
}

func TestSearchSet_MergeConsecutiveRanges(t *testing.T) {
	tests := []struct {
		description string
		mr          []MatchRanges
		want        []MatchRanges
	}{
		{
			description: "No Overlap",
			mr: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 108, TargetEnd: 115},
					{SrcStart: 23, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 108, TargetEnd: 115},
					{SrcStart: 23, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
			},
		},
		{
			description: "Consecutive Ranges No Overlap",
			mr: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 35, TargetEnd: 41},
					{SrcStart: 23, SrcEnd: 30, TargetStart: 125, TargetEnd: 135},
				},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 35, TargetEnd: 41},
					{SrcStart: 23, SrcEnd: 30, TargetStart: 125, TargetEnd: 135},
				},
			},
		},
		{
			description: "Consecutive Ranges with First Element Overlap",
			mr: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 34, TargetEnd: 41},
					{SrcStart: 33, SrcEnd: 40, TargetStart: 35, TargetEnd: 42},
				},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 36, TargetStart: 25, TargetEnd: 41},
					{SrcStart: 33, SrcEnd: 40, TargetStart: 35, TargetEnd: 42},
				},
			},
		},
		{
			description: "Consecutive Ranges with Overlap",
			mr: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 34, TargetEnd: 41},
					{SrcStart: 33, SrcEnd: 40, TargetStart: 45, TargetEnd: 52},
				},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 36, TargetStart: 25, TargetEnd: 41},
					{SrcStart: 33, SrcEnd: 40, TargetStart: 45, TargetEnd: 52},
				},
			},
		},
		{
			description: "Consecutive Ranges with Previous Deep Overlap",
			mr: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
					{SrcStart: 120, SrcEnd: 130, TargetStart: 37, TargetEnd: 47},
					{SrcStart: 122, SrcEnd: 132, TargetStart: 39, TargetEnd: 49},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 34, TargetEnd: 41},
					{SrcStart: 33, SrcEnd: 40, TargetStart: 45, TargetEnd: 52},
				},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 36, TargetStart: 25, TargetEnd: 41},
					{SrcStart: 33, SrcEnd: 40, TargetStart: 45, TargetEnd: 52},
				},
			},
		},
		{
			description: "Consecutive Ranges with Deep Overlap",
			mr: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
					{SrcStart: 24, SrcEnd: 34, TargetStart: 29, TargetEnd: 39},
					{SrcStart: 120, SrcEnd: 130, TargetStart: 37, TargetEnd: 47},
					{SrcStart: 122, SrcEnd: 132, TargetStart: 39, TargetEnd: 49},
				},
				{
					{SrcStart: 3, SrcEnd: 10, TargetStart: 26, TargetEnd: 33},
					{SrcStart: 5, SrcEnd: 12, TargetStart: 28, TargetEnd: 35},
					{SrcStart: 25, SrcEnd: 35, TargetStart: 31, TargetEnd: 41},
				},
			},
			want: []MatchRanges{
				{
					{SrcStart: 0, SrcEnd: 10, TargetStart: 5, TargetEnd: 15},
					{SrcStart: 20, SrcEnd: 30, TargetStart: 25, TargetEnd: 35},
					{SrcStart: 24, SrcEnd: 34, TargetStart: 29, TargetEnd: 39},
					{SrcStart: 25, SrcEnd: 35, TargetStart: 31, TargetEnd: 41},
				},
			},
		},
	}

	for _, tt := range tests {
		got := mergeConsecutiveRanges(tt.mr)
		if len(got) != len(tt.want) {
			t.Errorf("Number of matches %v, want %v", len(got), len(tt.want))
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Errorf("SplitRanges(%q) = %v, want %v", tt.description, got, tt.want)
		}
	}
}
