github.com/bazelbuild/rules_webtesting@v0.2.0/go/metadata/web_test_files.go (about) 1 // Copyright 2016 Google Inc. 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 metadata 16 17 import ( 18 "fmt" 19 "os" 20 "os/exec" 21 "path/filepath" 22 "sync" 23 24 "github.com/bazelbuild/rules_webtesting/go/bazel" 25 ) 26 27 // WebTestFiles defines a set of namedFiles located either in the runfiles directory or 28 // in an archive file located in the runfiles directory of the test. 29 type WebTestFiles struct { 30 // ArchiveFile is optional path to an archive file (.zip, .tar.gz, .tgz, .tar.bz2, .tbz2, .tar.Z) 31 // file. If present, paths in NamedFiles are paths in the archive. If absent, paths in NamedFiles 32 // are relative to the runfiles root. The archive will only be extracted if getFilePath is called 33 // at least once with a name defined in NamedFiles. If so, the entire archive will be extracted 34 // into subdirectory located test tmpdir. 35 ArchiveFile string `json:"archiveFile,omitempty"` 36 // StripPrefix is an optional prefix that will be stripped when an archive is extracted. 37 StripPrefix string `json:"stripPrefix,omitempty"` 38 // NamedFiles is a map of names to file paths. These file paths are relative to the runfiles 39 // root if ArchiveFile is absent, otherwise they are paths inside the archive referred to by 40 // ArchiveFile. The names are used by other parts of Web Test Launcher to refer to needed 41 // resources. For example, if your environment needs to know where a chromedriver executable is 42 // located, then there could be a name "CHROMEDRIVER" that refers to the path to the chromedriver 43 // executable, and the part of you environment that needs to use the chromedriver executable 44 // can call md.GetFilePath("CHROMEDRIVER") (where md is a *metadata.Metadata object) which will 45 // search through all NamedFiles of all WebTestFiles structs in md to find that key and return 46 // the path to the corresponding file (extracting an archive if necessary). 47 NamedFiles map[string]string `json:"namedFiles"` 48 49 // The mu field protects access to the extractedPath field. 50 mu sync.Mutex 51 // The extractedPath field refers to the location where this archive has been extracted to, if 52 // has been extracted. 53 extractedPath string 54 } 55 56 func normalizeWebTestFiles(in []*WebTestFiles) ([]*WebTestFiles, error) { 57 merged := map[string]*WebTestFiles{} 58 59 for _, a := range in { 60 // skip entries with no named files. 61 if len(a.NamedFiles) == 0 { 62 continue 63 } 64 if b := merged[a.ArchiveFile]; b != nil { 65 m, err := mergeWebTestFiles(a, b) 66 if err != nil { 67 return nil, err 68 } 69 merged[m.ArchiveFile] = m 70 } else { 71 merged[a.ArchiveFile] = a 72 } 73 } 74 75 names := map[string]bool{} 76 var result []*WebTestFiles 77 for _, m := range merged { 78 for name := range m.NamedFiles { 79 if names[name] { 80 return nil, fmt.Errorf("name %q exists in multiple WebTestFiles", name) 81 } 82 names[name] = true 83 } 84 result = append(result, m) 85 } 86 return result, nil 87 } 88 89 func mergeWebTestFiles(a1, a2 *WebTestFiles) (*WebTestFiles, error) { 90 if a1.ArchiveFile != a2.ArchiveFile { 91 return nil, fmt.Errorf("expected paths (%q, %q) to be equal", a1.ArchiveFile, a2.ArchiveFile) 92 } 93 nf, err := mergeNamedFiles(a1.NamedFiles, a2.NamedFiles) 94 if err != nil { 95 return nil, err 96 } 97 return &WebTestFiles{ 98 ArchiveFile: a1.ArchiveFile, 99 NamedFiles: nf, 100 }, nil 101 } 102 103 func mergeNamedFiles(n1, n2 map[string]string) (map[string]string, error) { 104 result := map[string]string{} 105 106 for k, v := range n1 { 107 result[k] = v 108 } 109 110 for k, v2 := range n2 { 111 if v1, ok := result[k]; ok && v1 != v2 { 112 return nil, fmt.Errorf("key %q exists in both NamedFiles with different values", k) 113 } 114 result[k] = v2 115 } 116 return result, nil 117 } 118 119 func (w *WebTestFiles) getFilePath(name string, m *Metadata) (string, error) { 120 filename, ok := w.NamedFiles[name] 121 if !ok { 122 return "", nil 123 } 124 125 if w.ArchiveFile == "" { 126 return bazel.Runfile(filename) 127 } 128 129 if err := w.extract(m); err != nil { 130 return "", err 131 } 132 133 path := filepath.Join(w.extractedPath, filename) 134 if _, err := os.Stat(path); err != nil { 135 return "", err 136 } 137 return path, nil 138 } 139 140 func (w *WebTestFiles) extract(m *Metadata) error { 141 w.mu.Lock() 142 defer w.mu.Unlock() 143 if w.extractedPath != "" { 144 return nil 145 } 146 147 extractor, err := m.GetFilePath("EXTRACT_EXE") 148 if err != nil { 149 return err 150 } 151 152 filename, err := bazel.Runfile(w.ArchiveFile) 153 if err != nil { 154 return err 155 } 156 157 extractPath, err := bazel.NewTmpDir(filepath.Base(filename)) 158 if err != nil { 159 return err 160 } 161 162 c := exec.Command(extractor, filename, extractPath, w.StripPrefix) 163 164 if err := c.Run(); err != nil { 165 return err 166 } 167 168 w.extractedPath = extractPath 169 return nil 170 }