github.com/bazelbuild/rules_webtesting@v0.2.0/go/metadata/metadata.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 provides a struct for storing browser metadata. 16 package metadata 17 18 import ( 19 "encoding/json" 20 "fmt" 21 "io/ioutil" 22 "os" 23 24 "github.com/bazelbuild/rules_webtesting/go/metadata/capabilities" 25 ) 26 27 // Values for Metadata.RecordVideo. 28 const ( 29 RecordNever = "never" 30 RecordFailed = "failed" 31 RecordAlways = "always" 32 ) 33 34 // Metadata provides necessary metadata for launching a browser. 35 type Metadata struct { 36 // The Capabilities that should be used for this browser. 37 Capabilities map[string]interface{} `json:"capabilities,omitempty"` 38 // The Environment that web test launcher should use to to launch the browser. 39 Environment string `json:"environment,omitempty"` 40 // Label for the web_test rule. 41 Label string `json:"label,omitempty"` 42 // Browser label set in the web_test rule. 43 BrowserLabel string `json:"browserLabel,omitempty"` 44 // Test label set in the web_test rule. 45 TestLabel string `json:"testLabel,omitempty"` 46 // Config label set in the web_test rule. 47 ConfigLabel string `json:"configLabel,omitempty"` 48 // Port to connect debugger to. If 0, debugger will not be started. 49 DebuggerPort int `json:"debuggerPort,omitempty"` 50 // A list of WebTestFiles with named files in them. 51 WebTestFiles []*WebTestFiles `json:"webTestFiles,omitempty"` 52 // An object for any additional metadata fields on this object. 53 Extension `json:"extension,omitempty"` 54 } 55 56 // Extension is an interface for adding additional fields that will be parsed as part of the metadata. 57 type Extension interface { 58 // Merge merges this extension data with another set of Extension data. It should not mutate either 59 // Extension object, but it is allowed to return one of the Extension objects unchanged if needed. 60 // In general values in other should take precedence over values in this object. 61 Merge(other Extension) (Extension, error) 62 // Normalize normalizes and validate the extension data. 63 Normalize() error 64 } 65 66 // Merge takes two Metadata objects and merges them into a new Metadata object. 67 func Merge(m1, m2 *Metadata) (*Metadata, error) { 68 capabilities := capabilities.Merge(m1.Capabilities, m2.Capabilities) 69 70 environment := m1.Environment 71 if m2.Environment != "" { 72 environment = m2.Environment 73 } 74 75 label := m1.Label 76 if m2.Label != "" { 77 label = m2.Label 78 } 79 80 browserLabel := m1.BrowserLabel 81 if m2.BrowserLabel != "" { 82 browserLabel = m2.BrowserLabel 83 } 84 85 testLabel := m1.TestLabel 86 if m2.TestLabel != "" { 87 testLabel = m2.TestLabel 88 } 89 90 configLabel := m1.ConfigLabel 91 if m2.ConfigLabel != "" { 92 configLabel = m2.ConfigLabel 93 } 94 95 debuggerPort := m1.DebuggerPort 96 if m2.DebuggerPort != 0 { 97 debuggerPort = m2.DebuggerPort 98 } 99 100 var webTestFiles []*WebTestFiles 101 webTestFiles = append(webTestFiles, m1.WebTestFiles...) 102 webTestFiles = append(webTestFiles, m2.WebTestFiles...) 103 104 webTestFiles, err := normalizeWebTestFiles(webTestFiles) 105 if err != nil { 106 return nil, err 107 } 108 109 extension := m1.Extension 110 if extension == nil { 111 extension = m2.Extension 112 } else if m2.Extension != nil { 113 e, err := extension.Merge(m2.Extension) 114 if err != nil { 115 return nil, err 116 } 117 extension = e 118 } 119 120 return &Metadata{ 121 Capabilities: capabilities, 122 Environment: environment, 123 Label: label, 124 BrowserLabel: browserLabel, 125 TestLabel: testLabel, 126 ConfigLabel: configLabel, 127 DebuggerPort: debuggerPort, 128 WebTestFiles: webTestFiles, 129 Extension: extension, 130 }, nil 131 } 132 133 // FromFile reads a Metadata object from a json file. 134 func FromFile(filename string, ext Extension) (*Metadata, error) { 135 bytes, err := ioutil.ReadFile(filename) 136 if err != nil { 137 return nil, err 138 } 139 140 return FromBytes(bytes, ext) 141 } 142 143 // FromBytes reads a Metadata object from a byte array. 144 func FromBytes(bytes []byte, ext Extension) (*Metadata, error) { 145 if ext == nil { 146 ext = &extension{} 147 } 148 metadata := &Metadata{Extension: ext} 149 150 if err := json.Unmarshal(bytes, metadata); err != nil { 151 return nil, err 152 } 153 webTestFiles, err := normalizeWebTestFiles(metadata.WebTestFiles) 154 if err != nil { 155 return nil, err 156 } 157 metadata.WebTestFiles = webTestFiles 158 159 if metadata.Extension != nil { 160 if err := metadata.Extension.Normalize(); err != nil { 161 return nil, err 162 } 163 } 164 165 return metadata, nil 166 } 167 168 // ToFile writes m to filename as json. 169 func (m *Metadata) ToFile(filename string) error { 170 bytes, err := m.ToBytes() 171 if err != nil { 172 return err 173 } 174 return ioutil.WriteFile(filename, bytes, 0644) 175 } 176 177 // ToBytes serializes metadata. 178 func (m *Metadata) ToBytes() ([]byte, error) { 179 return json.MarshalIndent(m, "", " ") 180 } 181 182 // GetFilePath returns the path to a file specified by web_test_archive, 183 // web_test_named_executable, or web_test_named_file. 184 func (m *Metadata) GetFilePath(name string) (string, error) { 185 for _, a := range m.WebTestFiles { 186 filename, err := a.getFilePath(name, m) 187 if err != nil { 188 return "", err 189 } 190 if filename != "" { 191 return filename, nil 192 } 193 } 194 return "", fmt.Errorf("no named file %q", name) 195 } 196 197 // Resolver returns a Resolver that processes ENV, FILE, and METADATA prefixed 198 // capabilities variables. 199 func (m *Metadata) Resolver() capabilities.Resolver { 200 metadataResolver := capabilities.MapResolver("METADATA", map[string]string{ 201 "LABEL": m.Label, 202 "TEST_LABEL": m.TestLabel, 203 "BROWSER_LABEL": m.BrowserLabel, 204 "CONFIG_LABEL": m.ConfigLabel, 205 "ENVIRONMENT": m.Environment, 206 }) 207 208 return func(prefix, name string) (string, error) { 209 switch prefix { 210 case "ENV": 211 v, ok := os.LookupEnv(name) 212 if !ok { 213 return "", fmt.Errorf("environment variable %q is not defined", name) 214 } 215 return v, nil 216 case "FILE": 217 return m.GetFilePath(name) 218 default: 219 return metadataResolver(prefix, name) 220 } 221 } 222 } 223 224 type extension map[string]interface{} 225 226 func (e extension) Merge(other Extension) (Extension, error) { 227 if other == nil { 228 return e, nil 229 } 230 if e == nil || len(e) == 0 { 231 return other, nil 232 } 233 o, ok := other.(extension) 234 if !ok || len(o) == 0 { 235 return e, nil 236 } 237 ext := extension{} 238 for k, v := range e { 239 ext[k] = v 240 } 241 for k, v := range o { 242 ext[k] = v 243 } 244 return ext, nil 245 } 246 247 func (e extension) Normalize() error { 248 return nil 249 }