github.com/mistwind/reviewdog@v0.0.0-20230322024206-9cfa11856d58/diff/parse_test.go (about)

     1  package diff
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  )
    13  
    14  //go:generate go run testdata/gen.go
    15  
    16  const longLine = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Convallis tellus id interdum velit laoreet. Porttitor rhoncus dolor purus non enim praesent. Phasellus egestas tellus rutrum tellus pellentesque. Fringilla urna porttitor rhoncus dolor purus non enim praesent. Proin fermentum leo vel orci porta non pulvinar neque laoreet. Imperdiet nulla malesuada pellentesque elit eget gravida cum. Sit amet nulla facilities morbi tempus iaculis urna id volutpat. Et molestie ac feugiat sed lectus vestibulum mattis. Tortor at auctor urna nunc id cursus metus. Nisl tincidunt eget nullam non nisi est. Tortor at auctor urna nunc id cursus metus.  Ornare massa eget egestas purus viverra accumsan. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Platea dictumst vestibulum rhoncus est pellentesque elit. Auctor neque vitae tempus quam pellentesque nec. Vivamus arcu felis bibendum ut tristique et egestas quis. At in tellus integer feugiat scelerisque varius morbi. Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis natoque. Ac turpis egestas integer eget aliquet nibh praesent tristique. Senectus et netus et malesuada fames ac turpis egestas. Odio eu feugiat pretium nibh ipsum consequat nisl vel. Elit eget gravida cum sociis natoque penatibus et.  Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Tincidunt arcu non sodales neque sodales ut etiam sit. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate. Urna cursus eget nunc scelerisque viverra mauris in aliquam. Fames ac turpis egestas sed. Vivamus arcu felis bibendum ut tristique et egestas quis ipsum. Turpis tincidunt id aliquet risus feugiat in ante metus dictum. Leo a diam sollicitudin tempor id eu. Consectetur adipiscing elit ut aliquam. Facilities nullam vehicula ipsum a arcu. Dignissim suspendisse in est ante in nibh mauris cursus. Faucibus interdum posuere lorem ipsum dolor sit amet consectetur. Neque convallis a cras semper. Pellentesque habitant morbi tristique senectus. Arcu non sodales neque sodales ut etiam. Pretium quam vulputate dignissim suspendisse in. Id velit ut tortor pretium viverra suspendisse potenti. Venenatis urna cursus eget nunc scelerisque. Vestibulum lorem sed risus ultricies tristique. Faucibus purus in massa tempor.  Tellus elementum sagittis vitae et. Ut tortor pretium viverra suspendisse potenti nullam ac. Non arcu risus quis varius. Magna ac placerat vestibulum lectus mauris ultrices eros in. Venenatis lectus magna fringilla urna porttitor. Ultrices gravida dictum fusce ut. Hac habitasse platea dictumst quisque sagittis purus sit. Dictum at tempor commodo ullamcorper a lacus. Mi bibendum neque egestas congue quisque. Lobortis elementum nibh tellus molestie nunc non. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique.  Pulvinar sapien et ligula ullamcorper malesuada proin libero nunc. Vel elit scelerisque mauris pellentesque pulvinar pellentesque. Eget nulla facilities etiam dignissim diam quis enim. Convallis posuere morbi leo urna molestie. Pellentesque adipiscing commodo elit at imperdiet dui. Mattis pellentesque id nibh tortor id aliquet. Praesent semper feugiat nibh sed pulvinar proin gravida hendrerit lectus. Enim lobortis scelerisque fermentum dui faucibus in ornare quam. Maecenas pharetra convallis posuere morbi leo. Dolor morbi non arcu risus quis. Sit amet purus gravida quis. Quis varius quam quisque id diam vel quam elementum. Tortor condimentum lacinia quis vel eros donec. Gravida neque convallis a cras semper auctor neque vitae tempus. Odio eu feugiat pretium nibh ipsum consequat nisl vel pretium. Urna et pharetra pharetra massa massa ultricies mi. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Convallis tellus id interdum velit laoreet. Porttitor rhoncus dolor purus non enim praesent. Phasellus egestas tellus rutrum tellus pellentesque. Fringilla urna porttitor rhoncus dolor purus non enim praesent. Proin fermentum leo vel orci porta non pulvinar neque laoreet. Imperdiet nulla malesuada pellentesque elit eget gravida cum. Sit amet nulla facilities morbi tempus iaculis urna id volutpat. Et molestie ac feugiat sed lectus vestibulum mattis. Tortor at auctor urna nunc id cursus metus. Nisl tincidunt eget nullam non nisi est. Tortor at auctor urna nunc id cursus metus.  Ornare massa eget egestas purus viverra accumsan. Venenatis urna cursus eget nunc scelerisque viverra mauris in. Platea dictumst vestibulum rhoncus est pellentesque elit. Auctor neque vitae tempus quam pellentesque nec. Vivamus arcu felis bibendum ut tristique et egestas quis. At in tellus integer feugiat scelerisque varius morbi. Imperdiet nulla malesuada pellentesque elit eget gravida cum sociis natoque. Ac turpis egestas integer eget aliquet nibh praesent tristique. Senectus et netus et malesuada fames ac turpis egestas. Odio eu feugiat pretium nibh ipsum consequat nisl vel. Elit eget gravida cum sociis natoque penatibus et.  Curabitur gravida arcu ac tortor dignissim convallis aenean et tortor. Tincidunt arcu non sodales neque sodales ut etiam sit. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate. Urna cursus eget nunc scelerisque viverra mauris in aliquam. Fames ac turpis egestas sed. Vivamus arcu felis bibendum ut tristique et egestas quis ipsum. Turpis tincidunt id aliquet risus feugiat in ante metus dictum. Leo a diam sollicitudin tempor id eu. Consectetur adipiscing elit ut aliquam. Facilities nullam vehicula ipsum a arcu. Dignissim suspendisse in est ante in nibh mauris cursus. Faucibus interdum posuere lorem ipsum dolor sit amet consectetur. Neque convallis a cras semper. Pellentesque habitant morbi tristique senectus. Arcu non sodales neque sodales ut etiam. Pretium quam vulputate dignissim suspendisse in. Id velit ut tortor pretium viverra suspendisse potenti. Venenatis urna cursus eget nunc scelerisque. Vestibulum lorem sed risus ultricies tristique. Faucibus purus in massa tempor.  Tellus elementum sagittis vitae et. Ut tortor pretium viverra suspendisse potenti nullam ac. Non arcu risus quis varius. Magna ac placerat vestibulum lectus mauris ultrices eros in. Venenatis lectus magna fringilla urna porttitor. Ultrices gravida dictum fusce ut. Hac habitasse platea dictumst quisque sagittis purus sit. Dictum at tempor commodo ullamcorper a lacus. Mi bibendum neque egestas congue quisque. Lobortis elementum nibh tellus molestie nunc non. Dolor sit amet consectetur adipiscing elit pellentesque habitant morbi tristique.  Pulvinar sapien et ligula ullamcorper malesuada proin libero nunc. Vel elit scelerisque mauris pellentesque pulvinar pellentesque. Eget nulla facilities etiam dignissim diam quis enim. Convallis posuere morbi leo urna molestie. Pellentesque adipiscing commodo elit at imperdiet dui. Mattis pellentesque id nibh tortor id aliquet. Praesent semper feugiat nibh sed pulvinar proin gravida hendrerit lectus. Enim lobortis scelerisque fermentum dui faucibus in ornare quam. Maecenas pharetra convallis posuere morbi leo. Dolor morbi non arcu risus quis. Sit amet purus gravida quis. Quis varius quam quisque id diam vel quam elementum. Tortor condimentum lacinia quis vel eros donec. Gravida neque convallis a cras semper auctor neque vitae tempus. Odio eu feugiat pretium nibh ipsum consequat nisl vel pretium. Urna et pharetra pharetra massa massa ultricies mi.`
    17  
    18  func TestParseMultiFile(t *testing.T) {
    19  	files, err := filepath.Glob("testdata/*.diff")
    20  	if err != nil {
    21  		t.Fatal(err)
    22  	}
    23  	for _, fname := range files {
    24  		t.Log(fname)
    25  		f, err := os.Open(fname)
    26  		if err != nil {
    27  			t.Fatal(err)
    28  		}
    29  		difffiles, err := ParseMultiFile(f)
    30  		if err != nil {
    31  			t.Errorf("%v: %v", fname, err)
    32  		}
    33  
    34  		wantfile, err := os.Open(fname + ".json")
    35  		if err != nil {
    36  			t.Fatal(err)
    37  		}
    38  		dec := json.NewDecoder(wantfile)
    39  		var want []*FileDiff
    40  		if err := dec.Decode(&want); err != nil {
    41  			t.Fatal(err)
    42  		}
    43  		if !reflect.DeepEqual(difffiles, want) {
    44  			l := len(difffiles)
    45  			if len(want) > l {
    46  				l = len(want)
    47  			}
    48  			for i := 0; i < l; i++ {
    49  				var a, b *FileDiff
    50  				if i < len(want) {
    51  					a = want[i]
    52  				}
    53  				if i < len(difffiles) {
    54  					b = difffiles[i]
    55  				}
    56  				t.Errorf("want %#v, got %#v", a, b)
    57  			}
    58  		}
    59  
    60  		wantfile.Close()
    61  		f.Close()
    62  	}
    63  }
    64  
    65  func TestParseMultiFile_sample(t *testing.T) {
    66  	content := `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
    67  +++ sample.new.txt	2016-10-13 05:15:26.839245048 +0900
    68  @@ -1,3 +1,4 @@
    69   unchanged, contextual line
    70  -deleted line
    71  +added line
    72  +added line
    73   unchanged, contextual line
    74  --- nonewline.old.txt	2016-10-13 15:34:14.931778318 +0900
    75  +++ nonewline.new.txt	2016-10-13 15:34:14.868444672 +0900
    76  @@ -1,4 +1,4 @@
    77   " vim: nofixeol noendofline
    78   No newline at end of both the old and new file
    79  -a
    80  -a
    81  \ No newline at end of file
    82  +b
    83  +b
    84  \ No newline at end of file
    85  `
    86  
    87  	got, err := ParseMultiFile(strings.NewReader(content))
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	want := []*FileDiff{
    92  		{
    93  			PathOld: "sample.old.txt",
    94  			PathNew: "sample.new.txt",
    95  			TimeOld: "2016-10-13 05:09:35.820791185 +0900",
    96  			TimeNew: "2016-10-13 05:15:26.839245048 +0900",
    97  			Hunks: []*Hunk{
    98  				{
    99  					StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
   100  					Lines: []*Line{
   101  						{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   102  						{Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0},
   103  						{Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2},
   104  						{Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3},
   105  						{Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4},
   106  					},
   107  				},
   108  			},
   109  		},
   110  		{
   111  			PathOld: "nonewline.old.txt",
   112  			PathNew: "nonewline.new.txt",
   113  			TimeOld: "2016-10-13 15:34:14.931778318 +0900",
   114  			TimeNew: "2016-10-13 15:34:14.868444672 +0900",
   115  			Hunks: []*Hunk{
   116  				{
   117  					StartLineOld: 1, LineLengthOld: 4, StartLineNew: 1, LineLengthNew: 4,
   118  					Lines: []*Line{
   119  						{Type: 0, Content: "\" vim: nofixeol noendofline", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   120  						{Type: 0, Content: "No newline at end of both the old and new file", LnumDiff: 2, LnumOld: 2, LnumNew: 2},
   121  						{Type: 2, Content: "a", LnumDiff: 3, LnumOld: 3, LnumNew: 0},
   122  						{Type: 2, Content: "a", LnumDiff: 4, LnumOld: 4, LnumNew: 0},
   123  						{Type: 1, Content: "b", LnumDiff: 5, LnumOld: 0, LnumNew: 3},
   124  						{Type: 1, Content: "b", LnumDiff: 6, LnumOld: 0, LnumNew: 4},
   125  					},
   126  				},
   127  			},
   128  		},
   129  	}
   130  	if !reflect.DeepEqual(got, want) {
   131  		t.Errorf("error. in:\n%v", content)
   132  		for _, fd := range got {
   133  			t.Logf("FileDiff: %#v\n", fd)
   134  			for _, h := range fd.Hunks {
   135  				t.Logf("  Hunk%#v\n", h)
   136  				for _, l := range h.Lines {
   137  					t.Logf("    Line%#v\n", l)
   138  				}
   139  			}
   140  		}
   141  	}
   142  }
   143  
   144  func TestFileParser_Parse(t *testing.T) {
   145  	tests := []struct {
   146  		in   string
   147  		want *FileDiff
   148  	}{
   149  		{
   150  			in:   "",
   151  			want: nil,
   152  		},
   153  		{
   154  			in: `diff --git a/empty.txt b/empty.txtq
   155  deleted file mode 100644
   156  index e69de29..0000000
   157  `,
   158  			want: &FileDiff{
   159  				Extended: []string{
   160  					"diff --git a/empty.txt b/empty.txtq",
   161  					"deleted file mode 100644",
   162  					"index e69de29..0000000",
   163  				},
   164  			},
   165  		},
   166  		{
   167  			in: `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
   168  +++ sample.new.txt	2016-10-13 05:15:26.839245048 +0900
   169  @@ -1,3 +1,4 @@
   170   unchanged, contextual line
   171  -deleted line
   172  +added line
   173  +added line
   174   unchanged, contextual line
   175  `,
   176  			want: &FileDiff{
   177  				PathOld: "sample.old.txt",
   178  				PathNew: "sample.new.txt",
   179  				TimeOld: "2016-10-13 05:09:35.820791185 +0900",
   180  				TimeNew: "2016-10-13 05:15:26.839245048 +0900",
   181  				Hunks: []*Hunk{
   182  					{
   183  						StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
   184  						Lines: []*Line{
   185  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   186  							{Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0},
   187  							{Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2},
   188  							{Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3},
   189  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4},
   190  						},
   191  					},
   192  				},
   193  			},
   194  		},
   195  		{
   196  			in: `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
   197  +++ sample.new.txt	2016-10-13 05:15:26.839245048 +0900
   198  @@ -1,1 +1,1 @@
   199   unchanged, contextual line
   200  @@ -2,1 +2,1 @@
   201   unchanged, contextual line
   202  `,
   203  			want: &FileDiff{
   204  				PathOld: "sample.old.txt",
   205  				PathNew: "sample.new.txt",
   206  				TimeOld: "2016-10-13 05:09:35.820791185 +0900",
   207  				TimeNew: "2016-10-13 05:15:26.839245048 +0900",
   208  				Hunks: []*Hunk{
   209  					{
   210  						StartLineOld: 1, LineLengthOld: 1, StartLineNew: 1, LineLengthNew: 1,
   211  						Lines: []*Line{
   212  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   213  						},
   214  					},
   215  					{
   216  						StartLineOld: 2, LineLengthOld: 1, StartLineNew: 2, LineLengthNew: 1,
   217  						Lines: []*Line{
   218  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 3, LnumOld: 2, LnumNew: 2},
   219  						},
   220  					},
   221  				},
   222  			},
   223  		},
   224  	}
   225  	for _, tt := range tests {
   226  		p := &fileParser{r: bufio.NewReader(strings.NewReader(tt.in))}
   227  		got, err := p.Parse()
   228  		if err != nil {
   229  			t.Errorf("got error %v for in:\n %v", err, tt.in)
   230  		}
   231  		if !reflect.DeepEqual(got, tt.want) {
   232  			t.Errorf("fileParser.Parse() = %#v, want %#v\nin: %v", got, tt.want, tt.in)
   233  			t.Log("got:")
   234  			for _, h := range got.Hunks {
   235  				for _, l := range h.Lines {
   236  					t.Logf("%#v", l)
   237  				}
   238  			}
   239  			t.Log("want:")
   240  			for _, h := range tt.want.Hunks {
   241  				for _, l := range h.Lines {
   242  					t.Logf("%#v", l)
   243  				}
   244  			}
   245  		}
   246  	}
   247  }
   248  
   249  func TestParseFileHeader(t *testing.T) {
   250  	tests := []struct {
   251  		in        string
   252  		filename  string
   253  		timestamp string
   254  	}{
   255  		{
   256  			in: "--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900",
   257  			filename:  "sample.old.txt",
   258  			timestamp: "2016-10-13 05:09:35.820791185 +0900",
   259  		},
   260  		{
   261  			in:        "+++ sample.old.txt",
   262  			filename:  "sample.old.txt",
   263  			timestamp: "",
   264  		},
   265  	}
   266  	for _, tt := range tests {
   267  		gotf, gott := parseFileHeader(tt.in)
   268  		if gotf != tt.filename || gott != tt.timestamp {
   269  			t.Errorf("parseFileHeader(%v) = (%v, %v), want (%v, %v)", tt.in, gotf, gott, tt.filename, tt.timestamp)
   270  		}
   271  	}
   272  }
   273  
   274  func TestParseExtendedHeader(t *testing.T) {
   275  	tests := []struct {
   276  		in   string
   277  		want []string
   278  	}{
   279  		{
   280  			in: `diff --git a/sample.txt b/sample.txt
   281  index a949a96..769bdae 100644
   282  --- a/sample.old.txt
   283  +++ b/sample.new.txt
   284  @@ -1,3 +1,4 @@
   285  `,
   286  			want: []string{"diff --git a/sample.txt b/sample.txt", "index a949a96..769bdae 100644"},
   287  		},
   288  		{
   289  			in: `diff --git a/sample.txt b/sample.txt
   290  deleted file mode 100644
   291  index e69de29..0000000
   292  `,
   293  			want: []string{"diff --git a/sample.txt b/sample.txt", "deleted file mode 100644", "index e69de29..0000000"},
   294  		},
   295  		{
   296  			in: `diff --git a/sample.txt b/sample.txt
   297  new file mode 100644
   298  index 0000000..e69de29
   299  diff --git a/sample2.txt b/sample2.txt
   300  new file mode 100644
   301  index 0000000..ee946eb
   302  `,
   303  			want: []string{"diff --git a/sample.txt b/sample.txt", "new file mode 100644", "index 0000000..e69de29"},
   304  		},
   305  		{
   306  			in: `--- a/sample.old.txt
   307  +++ b/sample.new.txt
   308  @@ -1,3 +1,4 @@
   309  `,
   310  			want: nil,
   311  		},
   312  	}
   313  	for _, tt := range tests {
   314  		got := parseExtendedHeader(bufio.NewReader(strings.NewReader(tt.in)))
   315  		if !reflect.DeepEqual(got, tt.want) {
   316  			t.Errorf("in:\n%v\ngot:\n%v\nwant:\n%v", tt.in, strings.Join(got, "\n"), strings.Join(tt.want, "\n"))
   317  		}
   318  	}
   319  }
   320  
   321  func TestHunkParser_Parse(t *testing.T) {
   322  	tests := []struct {
   323  		in       string
   324  		lnumdiff int
   325  		want     *Hunk
   326  	}{
   327  		{
   328  			in: `@@ -1,3 +1,4 @@ optional section heading
   329   unchanged, contextual line
   330  -deleted line
   331  +added line
   332  +added line
   333   unchanged, contextual line
   334  `,
   335  			want: &Hunk{
   336  				StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
   337  				Section: "optional section heading",
   338  				Lines: []*Line{
   339  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   340  					{Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0},
   341  					{Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2},
   342  					{Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3},
   343  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4},
   344  				},
   345  			},
   346  		},
   347  		{
   348  			in: `@@ -1,3 +1,4 @@
   349   unchanged, contextual line
   350  -deleted line
   351  +added line
   352  +added line
   353   unchanged, contextual line
   354  @@ -1,3 +1,4 @@
   355  `,
   356  			lnumdiff: 14,
   357  			want: &Hunk{
   358  				StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
   359  				Section: "",
   360  				Lines: []*Line{
   361  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 15, LnumOld: 1, LnumNew: 1},
   362  					{Type: 2, Content: "deleted line", LnumDiff: 16, LnumOld: 2, LnumNew: 0},
   363  					{Type: 1, Content: "added line", LnumDiff: 17, LnumOld: 0, LnumNew: 2},
   364  					{Type: 1, Content: "added line", LnumDiff: 18, LnumOld: 0, LnumNew: 3},
   365  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 19, LnumOld: 3, LnumNew: 4},
   366  				},
   367  			},
   368  		},
   369  	}
   370  	for _, tt := range tests {
   371  		got, err := (&hunkParser{r: bufio.NewReader(strings.NewReader(tt.in)), lnumdiff: tt.lnumdiff}).Parse()
   372  		if err != nil {
   373  			t.Errorf("hunkParser.Parse(%v) got an unexpected err %v", tt.in, err)
   374  		}
   375  		if !reflect.DeepEqual(got, tt.want) {
   376  			t.Errorf("hunkParser.Parse(%v) = \n%#v\n, want \n%#v", tt.in, got, tt.want)
   377  			t.Logf("got lines:")
   378  			for _, l := range got.Lines {
   379  				t.Logf("%#v", l)
   380  			}
   381  			t.Logf("want lines:")
   382  			for _, l := range tt.want.Lines {
   383  				t.Logf("%#v", l)
   384  			}
   385  		}
   386  	}
   387  }
   388  
   389  func TestParseHunkRange(t *testing.T) {
   390  	tests := []struct {
   391  		in   string
   392  		want *hunkrange
   393  	}{
   394  		{
   395  			in:   "@@ -1,3 +1,4 @@",
   396  			want: &hunkrange{lold: 1, sold: 3, lnew: 1, snew: 4},
   397  		},
   398  		{
   399  			in:   "@@ -1 +1 @@",
   400  			want: &hunkrange{lold: 1, sold: 1, lnew: 1, snew: 1},
   401  		},
   402  		{
   403  			in:   "@@ -1,3 +1,4 @@ optional section",
   404  			want: &hunkrange{lold: 1, sold: 3, lnew: 1, snew: 4, section: "optional section"},
   405  		},
   406  	}
   407  	for _, tt := range tests {
   408  		got, err := parseHunkRange(tt.in)
   409  		if err != nil {
   410  			t.Errorf("parseHunkRange(%v) got an unexpected err %v", tt.in, err)
   411  		}
   412  		if !reflect.DeepEqual(got, tt.want) {
   413  			t.Errorf("parseHunkRange(%v) = %#v, want %#v", tt.in, got, tt.want)
   414  		}
   415  	}
   416  }
   417  
   418  func TestParseLS(t *testing.T) {
   419  	tests := []struct {
   420  		in string
   421  		l  int
   422  		s  int
   423  	}{
   424  		{in: "1,3", l: 1, s: 3},
   425  		{in: "14", l: 14, s: 1},
   426  	}
   427  	for _, tt := range tests {
   428  		gotl, gots, err := parseLS(tt.in)
   429  		if err != nil {
   430  			t.Errorf("parseLS(%v) got an unexpected err %v", tt.in, err)
   431  		}
   432  		if gotl != tt.l || gots != tt.s {
   433  			t.Errorf("parseLS(%v) = (%v, %v, _), want (%v, %v, _)", tt.in, gotl, gots, tt.l, tt.s)
   434  		}
   435  	}
   436  }
   437  
   438  func TestReadline(t *testing.T) {
   439  	tests := []struct {
   440  		in  string
   441  		out []string
   442  	}{
   443  		{
   444  			in: `line1
   445  line2
   446  line3`,
   447  			out: []string{"line1", "line2", "line3"},
   448  		},
   449  		{
   450  			in: longLine + `
   451  line2
   452  line3`,
   453  			out: []string{longLine, "line2", "line3"},
   454  		},
   455  	}
   456  	for _, tt := range tests {
   457  		r := bufio.NewReader(strings.NewReader(tt.in))
   458  		for _, exp := range tt.out {
   459  			got, err := readline(r)
   460  			if err != nil {
   461  				t.Error(err)
   462  			}
   463  			if got != exp {
   464  				t.Errorf("got:\n%v\n\nwant:\n%v", got, exp)
   465  			}
   466  		}
   467  		{
   468  			if _, err := readline(r); err != io.EOF {
   469  				t.Errorf("got err %v, want io.EOF", err)
   470  			}
   471  		}
   472  	}
   473  }
   474  
   475  func TestUnquoteCStyle(t *testing.T) {
   476  	tests := []struct {
   477  		in  string
   478  		out string
   479  	}{
   480  		{in: `no need to unquote`, out: `no need to unquote`},
   481  		{in: `"C-escapes \a\b\t\n\v\f\r\"\\"`, out: "C-escapes \a\b\t\n\v\f\r\"\\"},
   482  
   483  		// from https://github.com/git/git/blob/041f5ea1cf987a4068ef5f39ba0a09be85952064/t/t3902-quoted.sh#L48-L76
   484  		{in: `Name`, out: `Name`},
   485  		{in: `"Name and a\nLF"`, out: "Name and a\nLF"},
   486  		{in: `"Name and an\tHT"`, out: "Name and an\tHT"},
   487  		{in: `"Name\""`, out: `Name"`},
   488  		{in: `With SP in it`, out: `With SP in it`},
   489  		{in: `"\346\277\261\351\207\216\t\347\264\224"`, out: "濱野\t純"},
   490  		{in: `"\346\277\261\351\207\216\n\347\264\224"`, out: "濱野\n純"},
   491  		{in: `"\346\277\261\351\207\216 \347\264\224"`, out: `濱野 純`},
   492  		{in: `"\346\277\261\351\207\216\"\347\264\224"`, out: "濱野\"純"},
   493  		{in: `"\346\277\261\351\207\216/file"`, out: `濱野/file`},
   494  		{in: `"\346\277\261\351\207\216\347\264\224"`, out: `濱野純`},
   495  
   496  		// Edge cases of ill-formed diff file name.
   497  		{in: `\347\264\224`, out: `\347\264\224`}, // no need to unquote
   498  		{in: `"\34a"`, out: "34a"},
   499  		{in: `"\14"`, out: `14`},
   500  	}
   501  
   502  	for _, tt := range tests {
   503  		if got := unquoteCStyle(tt.in); got != tt.out {
   504  			t.Errorf("unquoteCStyle(%q) = %q, want %q", tt.in, got, tt.out)
   505  		}
   506  	}
   507  }