kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/util/span/span_test.go (about)

     1  /*
     2   * Copyright 2018 The Kythe Authors. All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *   http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package span
    18  
    19  import (
    20  	"fmt"
    21  	"strconv"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	"kythe.io/kythe/go/test/testutil"
    26  	"kythe.io/kythe/go/util/compare"
    27  
    28  	"google.golang.org/protobuf/proto"
    29  
    30  	cpb "kythe.io/kythe/proto/common_go_proto"
    31  )
    32  
    33  func TestNormalizerPoint(t *testing.T) {
    34  	const text = `line 1
    35  line 2
    36  last line without newline`
    37  
    38  	tests := []struct{ p, expected *cpb.Point }{
    39  		{
    40  			&cpb.Point{},
    41  			&cpb.Point{ByteOffset: 0, LineNumber: 1, ColumnOffset: 0},
    42  		},
    43  		{
    44  			&cpb.Point{LineNumber: 1},
    45  			&cpb.Point{ByteOffset: 0, LineNumber: 1, ColumnOffset: 0},
    46  		},
    47  		{
    48  			&cpb.Point{ByteOffset: 1},
    49  			&cpb.Point{ByteOffset: 1, LineNumber: 1, ColumnOffset: 1},
    50  		},
    51  		{
    52  			&cpb.Point{ByteOffset: 6},
    53  			&cpb.Point{ByteOffset: 6, LineNumber: 1, ColumnOffset: 6},
    54  		},
    55  		{
    56  			&cpb.Point{LineNumber: 1, ColumnOffset: 6},
    57  			&cpb.Point{ByteOffset: 6, LineNumber: 1, ColumnOffset: 6},
    58  		},
    59  		{
    60  			&cpb.Point{ByteOffset: 7},
    61  			&cpb.Point{ByteOffset: 7, LineNumber: 2, ColumnOffset: 0},
    62  		},
    63  		{
    64  			&cpb.Point{LineNumber: 2},
    65  			&cpb.Point{ByteOffset: 7, LineNumber: 2, ColumnOffset: 0},
    66  		},
    67  		{
    68  			&cpb.Point{ByteOffset: 10},
    69  			&cpb.Point{ByteOffset: 10, LineNumber: 2, ColumnOffset: 3},
    70  		},
    71  		{
    72  			&cpb.Point{ByteOffset: 13},
    73  			&cpb.Point{ByteOffset: 13, LineNumber: 2, ColumnOffset: 6},
    74  		},
    75  		{
    76  			&cpb.Point{LineNumber: 2, ColumnOffset: 6},
    77  			&cpb.Point{ByteOffset: 13, LineNumber: 2, ColumnOffset: 6},
    78  		},
    79  		{
    80  			&cpb.Point{LineNumber: 2, ColumnOffset: 7}, // past end of column
    81  			&cpb.Point{ByteOffset: 13, LineNumber: 2, ColumnOffset: 6},
    82  		},
    83  		{
    84  			&cpb.Point{LineNumber: 3},
    85  			&cpb.Point{ByteOffset: 14, LineNumber: 3, ColumnOffset: 0},
    86  		},
    87  		{
    88  			&cpb.Point{LineNumber: 3, ColumnOffset: 5},
    89  			&cpb.Point{ByteOffset: 19, LineNumber: 3, ColumnOffset: 5},
    90  		},
    91  		{
    92  			&cpb.Point{ByteOffset: 39},
    93  			&cpb.Point{ByteOffset: 39, LineNumber: 3, ColumnOffset: 25},
    94  		},
    95  		{
    96  			&cpb.Point{ByteOffset: 40}, // past end of text
    97  			&cpb.Point{ByteOffset: 39, LineNumber: 3, ColumnOffset: 25},
    98  		},
    99  		{
   100  			&cpb.Point{LineNumber: 5, ColumnOffset: 5}, // past end of text
   101  			&cpb.Point{ByteOffset: 39, LineNumber: 3, ColumnOffset: 25},
   102  		},
   103  		{
   104  			&cpb.Point{ByteOffset: -1}, // before start of text
   105  			&cpb.Point{ByteOffset: 0, LineNumber: 1, ColumnOffset: 0},
   106  		},
   107  		{
   108  			&cpb.Point{LineNumber: -1, ColumnOffset: 5}, // before start of text
   109  			&cpb.Point{LineNumber: 1},
   110  		},
   111  	}
   112  
   113  	n := NewNormalizer([]byte(text))
   114  	for _, test := range tests {
   115  		if p := n.Point(test.p); !proto.Equal(p, test.expected) {
   116  			t.Errorf("n.Point({%v}): expected {%v}; found {%v}", test.p, test.expected, p)
   117  		}
   118  	}
   119  }
   120  
   121  func TestPatcher(t *testing.T) {
   122  	tests := []struct {
   123  		oldText, newText string
   124  
   125  		oldSpans []*span
   126  		newSpans []*span
   127  	}{{
   128  		oldText:  "this is some text",
   129  		newText:  "this is some changed text",
   130  		oldSpans: []*span{{0, 5}, {13, 17}, {13, 13}, {8, 17}},
   131  		newSpans: []*span{{0, 5}, {21, 25}, {21, 21}, nil},
   132  	}, {
   133  		oldText:  "line one\nline two\nline three\nline four\n",
   134  		newText:  "line one\nline three\nline two\nline four\n",
   135  		oldSpans: []*span{{0, 10}, {10, 14}, {16, 19}, {20, 24}, {25, 30}, {29, 38}},
   136  		newSpans: []*span{{0, 10}, {10, 14}, nil, {22, 26}, nil, {29, 38}},
   137  	}, {
   138  		oldText: "line three\n",
   139  		newText: "line one\ntwo\nthree\n",
   140  		oldSpans: []*span{
   141  			{0, 4},
   142  			{5, 5},
   143  			{5, 10},
   144  		},
   145  		newSpans: []*span{
   146  			{0, 4},
   147  			{13, 13},
   148  			{13, 18},
   149  		},
   150  	}}
   151  
   152  	for i, test := range tests {
   153  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   154  			p, err := NewPatcher([]byte(test.oldText), []byte(test.newText))
   155  			if err != nil {
   156  				t.Fatal(err)
   157  			}
   158  			if len(test.oldSpans) != len(test.newSpans) {
   159  				t.Fatalf("Invalid test: {%v}", test)
   160  			}
   161  
   162  			for i, s := range test.oldSpans {
   163  				start, end, exists := p.Patch(s.Start, s.End)
   164  
   165  				if ns := test.newSpans[i]; ns == nil && exists {
   166  					t.Errorf("Expected span not to exist in new text; received (%d, %d]", start, end)
   167  				} else if ns != nil && !exists {
   168  					t.Errorf("Expected span %v to exist in new text as %v; did not exist", s, ns)
   169  				} else if ns != nil && exists && (start != ns.Start || end != ns.End) {
   170  					t.Errorf("Expected %v; received (%d, %d]", ns, start, end)
   171  				}
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func p(bo, ln, co int32) *cpb.Point {
   178  	return &cpb.Point{ByteOffset: bo, LineNumber: ln, ColumnOffset: co}
   179  }
   180  
   181  func sp(start, end *cpb.Point) *cpb.Span { return &cpb.Span{Start: start, End: end} }
   182  
   183  func TestPatchSpan(t *testing.T) {
   184  	tests := []struct {
   185  		oldText, newText string
   186  
   187  		oldSpans []*cpb.Span
   188  		newSpans []*cpb.Span
   189  	}{{
   190  		oldText: "this is some text\nsecond line\n",
   191  		newText: "this is some text\nsecond line\n",
   192  
   193  		oldSpans: []*cpb.Span{
   194  			sp(p(0, 1, 0), p(4, 1, 4)),
   195  			sp(p(13, 1, 13), p(17, 1, 17)),
   196  		},
   197  		newSpans: []*cpb.Span{
   198  			sp(p(0, 1, 0), p(4, 1, 4)),
   199  			sp(p(13, 1, 13), p(17, 1, 17)),
   200  		},
   201  	}, {
   202  		oldText: "this is some text",
   203  		newText: "this is some changed text",
   204  
   205  		oldSpans: []*cpb.Span{
   206  			sp(p(0, 1, 0), p(4, 1, 4)),
   207  			sp(p(13, 1, 13), p(17, 1, 17)),
   208  			nil,
   209  		},
   210  		newSpans: []*cpb.Span{
   211  			sp(p(0, 1, 0), p(4, 1, 4)),
   212  			sp(p(21, 1, 21), p(25, 1, 25)),
   213  			nil,
   214  		},
   215  	}, {
   216  		oldText: "line one\nline two\nline three\nline four\n",
   217  		newText: "line one\nline three\nline two\npre line four\n",
   218  
   219  		oldSpans: []*cpb.Span{
   220  			sp(p(0, 1, 0), p(5, 1, 5)),
   221  			sp(p(10, 2, 0), p(14, 2, 4)),
   222  			sp(p(15, 2, 6), p(18, 2, 11)),
   223  			sp(p(30, 4, 0), p(34, 4, 4)),
   224  		},
   225  		newSpans: []*cpb.Span{
   226  			sp(p(0, 1, 0), p(5, 1, 5)),
   227  			sp(p(10, 2, 0), p(14, 2, 4)),
   228  			nil,
   229  			sp(p(34, 4, 4), p(38, 4, 8)),
   230  		},
   231  	}, {
   232  		oldText: "line one\nmoved\n",
   233  		newText: "line one\ninsert\nmoved\n",
   234  
   235  		oldSpans: []*cpb.Span{
   236  			sp(p(5, 1, 5), p(8, 1, 8)),
   237  			sp(p(10, 2, 0), p(15, 2, 5)),
   238  		},
   239  		newSpans: []*cpb.Span{
   240  			sp(p(5, 1, 5), p(8, 1, 8)),
   241  			sp(p(17, 3, 0), p(22, 3, 5)),
   242  		},
   243  	}, {
   244  		oldText: "line\ntwo\nthree\n",
   245  		newText: "line one\nline two\nthee\n",
   246  
   247  		oldSpans: []*cpb.Span{
   248  			sp(p(0, 1, 0), p(0, 1, 0)),
   249  			sp(p(0, 1, 0), p(4, 1, 4)),
   250  			sp(p(5, 2, 0), p(8, 2, 3)),
   251  			sp(p(9, 3, 0), p(14, 3, 5)),
   252  		},
   253  		newSpans: []*cpb.Span{
   254  			sp(p(0, 1, 0), p(0, 1, 0)),
   255  			sp(p(0, 1, 0), p(4, 1, 4)),
   256  			sp(p(14, 2, 5), p(17, 2, 8)),
   257  			nil,
   258  		},
   259  	}, {
   260  		oldText: "line three\n",
   261  		newText: "line one\ntwo\nthree\n",
   262  
   263  		oldSpans: []*cpb.Span{
   264  			sp(p(0, 1, 0), p(4, 1, 4)),
   265  			sp(p(5, 1, 5), p(5, 1, 5)),
   266  			sp(p(5, 1, 5), p(10, 1, 10)),
   267  		},
   268  		newSpans: []*cpb.Span{
   269  			sp(p(0, 1, 0), p(4, 1, 4)),
   270  			sp(p(13, 3, 0), p(13, 3, 0)),
   271  			sp(p(13, 3, 0), p(18, 3, 5)),
   272  		},
   273  	}, {
   274  		oldText: "line three\nfour\n",
   275  		newText: "line one\ntwo\nthree\nfour\n",
   276  
   277  		oldSpans: []*cpb.Span{
   278  			sp(p(0, 1, 0), p(4, 1, 4)),
   279  			sp(p(5, 1, 5), p(10, 1, 10)),
   280  			sp(p(11, 2, 0), p(15, 2, 4)),
   281  		},
   282  		newSpans: []*cpb.Span{
   283  			sp(p(0, 1, 0), p(4, 1, 4)),
   284  			sp(p(13, 3, 0), p(18, 3, 5)),
   285  			sp(p(19, 4, 0), p(23, 4, 4)),
   286  		},
   287  	}, {
   288  		oldText: "線\n動\n",
   289  		newText: "線\n入\n動\n",
   290  
   291  		oldSpans: []*cpb.Span{
   292  			sp(p(0, 1, 0), p(3, 1, 3)),
   293  			sp(p(0, 1, 0), p(7, 2, 3)),
   294  			sp(p(4, 2, 0), p(7, 2, 3)),
   295  		},
   296  		newSpans: []*cpb.Span{
   297  			sp(p(0, 1, 0), p(3, 1, 3)),
   298  			nil,
   299  			sp(p(8, 3, 0), p(11, 3, 3)),
   300  		},
   301  	}}
   302  
   303  	for i, test := range tests {
   304  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   305  			if len(test.oldSpans) == 0 || len(test.oldSpans) != len(test.newSpans) {
   306  				t.Fatalf("Invalid test: {%v}", test)
   307  			}
   308  
   309  			p, err := NewPatcher([]byte(test.oldText), []byte(test.newText))
   310  			if err != nil {
   311  				t.Fatal(err)
   312  			}
   313  
   314  			for i, s := range test.oldSpans {
   315  				t.Run(strconv.Itoa(i), func(t *testing.T) {
   316  					found, exists := p.PatchSpan(s)
   317  
   318  					if ns := test.newSpans[i]; s == nil && ns == nil {
   319  						if found != nil || !exists {
   320  							t.Errorf("Expected nil span to exist as nil span in new text; received %s %v", found, exists)
   321  						}
   322  					} else if ns == nil && exists {
   323  						t.Errorf("Expected span not to exist in new text; received %s", found)
   324  					} else if ns != nil && !exists {
   325  						t.Errorf("Expected span %s to exist in new text as %s; did not exist", s, ns)
   326  					} else if ns != nil && exists && !proto.Equal(found, ns) {
   327  						t.Errorf("(-expected; +found)\n%s", compare.ProtoDiff(ns, found))
   328  					}
   329  				})
   330  			}
   331  		})
   332  	}
   333  }
   334  
   335  func TestMarshal(t *testing.T) {
   336  	expected := &Patcher{
   337  		[]diff{
   338  			{Length: 2, Type: ins, Newlines: 1, FirstNewline: 2, LastNewline: 5},
   339  			{Length: 10, Type: del, Newlines: 5, FirstNewline: 0, LastNewline: 9},
   340  			{Length: 3, Type: eq, Newlines: 0, FirstNewline: -1, LastNewline: -1},
   341  			{Length: 5, Type: ins, Newlines: 1, FirstNewline: 2, LastNewline: 2},
   342  		},
   343  	}
   344  
   345  	rec, err := expected.Marshal()
   346  	testutil.Fatalf(t, "Marshal: %v", err)
   347  
   348  	found, err := Unmarshal(rec)
   349  	testutil.Fatalf(t, "Unmarshal: %v", err)
   350  
   351  	// Check that the unmarshalled Patcher has correct prefix sums
   352  	ignoreTypeField := cmpopts.IgnoreFields(offsetTracker{}, "Type")
   353  	oldT := offsetTracker{Type: del}
   354  	newT := offsetTracker{Type: ins}
   355  	for _, d := range found.spans {
   356  		if diff := compare.ProtoDiff(oldT, d.oldPrefix, ignoreTypeField); diff != "" {
   357  			t.Fatalf("Old prefix sum incorrect (-expected; +found)\n%s", diff)
   358  		}
   359  		if diff := compare.ProtoDiff(newT, d.newPrefix, ignoreTypeField); diff != "" {
   360  			t.Fatalf("New prefix sum incorrect (-expected; +found)\n%s", diff)
   361  		}
   362  
   363  		oldT.Update(d)
   364  		newT.Update(d)
   365  	}
   366  
   367  	if diff := compare.ProtoDiff(expected.spans, found.spans, cmpopts.IgnoreUnexported(diff{})); diff != "" {
   368  		t.Errorf("(-expected; +found)\n%s", diff)
   369  	}
   370  }
   371  
   372  func TestLargeDiff(t *testing.T) {
   373  	var before, after string
   374  	for i := 0; i < 60; i++ {
   375  		before += fmt.Sprintf("%d\n", i)
   376  		after += fmt.Sprintf("%d\n", i/2)
   377  	}
   378  
   379  	patcher, err := NewPatcher([]byte(before), []byte(after))
   380  	testutil.Fatalf(t, "NewPatcher: %v", err)
   381  
   382  	s := sp(p(26, 12, 1), p(28, 12, 3))
   383  	expected := sp(p(55, 25, 1), p(57, 25, 3))
   384  
   385  	found, exists := patcher.PatchSpan(s)
   386  	if !exists {
   387  		t.Fatal("Patched span does not exist")
   388  	}
   389  
   390  	if diff := compare.ProtoDiff(expected, found); diff != "" {
   391  		t.Errorf("(-expected; +found)\n%s", diff)
   392  	}
   393  }
   394  
   395  type span struct{ Start, End int32 }
   396  
   397  func (s span) String() string { return fmt.Sprintf("(%d, %d]", s.Start, s.End) }