1// Copyright 2018 The Bazel 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// 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 15package liteparse 16 17import ( 18 "context" 19 "fmt" 20 "io/ioutil" 21 "log" 22 "os" 23 "path/filepath" 24 "reflect" 25 "sort" 26 "testing" 27 28 "src/common/golang/runfilelocation" 29 rdpb "src/tools/ak/res/proto/res_data_go_proto" 30 "src/tools/ak/res/res" 31 "src/tools/ak/res/respipe/respipe" 32 "github.com/google/go-cmp/cmp" 33) 34 35const ( 36 testdata = "src/tools/ak/liteparse/testdata/" 37) 38 39func TestPathAsRes(t *testing.T) { 40 tests := []struct { 41 arg string 42 name string 43 ok bool 44 }{ 45 { 46 "foo/bar/res/values/strings.xml", 47 "", 48 false, 49 }, 50 { 51 "foo/bar/res/values-ldpi-v19/strings.xml", 52 "", 53 false, 54 }, 55 { 56 "foo/bar/res/layout-en-US-v19/hello_america.xml", 57 "hello_america", 58 true, 59 }, 60 { 61 "foo/bar/res/xml-land/perfs.xml", 62 "perfs", 63 true, 64 }, 65 { 66 "foo/bar/res/drawable-land/eagle.png", 67 "eagle", 68 true, 69 }, 70 { 71 "foo/bar/res/raw/vid.1080p.png", 72 "vid.1080p", 73 true, 74 }, 75 { 76 "foo/bar/res/drawable-land/circle.9.png", 77 "circle", 78 true, 79 }, 80 } 81 82 for _, tc := range tests { 83 pi, err := res.ParsePath(tc.arg) 84 if err != nil { 85 t.Errorf("res.ParsePath(%q) returns %v unexpectedly", tc.arg, err) 86 continue 87 } 88 rawName, ok := pathAsRes(&pi) 89 if tc.name != rawName || ok != tc.ok { 90 t.Errorf("pathAsRes(%v) got %q, %t want %q, %t", pi, rawName, ok, tc.name, tc.ok) 91 } 92 } 93} 94 95func TestNeedsParse(t *testing.T) { 96 tests := []struct { 97 arg string 98 content string 99 want bool 100 }{ 101 { 102 "foo/bar/res/values/strings.xml", 103 "", 104 true, 105 }, 106 { 107 "foo/bar/res/values-ldpi-v19/strings.xml", 108 "", 109 true, 110 }, 111 { 112 "foo/bar/res/layout-en-US-v19/hello_america.xml", 113 "", 114 true, 115 }, 116 { 117 "foo/bar/res/xml-land/perfs.xml", 118 "", 119 true, 120 }, 121 { 122 "foo/bar/res/drawable-land/eagle.png", 123 "", 124 false, 125 }, 126 { 127 "foo/bar/res/drawable-land/eagle", 128 "", 129 false, 130 }, 131 { 132 "foo/bar/res/drawable-land/eagle_xml", 133 "<?xml version=\"1.0\" encoding=\"utf-8\"?></xml>", 134 true, 135 }, 136 { 137 "foo/bar/res/drawable-land/eagle_txt", 138 "some non-xml file", 139 false, 140 }, 141 } 142 143 for _, tc := range tests { 144 f := createTestFile(tc.arg, tc.content) 145 defer os.Remove(f) 146 pi, err := res.ParsePath(f) 147 if err != nil { 148 t.Errorf("res.ParsePath(%s) returns %v unexpectedly", f, err) 149 continue 150 } 151 got, err := needsParse(&pi) 152 if err != nil { 153 t.Errorf("needsParse(%v) returns %v unexpectedly", pi, err) 154 } 155 if got != tc.want { 156 t.Errorf("needsParse(%v) got %t want %t", pi, got, tc.want) 157 } 158 } 159} 160 161func createTestFile(path, content string) string { 162 dir := filepath.Dir(path) 163 tmpDir, err := ioutil.TempDir("", "test") 164 if err != nil { 165 log.Fatal(err) 166 } 167 err = os.MkdirAll(tmpDir+"/"+dir, os.ModePerm) 168 if err != nil { 169 log.Fatal(err) 170 } 171 f, err := os.Create(tmpDir + "/" + path) 172 if err != nil { 173 log.Fatal(err) 174 } 175 if _, err := f.Write([]byte(content)); err != nil { 176 log.Fatal(err) 177 } 178 if err := f.Close(); err != nil { 179 log.Fatal(err) 180 } 181 return f.Name() 182} 183 184func TestParse(t *testing.T) { 185 ctx, cancel := context.WithCancel(context.Background()) 186 defer cancel() 187 testRes := createResFile("res") 188 piC, pathErrC := respipe.EmitPathInfosDir(ctx, testRes) 189 resC, parseErrC := ResParse(ctx, piC) 190 errC := respipe.MergeErrStreams(ctx, []<-chan error{pathErrC, parseErrC}) 191 var parsedNames []string 192 for resC != nil || errC != nil { 193 select { 194 case r, ok := <-resC: 195 if !ok { 196 resC = nil 197 continue 198 } 199 pn, err := res.ParseName(r.GetName(), res.Type(r.ResourceType)) 200 if err != nil { 201 t.Errorf("res.ParseName(%q, %v) unexpected err: %v", r.GetName(), r.ResourceType, err) 202 fmt.Printf("parsename err: %v\n", err) 203 continue 204 } 205 parsedNames = append(parsedNames, pn.String()) 206 case e, ok := <-errC: 207 if !ok { 208 errC = nil 209 continue 210 } 211 t.Errorf("Unexpected err: %v", e) 212 } 213 } 214 sort.Strings(parsedNames) 215 expectedNames := []string{ 216 "res-auto:attr/bg", 217 "res-auto:attr/size", 218 "res-auto:drawable/foo", 219 "res-auto:id/item1", 220 "res-auto:id/item2", 221 "res-auto:id/large", 222 "res-auto:id/response", 223 "res-auto:id/small", 224 "res-auto:menu/simple", 225 "res-auto:raw/garbage", 226 "res-auto:string/exlusive", 227 "res-auto:string/greeting", 228 "res-auto:string/lonely", 229 "res-auto:string/title", 230 "res-auto:string/title2", 231 "res-auto:string/version", 232 "res-auto:string/version", // yes duplicated (appears in 2 different files, dupes get handled later in the pipeline) 233 "res-auto:styleable/absPieChart", 234 } 235 if !reflect.DeepEqual(parsedNames, expectedNames) { 236 t.Errorf("%s: has these resources: %s expected: %s", testRes, parsedNames, expectedNames) 237 } 238} 239 240func TestParseAll(t *testing.T) { 241 tests := []struct { 242 resfiles []string 243 pkg string 244 want *rdpb.Resources 245 }{ 246 { 247 resfiles: createResfiles([]string{}), 248 pkg: "", 249 want: createResources("", []rdpb.Resource_Type{}, []string{}), 250 }, 251 { 252 resfiles: createResfiles([]string{"mini-1"}), 253 pkg: "example", 254 want: createResources("example", []rdpb.Resource_Type{rdpb.Resource_STRING}, []string{"greeting"}), 255 }, 256 { 257 resfiles: createResfiles([]string{"mini-2"}), 258 pkg: "com.example", 259 want: createResources("com.example", []rdpb.Resource_Type{rdpb.Resource_XML, rdpb.Resource_ID}, []string{"foo", "foobar"}), 260 }, 261 { 262 resfiles: createResfiles([]string{"res/drawable-ldpi/foo.9.png", "res/menu/simple.xml"}), 263 pkg: "com.example", 264 want: createResources("com.example", 265 []rdpb.Resource_Type{rdpb.Resource_DRAWABLE, rdpb.Resource_MENU, rdpb.Resource_ID, rdpb.Resource_ID}, 266 []string{"foo", "simple", "item1", "item2"}), 267 }, 268 } 269 270 for _, tc := range tests { 271 if got := ParseAll(context.Background(), tc.resfiles, tc.pkg); !resourcesEqual(got, tc.want) { 272 t.Errorf("ParseAll(%v, %v) = {%v}, want {%v}", tc.resfiles, tc.pkg, got, tc.want) 273 } 274 } 275} 276 277func TestParseAllContents(t *testing.T) { 278 tests := []struct { 279 resfiles []string 280 pkg string 281 want *rdpb.Resources 282 }{ 283 { 284 resfiles: createResfiles([]string{}), 285 pkg: "", 286 want: createResources("", []rdpb.Resource_Type{}, []string{}), 287 }, 288 { 289 resfiles: createResfiles([]string{"mini-1/res/values/strings.xml"}), 290 pkg: "example", 291 want: createResources("example", []rdpb.Resource_Type{rdpb.Resource_STRING}, []string{"greeting"}), 292 }, 293 { 294 resfiles: createResfiles([]string{"mini-2/res/xml/foo.xml"}), 295 pkg: "com.example", 296 want: createResources("com.example", []rdpb.Resource_Type{rdpb.Resource_XML, rdpb.Resource_ID}, []string{"foo", "foobar"}), 297 }, 298 { 299 resfiles: createResfiles([]string{"res/drawable-ldpi/foo.9.png", "res/menu/simple.xml"}), 300 pkg: "com.example", 301 want: createResources("com.example", 302 []rdpb.Resource_Type{rdpb.Resource_DRAWABLE, rdpb.Resource_MENU, rdpb.Resource_ID, rdpb.Resource_ID}, 303 []string{"foo", "simple", "item1", "item2"}), 304 }, 305 } 306 307 for _, tc := range tests { 308 allContents := getAllContents(t, tc.resfiles) 309 got, err := ParseAllContents(context.Background(), tc.resfiles, allContents, tc.pkg) 310 if err != nil { 311 t.Errorf("ParseAllContents(%v, %v) failed with error %v", tc.resfiles, tc.pkg, err) 312 } 313 if !resourcesEqual(got, tc.want) { 314 t.Errorf("ParseAllContents(%v, %v) = {%v}, want {%v}", tc.resfiles, tc.pkg, got, tc.want) 315 } 316 } 317} 318 319// createResFile creates filename with the testdata as the base 320func createResFile(filename string) string { 321 fullPath := testdata + filename 322 resFilePath, err := runfilelocation.Find(fullPath) 323 if err != nil { 324 log.Fatalf("Could not find the runfile at %v", resFilePath) 325 } 326 return resFilePath 327} 328 329// createResfiles creates filenames with the testdata as the base 330func createResfiles(filenames []string) []string { 331 var resfiles []string 332 for _, filename := range filenames { 333 resfiles = append(resfiles, createResFile(filename)) 334 } 335 return resfiles 336} 337 338func getAllContents(t *testing.T, paths []string) [][]byte { 339 var allContents [][]byte 340 for _, path := range paths { 341 contents, err := os.ReadFile(path) 342 if err != nil { 343 t.Errorf("cannot read file %v: %v", path, err) 344 } 345 allContents = append(allContents, contents) 346 } 347 return allContents 348} 349 350// createResources creates rdpb.Resources with package name pkg and resources {names[i], resource[i]} 351func createResources(pkg string, resources []rdpb.Resource_Type, names []string) *rdpb.Resources { 352 rscs := &rdpb.Resources{ 353 Pkg: pkg, 354 } 355 for i := 0; i < len(names); i++ { 356 r := &rdpb.Resource{Name: names[i], ResourceType: resources[i]} 357 rscs.Resource = append(rscs.Resource, r) 358 } 359 return rscs 360} 361 362// resourcesEqual checks if the two resources have the same package names and resources 363func resourcesEqual(rscs1 *rdpb.Resources, rscs2 *rdpb.Resources) bool { 364 return rscs1.Pkg == rscs2.Pkg && cmp.Equal(createResourcesMap(rscs1), createResourcesMap(rscs2)) 365} 366 367// createResourcesMap creates a map of resources contained in rscs that maps the rdpb.Resource_Type to the names and the number of times the name appears. 368func createResourcesMap(rscs *rdpb.Resources) map[rdpb.Resource_Type]map[string]int { 369 m := make(map[rdpb.Resource_Type]map[string]int) 370 for _, r := range rscs.Resource { 371 if _, ok := m[r.GetResourceType()]; !ok { 372 m[r.GetResourceType()] = make(map[string]int) 373 m[r.GetResourceType()][r.GetName()] = 1 374 } else if _, ok := m[r.GetResourceType()][r.GetName()]; !ok { 375 m[r.GetResourceType()][r.GetName()] = 1 376 } else { 377 m[r.GetResourceType()][r.GetName()]++ 378 } 379 } 380 return m 381} 382