github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugofs/walk_test.go (about)

     1  // Copyright 2019 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 hugofs
    15  
    16  import (
    17  	"context"
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/pkg/errors"
    26  
    27  	"github.com/gohugoio/hugo/common/para"
    28  	"github.com/gohugoio/hugo/htesting"
    29  
    30  	"github.com/spf13/afero"
    31  
    32  	qt "github.com/frankban/quicktest"
    33  )
    34  
    35  func TestWalk(t *testing.T) {
    36  	c := qt.New(t)
    37  
    38  	fs := NewBaseFileDecorator(afero.NewMemMapFs())
    39  
    40  	afero.WriteFile(fs, "b.txt", []byte("content"), 0777)
    41  	afero.WriteFile(fs, "c.txt", []byte("content"), 0777)
    42  	afero.WriteFile(fs, "a.txt", []byte("content"), 0777)
    43  
    44  	names, err := collectFilenames(fs, "", "")
    45  
    46  	c.Assert(err, qt.IsNil)
    47  	c.Assert(names, qt.DeepEquals, []string{"a.txt", "b.txt", "c.txt"})
    48  }
    49  
    50  func TestWalkRootMappingFs(t *testing.T) {
    51  	c := qt.New(t)
    52  
    53  	prepare := func(c *qt.C) afero.Fs {
    54  		fs := NewBaseFileDecorator(afero.NewMemMapFs())
    55  
    56  		testfile := "test.txt"
    57  
    58  		c.Assert(afero.WriteFile(fs, filepath.Join("a/b", testfile), []byte("some content"), 0755), qt.IsNil)
    59  		c.Assert(afero.WriteFile(fs, filepath.Join("c/d", testfile), []byte("some content"), 0755), qt.IsNil)
    60  		c.Assert(afero.WriteFile(fs, filepath.Join("e/f", testfile), []byte("some content"), 0755), qt.IsNil)
    61  
    62  		rm := []RootMapping{
    63  			{
    64  				From: "static/b",
    65  				To:   "e/f",
    66  			},
    67  			{
    68  				From: "static/a",
    69  				To:   "c/d",
    70  			},
    71  
    72  			{
    73  				From: "static/c",
    74  				To:   "a/b",
    75  			},
    76  		}
    77  
    78  		rfs, err := NewRootMappingFs(fs, rm...)
    79  		c.Assert(err, qt.IsNil)
    80  		return afero.NewBasePathFs(rfs, "static")
    81  	}
    82  
    83  	c.Run("Basic", func(c *qt.C) {
    84  		bfs := prepare(c)
    85  
    86  		names, err := collectFilenames(bfs, "", "")
    87  
    88  		c.Assert(err, qt.IsNil)
    89  		c.Assert(names, qt.DeepEquals, []string{"a/test.txt", "b/test.txt", "c/test.txt"})
    90  	})
    91  
    92  	c.Run("Para", func(c *qt.C) {
    93  		bfs := prepare(c)
    94  
    95  		p := para.New(4)
    96  		r, _ := p.Start(context.Background())
    97  
    98  		for i := 0; i < 8; i++ {
    99  			r.Run(func() error {
   100  				_, err := collectFilenames(bfs, "", "")
   101  				if err != nil {
   102  					return err
   103  				}
   104  				fi, err := bfs.Stat("b/test.txt")
   105  				if err != nil {
   106  					return err
   107  				}
   108  				meta := fi.(FileMetaInfo).Meta()
   109  				if meta.Filename == "" {
   110  					return errors.New("fail")
   111  				}
   112  				return nil
   113  			})
   114  		}
   115  
   116  		c.Assert(r.Wait(), qt.IsNil)
   117  	})
   118  }
   119  
   120  func skipSymlink() bool {
   121  	if runtime.GOOS != "windows" {
   122  		return false
   123  	}
   124  	if os.Getenv("GITHUB_ACTION") != "" {
   125  		// TODO(bep) figure out why this fails on GitHub Actions.
   126  		return true
   127  	}
   128  	return os.Getenv("CI") == ""
   129  }
   130  
   131  func TestWalkSymbolicLink(t *testing.T) {
   132  	if skipSymlink() {
   133  		t.Skip("Skip; os.Symlink needs administrator rights on Windows")
   134  	}
   135  	c := qt.New(t)
   136  	workDir, clean, err := htesting.CreateTempDir(Os, "hugo-walk-sym")
   137  	c.Assert(err, qt.IsNil)
   138  	defer clean()
   139  	wd, _ := os.Getwd()
   140  	defer func() {
   141  		os.Chdir(wd)
   142  	}()
   143  
   144  	fs := NewBaseFileDecorator(Os)
   145  
   146  	blogDir := filepath.Join(workDir, "blog")
   147  	docsDir := filepath.Join(workDir, "docs")
   148  	blogReal := filepath.Join(blogDir, "real")
   149  	blogRealSub := filepath.Join(blogReal, "sub")
   150  	c.Assert(os.MkdirAll(blogRealSub, 0777), qt.IsNil)
   151  	c.Assert(os.MkdirAll(docsDir, 0777), qt.IsNil)
   152  	afero.WriteFile(fs, filepath.Join(blogRealSub, "a.txt"), []byte("content"), 0777)
   153  	afero.WriteFile(fs, filepath.Join(docsDir, "b.txt"), []byte("content"), 0777)
   154  
   155  	os.Chdir(blogDir)
   156  	c.Assert(os.Symlink("real", "symlinked"), qt.IsNil)
   157  	os.Chdir(blogReal)
   158  	c.Assert(os.Symlink("../real", "cyclic"), qt.IsNil)
   159  	os.Chdir(docsDir)
   160  	c.Assert(os.Symlink("../blog/real/cyclic", "docsreal"), qt.IsNil)
   161  
   162  	t.Run("OS Fs", func(t *testing.T) {
   163  		c := qt.New(t)
   164  
   165  		names, err := collectFilenames(fs, workDir, workDir)
   166  		c.Assert(err, qt.IsNil)
   167  
   168  		c.Assert(names, qt.DeepEquals, []string{"blog/real/sub/a.txt", "blog/symlinked/sub/a.txt", "docs/b.txt"})
   169  	})
   170  
   171  	t.Run("BasePath Fs", func(t *testing.T) {
   172  		c := qt.New(t)
   173  
   174  		docsFs := afero.NewBasePathFs(fs, docsDir)
   175  
   176  		names, err := collectFilenames(docsFs, "", "")
   177  		c.Assert(err, qt.IsNil)
   178  
   179  		// Note: the docsreal folder is considered cyclic when walking from the root, but this works.
   180  		c.Assert(names, qt.DeepEquals, []string{"b.txt", "docsreal/sub/a.txt"})
   181  	})
   182  }
   183  
   184  func collectFilenames(fs afero.Fs, base, root string) ([]string, error) {
   185  	var names []string
   186  
   187  	walkFn := func(path string, info FileMetaInfo, err error) error {
   188  		if err != nil {
   189  			return err
   190  		}
   191  
   192  		if info.IsDir() {
   193  			return nil
   194  		}
   195  
   196  		filename := info.Meta().Path
   197  		filename = filepath.ToSlash(filename)
   198  
   199  		names = append(names, filename)
   200  
   201  		return nil
   202  	}
   203  
   204  	w := NewWalkway(WalkwayConfig{Fs: fs, BasePath: base, Root: root, WalkFn: walkFn})
   205  
   206  	err := w.Walk()
   207  
   208  	return names, err
   209  }
   210  
   211  func collectFileinfos(fs afero.Fs, base, root string) ([]FileMetaInfo, error) {
   212  	var fis []FileMetaInfo
   213  
   214  	walkFn := func(path string, info FileMetaInfo, err error) error {
   215  		if err != nil {
   216  			return err
   217  		}
   218  
   219  		fis = append(fis, info)
   220  
   221  		return nil
   222  	}
   223  
   224  	w := NewWalkway(WalkwayConfig{Fs: fs, BasePath: base, Root: root, WalkFn: walkFn})
   225  
   226  	err := w.Walk()
   227  
   228  	return fis, err
   229  }
   230  
   231  func BenchmarkWalk(b *testing.B) {
   232  	c := qt.New(b)
   233  	fs := NewBaseFileDecorator(afero.NewMemMapFs())
   234  
   235  	writeFiles := func(dir string, numfiles int) {
   236  		for i := 0; i < numfiles; i++ {
   237  			filename := filepath.Join(dir, fmt.Sprintf("file%d.txt", i))
   238  			c.Assert(afero.WriteFile(fs, filename, []byte("content"), 0777), qt.IsNil)
   239  		}
   240  	}
   241  
   242  	const numFilesPerDir = 20
   243  
   244  	writeFiles("root", numFilesPerDir)
   245  	writeFiles("root/l1_1", numFilesPerDir)
   246  	writeFiles("root/l1_1/l2_1", numFilesPerDir)
   247  	writeFiles("root/l1_1/l2_2", numFilesPerDir)
   248  	writeFiles("root/l1_2", numFilesPerDir)
   249  	writeFiles("root/l1_2/l2_1", numFilesPerDir)
   250  	writeFiles("root/l1_3", numFilesPerDir)
   251  
   252  	walkFn := func(path string, info FileMetaInfo, err error) error {
   253  		if err != nil {
   254  			return err
   255  		}
   256  		if info.IsDir() {
   257  			return nil
   258  		}
   259  
   260  		filename := info.Meta().Filename
   261  		if !strings.HasPrefix(filename, "root") {
   262  			return errors.New(filename)
   263  		}
   264  
   265  		return nil
   266  	}
   267  
   268  	b.ResetTimer()
   269  	for i := 0; i < b.N; i++ {
   270  		w := NewWalkway(WalkwayConfig{Fs: fs, Root: "root", WalkFn: walkFn})
   271  
   272  		if err := w.Walk(); err != nil {
   273  			b.Fatal(err)
   274  		}
   275  	}
   276  }