github.com/friedemannf/reviewdog@v0.14.0/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  func TestParseMultiFile(t *testing.T) {
    17  	files, err := filepath.Glob("testdata/*.diff")
    18  	if err != nil {
    19  		t.Fatal(err)
    20  	}
    21  	for _, fname := range files {
    22  		t.Log(fname)
    23  		f, err := os.Open(fname)
    24  		if err != nil {
    25  			t.Fatal(err)
    26  		}
    27  		difffiles, err := ParseMultiFile(f)
    28  		if err != nil {
    29  			t.Errorf("%v: %v", fname, err)
    30  		}
    31  
    32  		wantfile, err := os.Open(fname + ".json")
    33  		if err != nil {
    34  			t.Fatal(err)
    35  		}
    36  		dec := json.NewDecoder(wantfile)
    37  		var want []*FileDiff
    38  		if err := dec.Decode(&want); err != nil {
    39  			t.Fatal(err)
    40  		}
    41  		if !reflect.DeepEqual(difffiles, want) {
    42  			l := len(difffiles)
    43  			if len(want) > l {
    44  				l = len(want)
    45  			}
    46  			for i := 0; i < l; i++ {
    47  				var a, b *FileDiff
    48  				if i < len(want) {
    49  					a = want[i]
    50  				}
    51  				if i < len(difffiles) {
    52  					b = difffiles[i]
    53  				}
    54  				t.Errorf("want %#v, got %#v", a, b)
    55  			}
    56  		}
    57  
    58  		wantfile.Close()
    59  		f.Close()
    60  	}
    61  }
    62  
    63  func TestParseMultiFile_sample(t *testing.T) {
    64  	content := `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
    65  +++ sample.new.txt	2016-10-13 05:15:26.839245048 +0900
    66  @@ -1,3 +1,4 @@
    67   unchanged, contextual line
    68  -deleted line
    69  +added line
    70  +added line
    71   unchanged, contextual line
    72  --- nonewline.old.txt	2016-10-13 15:34:14.931778318 +0900
    73  +++ nonewline.new.txt	2016-10-13 15:34:14.868444672 +0900
    74  @@ -1,4 +1,4 @@
    75   " vim: nofixeol noendofline
    76   No newline at end of both the old and new file
    77  -a
    78  -a
    79  \ No newline at end of file
    80  +b
    81  +b
    82  \ No newline at end of file
    83  `
    84  
    85  	got, err := ParseMultiFile(strings.NewReader(content))
    86  	if err != nil {
    87  		t.Fatal(err)
    88  	}
    89  	want := []*FileDiff{
    90  		{
    91  			PathOld: "sample.old.txt",
    92  			PathNew: "sample.new.txt",
    93  			TimeOld: "2016-10-13 05:09:35.820791185 +0900",
    94  			TimeNew: "2016-10-13 05:15:26.839245048 +0900",
    95  			Hunks: []*Hunk{
    96  				{
    97  					StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
    98  					Lines: []*Line{
    99  						{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   100  						{Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0},
   101  						{Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2},
   102  						{Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3},
   103  						{Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4},
   104  					},
   105  				},
   106  			},
   107  		},
   108  		{
   109  			PathOld: "nonewline.old.txt",
   110  			PathNew: "nonewline.new.txt",
   111  			TimeOld: "2016-10-13 15:34:14.931778318 +0900",
   112  			TimeNew: "2016-10-13 15:34:14.868444672 +0900",
   113  			Hunks: []*Hunk{
   114  				{
   115  					StartLineOld: 1, LineLengthOld: 4, StartLineNew: 1, LineLengthNew: 4,
   116  					Lines: []*Line{
   117  						{Type: 0, Content: "\" vim: nofixeol noendofline", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   118  						{Type: 0, Content: "No newline at end of both the old and new file", LnumDiff: 2, LnumOld: 2, LnumNew: 2},
   119  						{Type: 2, Content: "a", LnumDiff: 3, LnumOld: 3, LnumNew: 0},
   120  						{Type: 2, Content: "a", LnumDiff: 4, LnumOld: 4, LnumNew: 0},
   121  						{Type: 1, Content: "b", LnumDiff: 5, LnumOld: 0, LnumNew: 3},
   122  						{Type: 1, Content: "b", LnumDiff: 6, LnumOld: 0, LnumNew: 4},
   123  					},
   124  				},
   125  			},
   126  		},
   127  	}
   128  	if !reflect.DeepEqual(got, want) {
   129  		t.Errorf("error. in:\n%v", content)
   130  		for _, fd := range got {
   131  			t.Logf("FileDiff: %#v\n", fd)
   132  			for _, h := range fd.Hunks {
   133  				t.Logf("  Hunk%#v\n", h)
   134  				for _, l := range h.Lines {
   135  					t.Logf("    Line%#v\n", l)
   136  				}
   137  			}
   138  		}
   139  	}
   140  }
   141  
   142  func TestFileParser_Parse(t *testing.T) {
   143  	tests := []struct {
   144  		in   string
   145  		want *FileDiff
   146  	}{
   147  		{
   148  			in:   "",
   149  			want: nil,
   150  		},
   151  		{
   152  			in: `diff --git a/empty.txt b/empty.txtq
   153  deleted file mode 100644
   154  index e69de29..0000000
   155  `,
   156  			want: &FileDiff{
   157  				Extended: []string{
   158  					"diff --git a/empty.txt b/empty.txtq",
   159  					"deleted file mode 100644",
   160  					"index e69de29..0000000",
   161  				},
   162  			},
   163  		},
   164  		{
   165  			in: `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
   166  +++ sample.new.txt	2016-10-13 05:15:26.839245048 +0900
   167  @@ -1,3 +1,4 @@
   168   unchanged, contextual line
   169  -deleted line
   170  +added line
   171  +added line
   172   unchanged, contextual line
   173  `,
   174  			want: &FileDiff{
   175  				PathOld: "sample.old.txt",
   176  				PathNew: "sample.new.txt",
   177  				TimeOld: "2016-10-13 05:09:35.820791185 +0900",
   178  				TimeNew: "2016-10-13 05:15:26.839245048 +0900",
   179  				Hunks: []*Hunk{
   180  					{
   181  						StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
   182  						Lines: []*Line{
   183  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   184  							{Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0},
   185  							{Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2},
   186  							{Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3},
   187  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4},
   188  						},
   189  					},
   190  				},
   191  			},
   192  		},
   193  		{
   194  			in: `--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900
   195  +++ sample.new.txt	2016-10-13 05:15:26.839245048 +0900
   196  @@ -1,1 +1,1 @@
   197   unchanged, contextual line
   198  @@ -2,1 +2,1 @@
   199   unchanged, contextual line
   200  `,
   201  			want: &FileDiff{
   202  				PathOld: "sample.old.txt",
   203  				PathNew: "sample.new.txt",
   204  				TimeOld: "2016-10-13 05:09:35.820791185 +0900",
   205  				TimeNew: "2016-10-13 05:15:26.839245048 +0900",
   206  				Hunks: []*Hunk{
   207  					{
   208  						StartLineOld: 1, LineLengthOld: 1, StartLineNew: 1, LineLengthNew: 1,
   209  						Lines: []*Line{
   210  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   211  						},
   212  					},
   213  					{
   214  						StartLineOld: 2, LineLengthOld: 1, StartLineNew: 2, LineLengthNew: 1,
   215  						Lines: []*Line{
   216  							{Type: 0, Content: "unchanged, contextual line", LnumDiff: 3, LnumOld: 2, LnumNew: 2},
   217  						},
   218  					},
   219  				},
   220  			},
   221  		},
   222  	}
   223  	for _, tt := range tests {
   224  		p := &fileParser{r: bufio.NewReader(strings.NewReader(tt.in))}
   225  		got, err := p.Parse()
   226  		if err != nil {
   227  			t.Errorf("got error %v for in:\n %v", err, tt.in)
   228  		}
   229  		if !reflect.DeepEqual(got, tt.want) {
   230  			t.Errorf("fileParser.Parse() = %#v, want %#v\nin: %v", got, tt.want, tt.in)
   231  			t.Log("got:")
   232  			for _, h := range got.Hunks {
   233  				for _, l := range h.Lines {
   234  					t.Logf("%#v", l)
   235  				}
   236  			}
   237  			t.Log("want:")
   238  			for _, h := range tt.want.Hunks {
   239  				for _, l := range h.Lines {
   240  					t.Logf("%#v", l)
   241  				}
   242  			}
   243  		}
   244  	}
   245  }
   246  
   247  func TestParseFileHeader(t *testing.T) {
   248  	tests := []struct {
   249  		in        string
   250  		filename  string
   251  		timestamp string
   252  	}{
   253  		{
   254  			in: "--- sample.old.txt	2016-10-13 05:09:35.820791185 +0900",
   255  			filename:  "sample.old.txt",
   256  			timestamp: "2016-10-13 05:09:35.820791185 +0900",
   257  		},
   258  		{
   259  			in:        "+++ sample.old.txt",
   260  			filename:  "sample.old.txt",
   261  			timestamp: "",
   262  		},
   263  	}
   264  	for _, tt := range tests {
   265  		gotf, gott := parseFileHeader(tt.in)
   266  		if gotf != tt.filename || gott != tt.timestamp {
   267  			t.Errorf("parseFileHeader(%v) = (%v, %v), want (%v, %v)", tt.in, gotf, gott, tt.filename, tt.timestamp)
   268  		}
   269  	}
   270  }
   271  
   272  func TestParseExtendedHeader(t *testing.T) {
   273  	tests := []struct {
   274  		in   string
   275  		want []string
   276  	}{
   277  		{
   278  			in: `diff --git a/sample.txt b/sample.txt
   279  index a949a96..769bdae 100644
   280  --- a/sample.old.txt
   281  +++ b/sample.new.txt
   282  @@ -1,3 +1,4 @@
   283  `,
   284  			want: []string{"diff --git a/sample.txt b/sample.txt", "index a949a96..769bdae 100644"},
   285  		},
   286  		{
   287  			in: `diff --git a/sample.txt b/sample.txt
   288  deleted file mode 100644
   289  index e69de29..0000000
   290  `,
   291  			want: []string{"diff --git a/sample.txt b/sample.txt", "deleted file mode 100644", "index e69de29..0000000"},
   292  		},
   293  		{
   294  			in: `diff --git a/sample.txt b/sample.txt
   295  new file mode 100644
   296  index 0000000..e69de29
   297  diff --git a/sample2.txt b/sample2.txt
   298  new file mode 100644
   299  index 0000000..ee946eb
   300  `,
   301  			want: []string{"diff --git a/sample.txt b/sample.txt", "new file mode 100644", "index 0000000..e69de29"},
   302  		},
   303  		{
   304  			in: `--- a/sample.old.txt
   305  +++ b/sample.new.txt
   306  @@ -1,3 +1,4 @@
   307  `,
   308  			want: nil,
   309  		},
   310  	}
   311  	for _, tt := range tests {
   312  		got := parseExtendedHeader(bufio.NewReader(strings.NewReader(tt.in)))
   313  		if !reflect.DeepEqual(got, tt.want) {
   314  			t.Errorf("in:\n%v\ngot:\n%v\nwant:\n%v", tt.in, strings.Join(got, "\n"), strings.Join(tt.want, "\n"))
   315  		}
   316  	}
   317  }
   318  
   319  func TestHunkParser_Parse(t *testing.T) {
   320  	tests := []struct {
   321  		in       string
   322  		lnumdiff int
   323  		want     *Hunk
   324  	}{
   325  		{
   326  			in: `@@ -1,3 +1,4 @@ optional section heading
   327   unchanged, contextual line
   328  -deleted line
   329  +added line
   330  +added line
   331   unchanged, contextual line
   332  `,
   333  			want: &Hunk{
   334  				StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
   335  				Section: "optional section heading",
   336  				Lines: []*Line{
   337  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 1, LnumOld: 1, LnumNew: 1},
   338  					{Type: 2, Content: "deleted line", LnumDiff: 2, LnumOld: 2, LnumNew: 0},
   339  					{Type: 1, Content: "added line", LnumDiff: 3, LnumOld: 0, LnumNew: 2},
   340  					{Type: 1, Content: "added line", LnumDiff: 4, LnumOld: 0, LnumNew: 3},
   341  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 5, LnumOld: 3, LnumNew: 4},
   342  				},
   343  			},
   344  		},
   345  		{
   346  			in: `@@ -1,3 +1,4 @@
   347   unchanged, contextual line
   348  -deleted line
   349  +added line
   350  +added line
   351   unchanged, contextual line
   352  @@ -1,3 +1,4 @@
   353  `,
   354  			lnumdiff: 14,
   355  			want: &Hunk{
   356  				StartLineOld: 1, LineLengthOld: 3, StartLineNew: 1, LineLengthNew: 4,
   357  				Section: "",
   358  				Lines: []*Line{
   359  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 15, LnumOld: 1, LnumNew: 1},
   360  					{Type: 2, Content: "deleted line", LnumDiff: 16, LnumOld: 2, LnumNew: 0},
   361  					{Type: 1, Content: "added line", LnumDiff: 17, LnumOld: 0, LnumNew: 2},
   362  					{Type: 1, Content: "added line", LnumDiff: 18, LnumOld: 0, LnumNew: 3},
   363  					{Type: 0, Content: "unchanged, contextual line", LnumDiff: 19, LnumOld: 3, LnumNew: 4},
   364  				},
   365  			},
   366  		},
   367  	}
   368  	for _, tt := range tests {
   369  		got, err := (&hunkParser{r: bufio.NewReader(strings.NewReader(tt.in)), lnumdiff: tt.lnumdiff}).Parse()
   370  		if err != nil {
   371  			t.Errorf("hunkParser.Parse(%v) got an unexpected err %v", tt.in, err)
   372  		}
   373  		if !reflect.DeepEqual(got, tt.want) {
   374  			t.Errorf("hunkParser.Parse(%v) = \n%#v\n, want \n%#v", tt.in, got, tt.want)
   375  			t.Logf("got lines:")
   376  			for _, l := range got.Lines {
   377  				t.Logf("%#v", l)
   378  			}
   379  			t.Logf("want lines:")
   380  			for _, l := range tt.want.Lines {
   381  				t.Logf("%#v", l)
   382  			}
   383  		}
   384  	}
   385  }
   386  
   387  func TestParseHunkRange(t *testing.T) {
   388  	tests := []struct {
   389  		in   string
   390  		want *hunkrange
   391  	}{
   392  		{
   393  			in:   "@@ -1,3 +1,4 @@",
   394  			want: &hunkrange{lold: 1, sold: 3, lnew: 1, snew: 4},
   395  		},
   396  		{
   397  			in:   "@@ -1 +1 @@",
   398  			want: &hunkrange{lold: 1, sold: 1, lnew: 1, snew: 1},
   399  		},
   400  		{
   401  			in:   "@@ -1,3 +1,4 @@ optional section",
   402  			want: &hunkrange{lold: 1, sold: 3, lnew: 1, snew: 4, section: "optional section"},
   403  		},
   404  	}
   405  	for _, tt := range tests {
   406  		got, err := parseHunkRange(tt.in)
   407  		if err != nil {
   408  			t.Errorf("parseHunkRange(%v) got an unexpected err %v", tt.in, err)
   409  		}
   410  		if !reflect.DeepEqual(got, tt.want) {
   411  			t.Errorf("parseHunkRange(%v) = %#v, want %#v", tt.in, got, tt.want)
   412  		}
   413  	}
   414  }
   415  
   416  func TestParseLS(t *testing.T) {
   417  	tests := []struct {
   418  		in string
   419  		l  int
   420  		s  int
   421  	}{
   422  		{in: "1,3", l: 1, s: 3},
   423  		{in: "14", l: 14, s: 1},
   424  	}
   425  	for _, tt := range tests {
   426  		gotl, gots, err := parseLS(tt.in)
   427  		if err != nil {
   428  			t.Errorf("parseLS(%v) got an unexpected err %v", tt.in, err)
   429  		}
   430  		if gotl != tt.l || gots != tt.s {
   431  			t.Errorf("parseLS(%v) = (%v, %v, _), want (%v, %v, _)", tt.in, gotl, gots, tt.l, tt.s)
   432  		}
   433  	}
   434  }
   435  
   436  func TestReadline(t *testing.T) {
   437  	text := `line1
   438  line2
   439  line3`
   440  	r := bufio.NewReader(strings.NewReader(text))
   441  	{
   442  		got, err := readline(r)
   443  		if err != nil {
   444  			t.Error(err)
   445  		}
   446  		if got != "line1" {
   447  			t.Errorf("got %v, want line1", got)
   448  		}
   449  	}
   450  	{
   451  		got, err := readline(r)
   452  		if err != nil {
   453  			t.Error(err)
   454  		}
   455  		if got != "line2" {
   456  			t.Errorf("got %v, want line2", got)
   457  		}
   458  	}
   459  	{
   460  		got, err := readline(r)
   461  		if err != nil {
   462  			t.Error(err)
   463  		}
   464  		if got != "line3" {
   465  			t.Errorf("got %v, want line3", got)
   466  		}
   467  	}
   468  	{
   469  		if _, err := readline(r); err != io.EOF {
   470  			t.Errorf("got err %v, want io.EOF", err)
   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  }