github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/util_test.go (about)

     1  // Copyright 2011 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nin
    16  
    17  import (
    18  	"runtime"
    19  	"strconv"
    20  	"strings"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  )
    25  
    26  func TestCanonicalizePath_PathSamples(t *testing.T) {
    27  	type row struct {
    28  		in   string
    29  		want string
    30  	}
    31  	data := []row{
    32  		{"", ""},
    33  		{"foo.h", "foo.h"},
    34  		{"./foo.h", "foo.h"},
    35  		{"./foo/./bar.h", "foo/bar.h"},
    36  		{"./x/foo/../bar.h", "x/bar.h"},
    37  		{"./x/foo/../../bar.h", "bar.h"},
    38  		{"foo//bar", "foo/bar"},
    39  		{"foo//.//..///bar", "bar"},
    40  		{"./x/../foo/../../bar.h", "../bar.h"},
    41  		{"foo/./.", "foo"},
    42  		{"foo/bar/..", "foo"},
    43  		{"foo/.hidden_bar", "foo/.hidden_bar"},
    44  		{"/foo", "/foo"},
    45  		{"/", ""},
    46  		{"/foo/..", ""},
    47  		{".", "."},
    48  		{"./.", "."},
    49  		{"foo/..", "."},
    50  		// CanonicalizePath.UpDir:
    51  		{"../../foo/bar.h", "../../foo/bar.h"},
    52  		{"test/../../foo/bar.h", "../foo/bar.h"},
    53  		// CanonicalizePath.AbsolutePath
    54  		{"/usr/include/stdio.h", "/usr/include/stdio.h"},
    55  	}
    56  	if runtime.GOOS == "windows" {
    57  		data = append(data, row{"//foo", "//foo"})
    58  	} else {
    59  		data = append(data, row{"//foo", "/foo"})
    60  	}
    61  	for i, l := range data {
    62  		t.Run(strconv.Itoa(i), func(t *testing.T) {
    63  			got := CanonicalizePath(l.in)
    64  			if l.want != got {
    65  				t.Fatalf("want: %q, got: %q", l.want, got)
    66  			}
    67  			got2, _ := CanonicalizePathBits(l.in)
    68  			if got != got2 {
    69  				t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits")
    70  			}
    71  		})
    72  	}
    73  }
    74  
    75  func TestCanonicalizePath_PathSamplesWindows(t *testing.T) {
    76  	if runtime.GOOS != "windows" {
    77  		t.Skip("windows only")
    78  	}
    79  	type row struct {
    80  		in   string
    81  		want string
    82  	}
    83  	data := []row{
    84  		{"", ""},
    85  		{"foo.", "foo."},
    86  		{".\\foo.h", "foo.h"},
    87  		{".\\foo\\.\\bar.h", "foo/bar.h"},
    88  		{".\\x\\foo\\..\\bar.h", "x/bar.h"},
    89  		{".\\x\\foo\\..\\..\\bar.h", "bar.h"},
    90  		{"foo\\\\bar", "foo/bar"},
    91  		{"foo\\\\.\\\\..\\\\\\bar", "bar"},
    92  		{".\\x\\..\\foo\\..\\..\\bar.h", "../bar.h"},
    93  		{"foo\\.\\.", "foo"},
    94  		{"foo\\bar\\..", "foo"},
    95  		{"foo\\.hidden_bar", "foo/.hidden_bar"},
    96  		{"\\foo", "/foo"},
    97  		{"\\\\foo", "//foo"},
    98  		{"\\", ""},
    99  	}
   100  	for i, l := range data {
   101  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   102  			got := CanonicalizePath(l.in)
   103  			if l.want != got {
   104  				t.Fatalf("want: %q, got: %q", l.want, got)
   105  			}
   106  			got2, _ := CanonicalizePathBits(l.in)
   107  			if got != got2 {
   108  				t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits")
   109  			}
   110  		})
   111  	}
   112  }
   113  
   114  func TestCanonicalizePathBits_SlashTracking(t *testing.T) {
   115  	if runtime.GOOS != "windows" {
   116  		t.Skip("windows only")
   117  	}
   118  	type row struct {
   119  		in       string
   120  		want     string
   121  		wantBits uint64
   122  	}
   123  	data := []row{
   124  		{"", "", 0},
   125  		{"foo.h", "foo.h", 0},
   126  		{"foo.h", "foo.h", 0},
   127  		{"a\\foo.h", "a/foo.h", 1},
   128  		{"a/bcd/efh\\foo.h", "a/bcd/efh/foo.h", 4},
   129  		{"a\\bcd/efh\\foo.h", "a/bcd/efh/foo.h", 5},
   130  		{"a\\bcd\\efh\\foo.h", "a/bcd/efh/foo.h", 7},
   131  		{"a/bcd/efh/foo.h", "a/bcd/efh/foo.h", 0},
   132  		{"a\\./efh\\foo.h", "a/efh/foo.h", 3},
   133  		{"a\\../efh\\foo.h", "efh/foo.h", 1},
   134  		{"a\\b\\c\\d\\e\\f\\g\\foo.h", "a/b/c/d/e/f/g/foo.h", 127},
   135  		{"a\\b\\c\\..\\..\\..\\g\\foo.h", "g/foo.h", 1},
   136  		{"a\\b/c\\../../..\\g\\foo.h", "g/foo.h", 1},
   137  		{"a\\b/c\\./../..\\g\\foo.h", "a/g/foo.h", 3},
   138  		{"a\\b/c\\./../..\\g/foo.h", "a/g/foo.h", 1},
   139  		{"a\\\\\\foo.h", "a/foo.h", 1},
   140  		{"a/\\\\foo.h", "a/foo.h", 0},
   141  		{"a\\//foo.h", "a/foo.h", 1},
   142  	}
   143  	for i, l := range data {
   144  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   145  			got, slashBits := CanonicalizePathBits(l.in)
   146  			if l.want != got {
   147  				t.Fatalf("want: %q, got: %q", l.want, got)
   148  			}
   149  			if slashBits != l.wantBits {
   150  				t.Fatalf("want: %d, got: %d", l.wantBits, slashBits)
   151  			}
   152  			got2 := CanonicalizePath(l.in)
   153  			if got != got2 {
   154  				t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits")
   155  			}
   156  		})
   157  	}
   158  }
   159  
   160  func TestCanonicalizePath_CanonicalizeNotExceedingLen(t *testing.T) {
   161  	if runtime.GOOS != "windows" {
   162  		t.Skip("windows only")
   163  	}
   164  	t.Skip("This test is irrelevant in Go. Remove once conversion is done")
   165  }
   166  
   167  func TestCanonicalizePath_TooManyComponents(t *testing.T) {
   168  	if runtime.GOOS != "windows" {
   169  		t.Skip("windows only")
   170  	}
   171  	t.Skip("TODO")
   172  	type row struct {
   173  		in string
   174  		//want      string
   175  		wantBits uint64
   176  	}
   177  	data := []row{
   178  		// 64 is OK.
   179  		{
   180  			"a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x.h",
   181  			0,
   182  		},
   183  		// Backslashes version.
   184  		{
   185  			"a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x.h",
   186  			0xffffffff,
   187  		},
   188  		// 65 is OK if #component is less than 60 after path canonicalization.
   189  		{
   190  			"a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./a/./x/y.h",
   191  			0,
   192  		},
   193  		// Backslashes version.
   194  		{
   195  			"a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\a\\.\\x\\y.h",
   196  			0x1ffffffff,
   197  		},
   198  		// 59 after canonicalization is OK.
   199  		{
   200  			"a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/x/y.h",
   201  			0,
   202  		},
   203  		// Backslashes version.
   204  		{
   205  			"a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\a\\x\\y.h",
   206  			0x3ffffffffffffff,
   207  		},
   208  	}
   209  	// Manual check that the last 2 ones have 58 items.
   210  	if 58 != strings.Count(data[4].in, "/") {
   211  		t.Fatal("expected equal")
   212  	}
   213  	if 58 != strings.Count(data[5].in, "\\") {
   214  		t.Fatal("expected equal")
   215  	}
   216  
   217  	for i, l := range data {
   218  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   219  			got, slashBits := CanonicalizePathBits(l.in)
   220  			if slashBits != l.wantBits {
   221  				t.Fatalf("want: %d, got: %d", l.wantBits, slashBits)
   222  			}
   223  			got2 := CanonicalizePath(l.in)
   224  			if got != got2 {
   225  				t.Fatal("Mismatch between CanonicalizePath and CanonicalizePathBits")
   226  			}
   227  		})
   228  	}
   229  }
   230  
   231  func TestCanonicalizePath_NotNullTerminated(t *testing.T) {
   232  	t.Skip("This test is irrelevant in Go. Remove once conversion is done")
   233  }
   234  
   235  func TestPathEscaping_TortureTest(t *testing.T) {
   236  	got := getWin32EscapedString("foo bar\\\"'$@d!st!c'\\path'\\")
   237  	if diff := cmp.Diff("\"foo bar\\\\\\\"'$@d!st!c'\\path'\\\\\"", got); diff != "" {
   238  		t.Fatalf("+want, -got: %s", diff)
   239  	}
   240  	got = getShellEscapedString("foo bar\"/'$@d!st!c'/path'")
   241  	if diff := cmp.Diff("'foo bar\"/'\\''$@d!st!c'\\''/path'\\'''", got); diff != "" {
   242  		t.Fatalf("+want, -got: %s", diff)
   243  	}
   244  }
   245  
   246  func TestPathEscaping_SensiblePathsAreNotNeedlesslyEscaped(t *testing.T) {
   247  	path := "some/sensible/path/without/crazy/characters.c++"
   248  	got := getWin32EscapedString(path)
   249  	if diff := cmp.Diff(path, got); diff != "" {
   250  		t.Fatalf("+want, -got: %s", diff)
   251  	}
   252  	got = getShellEscapedString(path)
   253  	if diff := cmp.Diff(path, got); diff != "" {
   254  		t.Fatalf("+want, -got: %s", diff)
   255  	}
   256  }
   257  
   258  func TestPathEscaping_SensibleWin32PathsAreNotNeedlesslyEscaped(t *testing.T) {
   259  	path := "some\\sensible\\path\\without\\crazy\\characters.c++"
   260  	result := getWin32EscapedString(path)
   261  	if path != result {
   262  		t.Fatal("expected equal")
   263  	}
   264  }
   265  
   266  func TestElideMiddle_NothingToElide(t *testing.T) {
   267  	input := "Nothing to elide in this short string."
   268  	if input != elideMiddle(input, 80) {
   269  		t.Fatal("expected equal")
   270  	}
   271  	if input != elideMiddle(input, 38) {
   272  		t.Fatal("expected equal")
   273  	}
   274  	if "" != elideMiddle(input, 0) {
   275  		t.Fatal("expected equal")
   276  	}
   277  	if "." != elideMiddle(input, 1) {
   278  		t.Fatal("expected equal")
   279  	}
   280  	if ".." != elideMiddle(input, 2) {
   281  		t.Fatal("expected equal")
   282  	}
   283  	if "..." != elideMiddle(input, 3) {
   284  		t.Fatal("expected equal")
   285  	}
   286  }
   287  
   288  func TestElideMiddle_ElideInTheMiddle(t *testing.T) {
   289  	input := "01234567890123456789"
   290  	elided := elideMiddle(input, 10)
   291  	if "012...789" != elided {
   292  		t.Fatal("expected equal")
   293  	}
   294  	if "01234567...23456789" != elideMiddle(input, 19) {
   295  		t.Fatal("expected equal")
   296  	}
   297  }
   298  
   299  var dummyBenchmarkValue = ""
   300  
   301  // The C++ version is canonPerftest. It runs 2000000 iterations.
   302  //
   303  // On my workstation:
   304  //
   305  // The C++ version has an minimum of 82ms.
   306  //
   307  // The Go version with "go test -cpu 1 -bench=. -run BenchmarkCanonicalizePath"
   308  // has a minimum of 157ns, which multiplied by 2000000 gives 306ms. So the code
   309  // is nearly 4x slower. I'll have to optimize later.
   310  func BenchmarkCanonicalizePathBits(b *testing.B) {
   311  	b.ReportAllocs()
   312  	kPath := "../../third_party/WebKit/Source/WebCore/platform/leveldb/LevelDBWriteBatch.cpp"
   313  	s := ""
   314  	for i := 0; i < b.N; i++ {
   315  		s, _ = CanonicalizePathBits(kPath)
   316  	}
   317  	// Use s so it's not optimized out.
   318  	dummyBenchmarkValue = s
   319  }
   320  
   321  func BenchmarkCanonicalizePath(b *testing.B) {
   322  	b.ReportAllocs()
   323  	kPath := "../../third_party/WebKit/Source/WebCore/platform/leveldb/LevelDBWriteBatch.cpp"
   324  	s := ""
   325  	for i := 0; i < b.N; i++ {
   326  		s = CanonicalizePath(kPath)
   327  	}
   328  	// Use s so it's not optimized out.
   329  	dummyBenchmarkValue = s
   330  }