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 }