github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/helpers/path_test.go (about)

     1  // Copyright 2015 The Hugo Authors. 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  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package helpers
    15  
    16  import (
    17  	"fmt"
    18  	"os"
    19  	"path/filepath"
    20  	"reflect"
    21  	"runtime"
    22  	"strconv"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/gohugoio/hugo/langs"
    28  
    29  	qt "github.com/frankban/quicktest"
    30  
    31  	"github.com/gohugoio/hugo/hugofs"
    32  	"github.com/spf13/afero"
    33  )
    34  
    35  func TestMakePath(t *testing.T) {
    36  	c := qt.New(t)
    37  	tests := []struct {
    38  		input         string
    39  		expected      string
    40  		removeAccents bool
    41  	}{
    42  		{"dot.slash/backslash\\underscore_pound#plus+hyphen-", "dot.slash/backslash\\underscore_pound#plus+hyphen-", true},
    43  		{"abcXYZ0123456789", "abcXYZ0123456789", true},
    44  		{"%20 %2", "%20-2", true},
    45  		{"foo- bar", "foo-bar", true},
    46  		{"  Foo bar  ", "Foo-bar", true},
    47  		{"Foo.Bar/foo_Bar-Foo", "Foo.Bar/foo_Bar-Foo", true},
    48  		{"fOO,bar:foobAR", "fOObarfoobAR", true},
    49  		{"FOo/BaR.html", "FOo/BaR.html", true},
    50  		{"трям/трям", "трям/трям", true},
    51  		{"은행", "은행", true},
    52  		{"Банковский кассир", "Банковскии-кассир", true},
    53  		// Issue #1488
    54  		{"संस्कृत", "संस्कृत", false},
    55  		{"a%C3%B1ame", "a%C3%B1ame", false},         // Issue #1292
    56  		{"this+is+a+test", "this+is+a+test", false}, // Issue #1290
    57  		{"~foo", "~foo", false},                     // Issue #2177
    58  		{"foo--bar", "foo--bar", true},              // Issue #7288
    59  		{"foo@bar", "foo@bar", true},                //	Issue #10548
    60  	}
    61  
    62  	for _, test := range tests {
    63  		v := newTestCfg()
    64  		v.Set("removePathAccents", test.removeAccents)
    65  
    66  		l := langs.NewDefaultLanguage(v)
    67  		p, err := NewPathSpec(hugofs.NewMem(v), l, nil)
    68  		c.Assert(err, qt.IsNil)
    69  
    70  		output := p.MakePath(test.input)
    71  		if output != test.expected {
    72  			t.Errorf("Expected %#v, got %#v\n", test.expected, output)
    73  		}
    74  	}
    75  }
    76  
    77  func TestMakePathSanitized(t *testing.T) {
    78  	v := newTestCfg()
    79  
    80  	p, _ := NewPathSpec(hugofs.NewMem(v), v, nil)
    81  
    82  	tests := []struct {
    83  		input    string
    84  		expected string
    85  	}{
    86  		{"  FOO bar  ", "foo-bar"},
    87  		{"Foo.Bar/fOO_bAr-Foo", "foo.bar/foo_bar-foo"},
    88  		{"FOO,bar:FooBar", "foobarfoobar"},
    89  		{"foo/BAR.HTML", "foo/bar.html"},
    90  		{"трям/трям", "трям/трям"},
    91  		{"은행", "은행"},
    92  	}
    93  
    94  	for _, test := range tests {
    95  		output := p.MakePathSanitized(test.input)
    96  		if output != test.expected {
    97  			t.Errorf("Expected %#v, got %#v\n", test.expected, output)
    98  		}
    99  	}
   100  }
   101  
   102  func TestMakePathSanitizedDisablePathToLower(t *testing.T) {
   103  	v := newTestCfg()
   104  
   105  	v.Set("disablePathToLower", true)
   106  
   107  	l := langs.NewDefaultLanguage(v)
   108  	p, _ := NewPathSpec(hugofs.NewMem(v), l, nil)
   109  
   110  	tests := []struct {
   111  		input    string
   112  		expected string
   113  	}{
   114  		{"  FOO bar  ", "FOO-bar"},
   115  		{"Foo.Bar/fOO_bAr-Foo", "Foo.Bar/fOO_bAr-Foo"},
   116  		{"FOO,bar:FooBar", "FOObarFooBar"},
   117  		{"foo/BAR.HTML", "foo/BAR.HTML"},
   118  		{"трям/трям", "трям/трям"},
   119  		{"은행", "은행"},
   120  	}
   121  
   122  	for _, test := range tests {
   123  		output := p.MakePathSanitized(test.input)
   124  		if output != test.expected {
   125  			t.Errorf("Expected %#v, got %#v\n", test.expected, output)
   126  		}
   127  	}
   128  }
   129  
   130  func TestMakePathRelative(t *testing.T) {
   131  	type test struct {
   132  		inPath, path1, path2, output string
   133  	}
   134  
   135  	data := []test{
   136  		{"/abc/bcd/ab.css", "/abc/bcd", "/bbc/bcd", "/ab.css"},
   137  		{"/abc/bcd/ab.css", "/abcd/bcd", "/abc/bcd", "/ab.css"},
   138  	}
   139  
   140  	for i, d := range data {
   141  		output, _ := makePathRelative(d.inPath, d.path1, d.path2)
   142  		if d.output != output {
   143  			t.Errorf("Test #%d failed. Expected %q got %q", i, d.output, output)
   144  		}
   145  	}
   146  	_, error := makePathRelative("a/b/c.ss", "/a/c", "/d/c", "/e/f")
   147  
   148  	if error == nil {
   149  		t.Errorf("Test failed, expected error")
   150  	}
   151  }
   152  
   153  func TestGetDottedRelativePath(t *testing.T) {
   154  	// on Windows this will receive both kinds, both country and western ...
   155  	for _, f := range []func(string) string{filepath.FromSlash, func(s string) string { return s }} {
   156  		doTestGetDottedRelativePath(f, t)
   157  	}
   158  }
   159  
   160  func doTestGetDottedRelativePath(urlFixer func(string) string, t *testing.T) {
   161  	type test struct {
   162  		input, expected string
   163  	}
   164  	data := []test{
   165  		{"", "./"},
   166  		{urlFixer("/"), "./"},
   167  		{urlFixer("post"), "../"},
   168  		{urlFixer("/post"), "../"},
   169  		{urlFixer("post/"), "../"},
   170  		{urlFixer("tags/foo.html"), "../"},
   171  		{urlFixer("/tags/foo.html"), "../"},
   172  		{urlFixer("/post/"), "../"},
   173  		{urlFixer("////post/////"), "../"},
   174  		{urlFixer("/foo/bar/index.html"), "../../"},
   175  		{urlFixer("/foo/bar/foo/"), "../../../"},
   176  		{urlFixer("/foo/bar/foo"), "../../../"},
   177  		{urlFixer("foo/bar/foo/"), "../../../"},
   178  		{urlFixer("foo/bar/foo/bar"), "../../../../"},
   179  		{"404.html", "./"},
   180  		{"404.xml", "./"},
   181  		{"/404.html", "./"},
   182  	}
   183  	for i, d := range data {
   184  		output := GetDottedRelativePath(d.input)
   185  		if d.expected != output {
   186  			t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
   187  		}
   188  	}
   189  }
   190  
   191  func TestMakeTitle(t *testing.T) {
   192  	type test struct {
   193  		input, expected string
   194  	}
   195  	data := []test{
   196  		{"Make-Title", "Make Title"},
   197  		{"MakeTitle", "MakeTitle"},
   198  		{"make_title", "make_title"},
   199  	}
   200  	for i, d := range data {
   201  		output := MakeTitle(d.input)
   202  		if d.expected != output {
   203  			t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
   204  		}
   205  	}
   206  }
   207  
   208  func TestDirExists(t *testing.T) {
   209  	type test struct {
   210  		input    string
   211  		expected bool
   212  	}
   213  
   214  	data := []test{
   215  		{".", true},
   216  		{"./", true},
   217  		{"..", true},
   218  		{"../", true},
   219  		{"./..", true},
   220  		{"./../", true},
   221  		{os.TempDir(), true},
   222  		{os.TempDir() + FilePathSeparator, true},
   223  		{"/", true},
   224  		{"/some-really-random-directory-name", false},
   225  		{"/some/really/random/directory/name", false},
   226  		{"./some-really-random-local-directory-name", false},
   227  		{"./some/really/random/local/directory/name", false},
   228  	}
   229  
   230  	for i, d := range data {
   231  		exists, _ := DirExists(filepath.FromSlash(d.input), new(afero.OsFs))
   232  		if d.expected != exists {
   233  			t.Errorf("Test %d failed. Expected %t got %t", i, d.expected, exists)
   234  		}
   235  	}
   236  }
   237  
   238  func TestIsDir(t *testing.T) {
   239  	type test struct {
   240  		input    string
   241  		expected bool
   242  	}
   243  	data := []test{
   244  		{"./", true},
   245  		{"/", true},
   246  		{"./this-directory-does-not-existi", false},
   247  		{"/this-absolute-directory/does-not-exist", false},
   248  	}
   249  
   250  	for i, d := range data {
   251  
   252  		exists, _ := IsDir(d.input, new(afero.OsFs))
   253  		if d.expected != exists {
   254  			t.Errorf("Test %d failed. Expected %t got %t", i, d.expected, exists)
   255  		}
   256  	}
   257  }
   258  
   259  func createZeroSizedFileInTempDir() (*os.File, error) {
   260  	filePrefix := "_path_test_"
   261  	f, e := os.CreateTemp("", filePrefix) // dir is os.TempDir()
   262  	if e != nil {
   263  		// if there was an error no file was created.
   264  		// => no requirement to delete the file
   265  		return nil, e
   266  	}
   267  	return f, nil
   268  }
   269  
   270  func createNonZeroSizedFileInTempDir() (*os.File, error) {
   271  	f, err := createZeroSizedFileInTempDir()
   272  	if err != nil {
   273  		// no file ??
   274  		return nil, err
   275  	}
   276  	byteString := []byte("byteString")
   277  	err = os.WriteFile(f.Name(), byteString, 0644)
   278  	if err != nil {
   279  		// delete the file
   280  		deleteFileInTempDir(f)
   281  		return nil, err
   282  	}
   283  	return f, nil
   284  }
   285  
   286  func deleteFileInTempDir(f *os.File) {
   287  	_ = os.Remove(f.Name())
   288  }
   289  
   290  func TestExists(t *testing.T) {
   291  	zeroSizedFile, _ := createZeroSizedFileInTempDir()
   292  	defer deleteFileInTempDir(zeroSizedFile)
   293  	nonZeroSizedFile, _ := createNonZeroSizedFileInTempDir()
   294  	defer deleteFileInTempDir(nonZeroSizedFile)
   295  	emptyDirectory := t.TempDir()
   296  	nonExistentFile := os.TempDir() + "/this-file-does-not-exist.txt"
   297  	nonExistentDir := os.TempDir() + "/this/directory/does/not/exist/"
   298  
   299  	type test struct {
   300  		input          string
   301  		expectedResult bool
   302  		expectedErr    error
   303  	}
   304  
   305  	data := []test{
   306  		{zeroSizedFile.Name(), true, nil},
   307  		{nonZeroSizedFile.Name(), true, nil},
   308  		{emptyDirectory, true, nil},
   309  		{nonExistentFile, false, nil},
   310  		{nonExistentDir, false, nil},
   311  	}
   312  	for i, d := range data {
   313  		exists, err := Exists(d.input, new(afero.OsFs))
   314  		if d.expectedResult != exists {
   315  			t.Errorf("Test %d failed. Expected result %t got %t", i, d.expectedResult, exists)
   316  		}
   317  		if d.expectedErr != err {
   318  			t.Errorf("Test %d failed. Expected %q got %q", i, d.expectedErr, err)
   319  		}
   320  	}
   321  }
   322  
   323  func TestAbsPathify(t *testing.T) {
   324  	type test struct {
   325  		inPath, workingDir, expected string
   326  	}
   327  	data := []test{
   328  		{os.TempDir(), filepath.FromSlash("/work"), filepath.Clean(os.TempDir())}, // TempDir has trailing slash
   329  		{"dir", filepath.FromSlash("/work"), filepath.FromSlash("/work/dir")},
   330  	}
   331  
   332  	windowsData := []test{
   333  		{"c:\\banana\\..\\dir", "c:\\foo", "c:\\dir"},
   334  		{"\\dir", "c:\\foo", "c:\\foo\\dir"},
   335  		{"c:\\", "c:\\foo", "c:\\"},
   336  	}
   337  
   338  	unixData := []test{
   339  		{"/banana/../dir/", "/work", "/dir"},
   340  	}
   341  
   342  	for i, d := range data {
   343  		// todo see comment in AbsPathify
   344  		ps := newTestDefaultPathSpec("workingDir", d.workingDir)
   345  
   346  		expected := ps.AbsPathify(d.inPath)
   347  		if d.expected != expected {
   348  			t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected)
   349  		}
   350  	}
   351  	t.Logf("Running platform specific path tests for %s", runtime.GOOS)
   352  	if runtime.GOOS == "windows" {
   353  		for i, d := range windowsData {
   354  			ps := newTestDefaultPathSpec("workingDir", d.workingDir)
   355  
   356  			expected := ps.AbsPathify(d.inPath)
   357  			if d.expected != expected {
   358  				t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected)
   359  			}
   360  		}
   361  	} else {
   362  		for i, d := range unixData {
   363  			ps := newTestDefaultPathSpec("workingDir", d.workingDir)
   364  
   365  			expected := ps.AbsPathify(d.inPath)
   366  			if d.expected != expected {
   367  				t.Errorf("Test %d failed. Expected %q but got %q", i, d.expected, expected)
   368  			}
   369  		}
   370  	}
   371  }
   372  
   373  func TestExtractAndGroupRootPaths(t *testing.T) {
   374  	in := []string{
   375  		filepath.FromSlash("/a/b/c/d"),
   376  		filepath.FromSlash("/a/b/c/e"),
   377  		filepath.FromSlash("/a/b/e/f"),
   378  		filepath.FromSlash("/a/b"),
   379  		filepath.FromSlash("/a/b/c/b/g"),
   380  		filepath.FromSlash("/c/d/e"),
   381  	}
   382  
   383  	inCopy := make([]string, len(in))
   384  	copy(inCopy, in)
   385  
   386  	result := ExtractAndGroupRootPaths(in)
   387  
   388  	c := qt.New(t)
   389  	c.Assert(fmt.Sprint(result), qt.Equals, filepath.FromSlash("[/a/b/{c,e} /c/d/e]"))
   390  
   391  	// Make sure the original is preserved
   392  	c.Assert(in, qt.DeepEquals, inCopy)
   393  }
   394  
   395  func TestExtractRootPaths(t *testing.T) {
   396  	tests := []struct {
   397  		input    []string
   398  		expected []string
   399  	}{{
   400  		[]string{
   401  			filepath.FromSlash("a/b"), filepath.FromSlash("a/b/c/"), "b",
   402  			filepath.FromSlash("/c/d"), filepath.FromSlash("d/"), filepath.FromSlash("//e//"),
   403  		},
   404  		[]string{"a", "a", "b", "c", "d", "e"},
   405  	}}
   406  
   407  	for _, test := range tests {
   408  		output := ExtractRootPaths(test.input)
   409  		if !reflect.DeepEqual(output, test.expected) {
   410  			t.Errorf("Expected %#v, got %#v\n", test.expected, output)
   411  		}
   412  	}
   413  }
   414  
   415  func TestFindCWD(t *testing.T) {
   416  	type test struct {
   417  		expectedDir string
   418  		expectedErr error
   419  	}
   420  
   421  	// cwd, _ := os.Getwd()
   422  	data := []test{
   423  		//{cwd, nil},
   424  		// Commenting this out. It doesn't work properly.
   425  		// There's a good reason why we don't use os.Getwd(), it doesn't actually work the way we want it to.
   426  		// I really don't know a better way to test this function. - SPF 2014.11.04
   427  	}
   428  	for i, d := range data {
   429  		dir, err := FindCWD()
   430  		if d.expectedDir != dir {
   431  			t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedDir, dir)
   432  		}
   433  		if d.expectedErr != err {
   434  			t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, err)
   435  		}
   436  	}
   437  }
   438  
   439  func TestSafeWriteToDisk(t *testing.T) {
   440  	emptyFile, _ := createZeroSizedFileInTempDir()
   441  	defer deleteFileInTempDir(emptyFile)
   442  	tmpDir := t.TempDir()
   443  
   444  	randomString := "This is a random string!"
   445  	reader := strings.NewReader(randomString)
   446  
   447  	fileExists := fmt.Errorf("%v already exists", emptyFile.Name())
   448  
   449  	type test struct {
   450  		filename    string
   451  		expectedErr error
   452  	}
   453  
   454  	now := time.Now().Unix()
   455  	nowStr := strconv.FormatInt(now, 10)
   456  	data := []test{
   457  		{emptyFile.Name(), fileExists},
   458  		{tmpDir + "/" + nowStr, nil},
   459  	}
   460  
   461  	for i, d := range data {
   462  		e := SafeWriteToDisk(d.filename, reader, new(afero.OsFs))
   463  		if d.expectedErr != nil {
   464  			if d.expectedErr.Error() != e.Error() {
   465  				t.Errorf("Test %d failed. Expected error %q but got %q", i, d.expectedErr.Error(), e.Error())
   466  			}
   467  		} else {
   468  			if d.expectedErr != e {
   469  				t.Errorf("Test %d failed. Expected %q but got %q", i, d.expectedErr, e)
   470  			}
   471  			contents, _ := os.ReadFile(d.filename)
   472  			if randomString != string(contents) {
   473  				t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents))
   474  			}
   475  		}
   476  		reader.Seek(0, 0)
   477  	}
   478  }
   479  
   480  func TestWriteToDisk(t *testing.T) {
   481  	emptyFile, _ := createZeroSizedFileInTempDir()
   482  	defer deleteFileInTempDir(emptyFile)
   483  	tmpDir := t.TempDir()
   484  
   485  	randomString := "This is a random string!"
   486  	reader := strings.NewReader(randomString)
   487  
   488  	type test struct {
   489  		filename    string
   490  		expectedErr error
   491  	}
   492  
   493  	now := time.Now().Unix()
   494  	nowStr := strconv.FormatInt(now, 10)
   495  	data := []test{
   496  		{emptyFile.Name(), nil},
   497  		{tmpDir + "/" + nowStr, nil},
   498  	}
   499  
   500  	for i, d := range data {
   501  		e := WriteToDisk(d.filename, reader, new(afero.OsFs))
   502  		if d.expectedErr != e {
   503  			t.Errorf("Test %d failed. WriteToDisk Error Expected %q but got %q", i, d.expectedErr, e)
   504  		}
   505  		contents, e := os.ReadFile(d.filename)
   506  		if e != nil {
   507  			t.Errorf("Test %d failed. Could not read file %s. Reason: %s\n", i, d.filename, e)
   508  		}
   509  		if randomString != string(contents) {
   510  			t.Errorf("Test %d failed. Expected contents %q but got %q", i, randomString, string(contents))
   511  		}
   512  		reader.Seek(0, 0)
   513  	}
   514  }
   515  
   516  func TestGetTempDir(t *testing.T) {
   517  	dir := os.TempDir()
   518  	if FilePathSeparator != dir[len(dir)-1:] {
   519  		dir = dir + FilePathSeparator
   520  	}
   521  	testDir := "hugoTestFolder" + FilePathSeparator
   522  	tests := []struct {
   523  		input    string
   524  		expected string
   525  	}{
   526  		{"", dir},
   527  		{testDir + "  Foo bar  ", dir + testDir + "  Foo bar  " + FilePathSeparator},
   528  		{testDir + "Foo.Bar/foo_Bar-Foo", dir + testDir + "Foo.Bar/foo_Bar-Foo" + FilePathSeparator},
   529  		{testDir + "fOO,bar:foo%bAR", dir + testDir + "fOObarfoo%bAR" + FilePathSeparator},
   530  		{testDir + "fOO,bar:foobAR", dir + testDir + "fOObarfoobAR" + FilePathSeparator},
   531  		{testDir + "FOo/BaR.html", dir + testDir + "FOo/BaR.html" + FilePathSeparator},
   532  		{testDir + "трям/трям", dir + testDir + "трям/трям" + FilePathSeparator},
   533  		{testDir + "은행", dir + testDir + "은행" + FilePathSeparator},
   534  		{testDir + "Банковский кассир", dir + testDir + "Банковский кассир" + FilePathSeparator},
   535  	}
   536  
   537  	for _, test := range tests {
   538  		output := GetTempDir(test.input, new(afero.MemMapFs))
   539  		if output != test.expected {
   540  			t.Errorf("Expected %#v, got %#v\n", test.expected, output)
   541  		}
   542  	}
   543  }