go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/testing/testfs/build.go (about)

     1  // Copyright 2017 The LUCI Authors.
     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 testfs
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"sort"
    21  	"strings"
    22  
    23  	"go.chromium.org/luci/common/errors"
    24  	"go.chromium.org/luci/common/system/filesystem"
    25  )
    26  
    27  // Build constructs a filesystem hierarchy given a layout.
    28  //
    29  // The layouts keys should be ToSlash-style file paths. Its values should be the
    30  // content that is written at those paths. Intermediate directories will be
    31  // automatically created.
    32  //
    33  // To create a directory, end its path with a "/". In this case, the content
    34  // will be ignored.
    35  func Build(base string, layout map[string]string) error {
    36  	keys := make([]string, 0, len(layout))
    37  	for k := range layout {
    38  		keys = append(keys, k)
    39  	}
    40  	sort.Strings(keys)
    41  
    42  	for _, path := range keys {
    43  		makeDir := strings.HasSuffix(path, "/")
    44  		content := layout[path]
    45  
    46  		// Normalize "path" to the current OS.
    47  		path = filepath.Join(base, filepath.FromSlash(path))
    48  
    49  		if makeDir {
    50  			// Make a directory.
    51  			if err := filesystem.MakeDirs(path); err != nil {
    52  				return err
    53  			}
    54  		} else {
    55  			// Make a file.
    56  
    57  			if err := filesystem.MakeDirs(filepath.Dir(path)); err != nil {
    58  				return err
    59  			}
    60  			if err := os.WriteFile(path, []byte(content), 0644); err != nil {
    61  				return err
    62  			}
    63  		}
    64  	}
    65  	return nil
    66  }
    67  
    68  // Collect constructs layout from a given directory.
    69  //
    70  // This function does reverse of Build.
    71  // Content of empty directory is represented as "" with "/" suffix.
    72  // But this does not work if there are symlink entries under |base| dir.
    73  func Collect(base string) (map[string]string, error) {
    74  	layout := make(map[string]string)
    75  
    76  	base = strings.TrimSuffix(base, "/")
    77  
    78  	if err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error {
    79  		if err != nil {
    80  			return err
    81  		}
    82  
    83  		if path == base {
    84  			return nil
    85  		}
    86  		path = path[len(base)+1:]
    87  
    88  		// Remove non-empty directory.
    89  		delete(layout, filepath.ToSlash(filepath.Dir(path))+"/")
    90  
    91  		if info.IsDir() {
    92  			layout[filepath.ToSlash(path)+"/"] = ""
    93  			return nil
    94  		}
    95  
    96  		if !info.Mode().IsRegular() {
    97  			return errors.Reason("unknown file info is detected for %s: %v", path, info).Err()
    98  		}
    99  
   100  		buf, err := os.ReadFile(filepath.Join(base, path))
   101  		if err != nil {
   102  			return errors.Annotate(err, "failed to read: %s", filepath.Join(base, path)).Err()
   103  		}
   104  
   105  		layout[filepath.ToSlash(path)] = string(buf)
   106  		return nil
   107  	}); err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return layout, nil
   112  }