github.com/shranet/mobile@v0.0.0-20200814083559-5702cdcd481b/internal/binres/binres_test.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package binres 6 7 import ( 8 "bytes" 9 "encoding" 10 "encoding/xml" 11 "fmt" 12 "io/ioutil" 13 "log" 14 "math" 15 "os" 16 "sort" 17 "strings" 18 "testing" 19 ) 20 21 func init() { 22 skipSynthesize = true 23 } 24 25 func printrecurse(t *testing.T, pl *Pool, el *Element, ws string) { 26 27 t.Logf("%s+elem:ns(%v) name(%s)", ws, el.NS, el.Name.Resolve(pl)) 28 29 for _, attr := range el.attrs { 30 ns := "" 31 if attr.NS != math.MaxUint32 { 32 ns = pl.strings[int(attr.NS)] 33 nss := strings.Split(ns, "/") 34 ns = nss[len(nss)-1] 35 } 36 37 val := "" 38 if attr.RawValue != NoEntry { 39 val = pl.strings[int(attr.RawValue)] 40 } else { 41 switch attr.TypedValue.Type { 42 case DataIntDec: 43 val = fmt.Sprintf("%v", attr.TypedValue.Value) 44 case DataIntBool: 45 val = fmt.Sprintf("%v", attr.TypedValue.Value == 0xFFFFFFFF) 46 default: 47 val = fmt.Sprintf("0x%08X", attr.TypedValue.Value) 48 } 49 } 50 dt := attr.TypedValue.Type 51 52 t.Logf("%s|attr:ns(%v) name(%s) val(%s) valtyp(%s)\n", ws, ns, pl.strings[int(attr.Name)], val, dt) 53 } 54 t.Logf("\n") 55 for _, e := range el.Children { 56 printrecurse(t, pl, e, ws+" ") 57 } 58 } 59 60 func compareBytes(a, b []byte) error { 61 if bytes.Equal(a, b) { 62 return nil 63 } 64 buf := new(bytes.Buffer) 65 x, y := len(a), len(b) 66 if x != y { 67 fmt.Fprintf(buf, "byte length does not match, have %v, want %v\n", y, x) 68 } 69 if x > y { 70 x, y = y, x 71 } 72 mismatch := false 73 for i := 0; i < x; i++ { 74 if mismatch = a[i] != b[i]; mismatch { 75 fmt.Fprintf(buf, "first byte mismatch at %v\n", i) 76 break 77 } 78 } 79 if mismatch { 80 // print out a reasonable amount of data to help identify issues 81 truncate := x > 3300 82 if truncate { 83 x = 3300 84 } 85 buf.WriteString(" HAVE WANT\n") 86 for i := 0; i < x; i += 4 { 87 he, we := 4, 4 88 if i+he >= x { 89 he = x - i 90 } 91 if i+we >= y { 92 we = y - i 93 } 94 notequal := "" 95 if !bytes.Equal(b[i:i+he], a[i:i+we]) { 96 notequal = "***" 97 } 98 fmt.Fprintf(buf, "%3v | % X % X %s\n", i, b[i:i+he], a[i:i+we], notequal) 99 } 100 if truncate { 101 fmt.Fprint(buf, "... output truncated.\n") 102 } 103 } 104 return fmt.Errorf(buf.String()) 105 } 106 107 func TestBootstrap(t *testing.T) { 108 bin, err := ioutil.ReadFile("testdata/bootstrap.bin") 109 if err != nil { 110 log.Fatal(err) 111 } 112 113 // unmarshal binary xml and store byte indices of decoded resources. 114 debugIndices := make(map[encoding.BinaryMarshaler]int) 115 trackUnmarshal := func(buf []byte) (*XML, error) { 116 bx := new(XML) 117 if err := (&bx.chunkHeader).UnmarshalBinary(buf); err != nil { 118 return nil, err 119 } 120 buf = buf[8:] 121 debugIndex := 8 122 for len(buf) > 0 { 123 k, err := bx.unmarshalBinaryKind(buf) 124 if err != nil { 125 return nil, err 126 } 127 debugIndices[k.(encoding.BinaryMarshaler)] = debugIndex 128 debugIndex += k.size() 129 buf = buf[k.size():] 130 } 131 return bx, nil 132 } 133 134 checkMarshal := func(res encoding.BinaryMarshaler, bsize int) { 135 b, err := res.MarshalBinary() 136 if err != nil { 137 t.Error(err) 138 } 139 idx := debugIndices[res] 140 a := bin[idx : idx+bsize] 141 if !bytes.Equal(a, b) { 142 x, y := len(a), len(b) 143 if x != y { 144 t.Errorf("%v: %T: byte length does not match, have %v, want %v", idx, res, y, x) 145 } 146 if x > y { 147 x, y = y, x 148 } 149 mismatch := false 150 for i := 0; i < x; i++ { 151 if mismatch = a[i] != b[i]; mismatch { 152 t.Errorf("%v: %T: first byte mismatch at %v of %v", idx, res, i, bsize) 153 break 154 } 155 } 156 if mismatch { 157 // print out a reasonable amount of data to help identify issues 158 truncate := x > 1300 159 if truncate { 160 x = 1300 161 } 162 t.Log(" HAVE WANT") 163 for i := 0; i < x; i += 4 { 164 he, we := 4, 4 165 if i+he >= x { 166 he = x - i 167 } 168 if i+we >= y { 169 we = y - i 170 } 171 t.Logf("%3v | % X % X\n", i, b[i:i+he], a[i:i+we]) 172 } 173 if truncate { 174 t.Log("... output truncated.") 175 } 176 } 177 } 178 } 179 180 bxml, err := trackUnmarshal(bin) 181 if err != nil { 182 t.Fatal(err) 183 } 184 185 for i, x := range bxml.Pool.strings { 186 t.Logf("Pool(%v): %q\n", i, x) 187 } 188 189 for _, e := range bxml.Children { 190 printrecurse(t, bxml.Pool, e, "") 191 } 192 193 checkMarshal(&bxml.chunkHeader, int(bxml.headerByteSize)) 194 checkMarshal(bxml.Pool, bxml.Pool.size()) 195 checkMarshal(bxml.Map, bxml.Map.size()) 196 checkMarshal(bxml.Namespace, bxml.Namespace.size()) 197 198 for el := range bxml.iterElements() { 199 checkMarshal(el, el.size()) 200 checkMarshal(el.end, el.end.size()) 201 } 202 203 checkMarshal(bxml.Namespace.end, bxml.Namespace.end.size()) 204 checkMarshal(bxml, bxml.size()) 205 } 206 207 func TestEncode(t *testing.T) { 208 f, err := os.Open("testdata/bootstrap.xml") 209 if err != nil { 210 t.Fatal(err) 211 } 212 213 bx, err := UnmarshalXML(f, false) 214 if err != nil { 215 t.Fatal(err) 216 } 217 218 bin, err := ioutil.ReadFile("testdata/bootstrap.bin") 219 if err != nil { 220 log.Fatal(err) 221 } 222 bxml := new(XML) 223 if err := bxml.UnmarshalBinary(bin); err != nil { 224 t.Fatal(err) 225 } 226 227 if err := compareStrings(t, bxml.Pool.strings, bx.Pool.strings); err != nil { 228 t.Error(err) 229 } 230 231 if err := compareUint32s(t, rtou(bxml.Map.rs), rtou(bx.Map.rs)); err != nil { 232 t.Error(err) 233 } 234 235 if err := compareNamespaces(bx.Namespace, bxml.Namespace); err != nil { 236 t.Error(err) 237 } 238 239 if err := compareElements(bx, bxml); err != nil { 240 t.Error(err) 241 } 242 243 // Current output byte-for-byte of pkg binres is close, but not exact, to output of aapt. 244 // The current exceptions to this are as follows: 245 // * sort order of certain attributes 246 // * typed value of minSdkVersion 247 // The below check will produce an error, listing differences in the byte output of each. 248 // have, err := bx.MarshalBinary() 249 // if err != nil { 250 // t.Fatal(err) 251 // } 252 // if err := compareBytes(bin, have); err != nil { 253 // t.Fatal(err) 254 // } 255 } 256 257 func TestRawValueByName(t *testing.T) { 258 f, err := os.Open("testdata/bootstrap.xml") 259 if err != nil { 260 t.Fatal(err) 261 } 262 263 bx, err := UnmarshalXML(f, false) 264 if err != nil { 265 t.Fatal(err) 266 } 267 268 pkgname, err := bx.RawValueByName("manifest", xml.Name{Local: "package"}) 269 if want := "com.zentus.balloon"; err != nil || pkgname != want { 270 t.Fatalf("have (%q, %v), want (%q, nil)", pkgname, err, want) 271 } 272 } 273 274 type byAttrName []*Attribute 275 276 func (a byAttrName) Len() int { return len(a) } 277 func (a byAttrName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 278 func (a byAttrName) Less(i, j int) bool { return a[i].Name < a[j].Name } 279 280 func compareElements(have, want *XML) error { 281 h, w := have.iterElements(), want.iterElements() 282 buf := new(bytes.Buffer) 283 for { 284 a, b := <-h, <-w 285 if a == nil || b == nil { 286 break 287 } 288 if a.Name.Resolve(have.Pool) == "uses-sdk" { 289 a = <-h // discard uses-sdk token from tests since it's synthesized internally 290 } 291 if a.NS != b.NS || 292 a.Name != b.Name { 293 return fmt.Errorf("elements don't match, have %+v, want %+v", a, b) 294 } 295 if a.end.NS != b.end.NS || 296 a.end.Name != b.end.Name { 297 return fmt.Errorf("element ends don't match, have %+v, want %+v", a.end, b.end) 298 } 299 if len(a.attrs) != len(b.attrs) { 300 return fmt.Errorf("element attribute lengths don't match, have %v, want %v", len(a.attrs), len(b.attrs)) 301 } 302 303 // discards order of aapt and binres as some sorting details of apt have eluded this package but do not 304 // affect final output from functioning correctly 305 sort.Sort(byAttrName(a.attrs)) 306 sort.Sort(byAttrName(b.attrs)) 307 308 for i, attr := range a.attrs { 309 bttr := b.attrs[i] 310 if attr.NS != bttr.NS || 311 attr.Name != bttr.Name || 312 attr.RawValue != bttr.RawValue || 313 attr.TypedValue.Type != bttr.TypedValue.Type || 314 attr.TypedValue.Value != bttr.TypedValue.Value { 315 // single exception to check for minSdkVersion which has peculiar output from aapt 316 // but following same format of all other like-types appears to work correctly. 317 // BUG(dskinner) this check is brittle as it will skip over any attribute in 318 // bootstrap.xml that has value == MinSDK. 319 if attr.TypedValue.Value == MinSDK { 320 continue 321 } 322 fmt.Fprintf(buf, "attrs don't match\nhave: %+v\nwant: %+v\n", attr, bttr) 323 } 324 } 325 if buf.Len() > 0 { 326 buf.WriteString("-------------\n") 327 } 328 } 329 if buf.Len() > 0 { 330 return fmt.Errorf(buf.String()) 331 } 332 return nil 333 } 334 335 func compareNamespaces(have, want *Namespace) error { 336 if have == nil || want == nil || 337 have.LineNumber != want.LineNumber || 338 have.Comment != want.Comment || 339 have.prefix != want.prefix || 340 have.uri != want.uri { 341 return fmt.Errorf("namespaces don't match, have %+v, want %+v", have, want) 342 } 343 if have.end != nil || want.end != nil { 344 return compareNamespaces(have.end, want.end) 345 } 346 return nil 347 } 348 349 func rtou(a []TableRef) (b []uint32) { 350 for _, x := range a { 351 b = append(b, uint32(x)) 352 } 353 return 354 } 355 356 func compareUint32s(t *testing.T, a, b []uint32) error { 357 var err error 358 if len(a) != len(b) { 359 err = fmt.Errorf("lengths do not match") 360 } 361 362 n := len(a) 363 if n < len(b) { 364 n = len(b) 365 } 366 367 var buf bytes.Buffer 368 buf.WriteString("a.Map.rs b.Map.rs\n") 369 for i := 0; i < n; i++ { 370 var c, d string 371 if i < len(a) { 372 c = fmt.Sprintf("%0#8x ", a[i]) 373 } else { 374 c = "__________ " 375 } 376 if i < len(b) { 377 d = fmt.Sprintf("%0#8x ", b[i]) 378 } else { 379 d = "__________ " 380 } 381 if err == nil && c != d { 382 err = fmt.Errorf("has missing/incorrect values") 383 } 384 buf.WriteString(c + " " + d + "\n") 385 } 386 387 if err != nil { 388 err = fmt.Errorf("%s\n%s", err, buf.String()) 389 } 390 391 return err 392 } 393 394 func compareStrings(t *testing.T, a, b []string) error { 395 var err error 396 if len(a) != len(b) { 397 err = fmt.Errorf("lengths do not match") 398 } 399 400 buf := new(bytes.Buffer) 401 402 for i, x := range a { 403 v := "__" 404 for j, y := range b { 405 if x == y { 406 v = fmt.Sprintf("%2v", j) 407 break 408 } 409 } 410 if err == nil && v == "__" { 411 if !strings.HasPrefix(x, "4.0.") { 412 // as of the time of this writing, the current version of build tools being targetted 413 // reports 4.0.4-1406430. Previously, this was 4.0.3. This number is likely still due 414 // to change so only report error if 4.x incremented. 415 // 416 // TODO this check has the potential to hide real errors but can be fixed once more 417 // of the xml document is unmarshalled and XML can be queried to assure this is related 418 // to platformBuildVersionName. 419 err = fmt.Errorf("has missing/incorrect values") 420 } 421 } 422 fmt.Fprintf(buf, "Pool(%2v, %s) %q\n", i, v, x) 423 } 424 425 contains := func(xs []string, a string) bool { 426 for _, x := range xs { 427 if x == a { 428 return true 429 } 430 } 431 return false 432 } 433 434 if err != nil { 435 buf.WriteString("\n## only in var a\n") 436 for i, x := range a { 437 if !contains(b, x) { 438 fmt.Fprintf(buf, "Pool(%2v) %q\n", i, x) 439 } 440 } 441 442 buf.WriteString("\n## only in var b\n") 443 for i, x := range b { 444 if !contains(a, x) { 445 fmt.Fprintf(buf, "Pool(%2v) %q\n", i, x) 446 } 447 } 448 } 449 450 if err != nil { 451 err = fmt.Errorf("%s\n%s", err, buf.String()) 452 } 453 454 return err 455 } 456 457 func TestOpenTable(t *testing.T) { 458 sdkdir := os.Getenv("ANDROID_HOME") 459 if sdkdir == "" { 460 t.Skip("ANDROID_HOME env var not set") 461 } 462 tbl, err := OpenTable() 463 if err != nil { 464 t.Fatal(err) 465 } 466 if len(tbl.pkgs) == 0 { 467 t.Fatal("failed to decode any resource packages") 468 } 469 470 pkg := tbl.pkgs[0] 471 472 t.Log("package name:", pkg.name) 473 474 for i, x := range pkg.typePool.strings { 475 t.Logf("typePool[i=%v]: %s\n", i, x) 476 } 477 478 for i, spec := range pkg.specs { 479 t.Logf("spec[i=%v]: %v %q\n", i, spec.id, pkg.typePool.strings[spec.id-1]) 480 for j, typ := range spec.types { 481 t.Logf("\ttype[i=%v]: %v\n", j, typ.id) 482 for k, nt := range typ.entries { 483 if nt == nil { // NoEntry 484 continue 485 } 486 t.Logf("\t\tentry[i=%v]: %v %q\n", k, nt.key, pkg.keyPool.strings[nt.key]) 487 if k > 5 { 488 t.Logf("\t\t... truncating output") 489 break 490 } 491 } 492 } 493 } 494 } 495 496 func TestTableRefByName(t *testing.T) { 497 checkResources(t) 498 tbl, err := OpenSDKTable() 499 if err != nil { 500 t.Fatal(err) 501 } 502 if len(tbl.pkgs) == 0 { 503 t.Fatal("failed to decode any resource packages") 504 } 505 506 ref, err := tbl.RefByName("@android:style/Theme.NoTitleBar.Fullscreen") 507 if err != nil { 508 t.Fatal(err) 509 } 510 511 if want := uint32(0x01030007); uint32(ref) != want { 512 t.Fatalf("RefByName does not match expected result, have %0#8x, want %0#8x", ref, want) 513 } 514 } 515 516 func TestTableMarshal(t *testing.T) { 517 checkResources(t) 518 tbl, err := OpenSDKTable() 519 if err != nil { 520 t.Fatal(err) 521 } 522 523 bin, err := tbl.MarshalBinary() 524 if err != nil { 525 t.Fatal(err) 526 } 527 528 xtbl := new(Table) 529 if err := xtbl.UnmarshalBinary(bin); err != nil { 530 t.Fatal(err) 531 } 532 533 if len(tbl.pool.strings) != len(xtbl.pool.strings) { 534 t.Fatal("tbl.pool lengths don't match") 535 } 536 if len(tbl.pkgs) != len(xtbl.pkgs) { 537 t.Fatal("tbl.pkgs lengths don't match") 538 } 539 540 pkg, xpkg := tbl.pkgs[0], xtbl.pkgs[0] 541 if err := compareStrings(t, pkg.typePool.strings, xpkg.typePool.strings); err != nil { 542 t.Fatal(err) 543 } 544 if err := compareStrings(t, pkg.keyPool.strings, xpkg.keyPool.strings); err != nil { 545 t.Fatal(err) 546 } 547 548 if len(pkg.specs) != len(xpkg.specs) { 549 t.Fatal("pkg.specs lengths don't match") 550 } 551 552 for i, spec := range pkg.specs { 553 xspec := xpkg.specs[i] 554 if spec.id != xspec.id { 555 t.Fatal("spec.id doesn't match") 556 } 557 if spec.entryCount != xspec.entryCount { 558 t.Fatal("spec.entryCount doesn't match") 559 } 560 if len(spec.entries) != len(xspec.entries) { 561 t.Fatal("spec.entries lengths don't match") 562 } 563 for j, mask := range spec.entries { 564 xmask := xspec.entries[j] 565 if mask != xmask { 566 t.Fatal("entry mask doesn't match") 567 } 568 } 569 if len(spec.types) != len(xspec.types) { 570 t.Fatal("spec.types length don't match") 571 } 572 for j, typ := range spec.types { 573 xtyp := xspec.types[j] 574 if typ.id != xtyp.id { 575 t.Fatal("typ.id doesn't match") 576 } 577 if typ.entryCount != xtyp.entryCount { 578 t.Fatal("typ.entryCount doesn't match") 579 } 580 if typ.entriesStart != xtyp.entriesStart { 581 t.Fatal("typ.entriesStart doesn't match") 582 } 583 if len(typ.indices) != len(xtyp.indices) { 584 t.Fatal("typ.indices length don't match") 585 } 586 for k, index := range typ.indices { 587 xindex := xtyp.indices[k] 588 if index != xindex { 589 t.Errorf("type index doesn't match at %v, have %v, want %v", k, xindex, index) 590 } 591 } 592 if len(typ.entries) != len(xtyp.entries) { 593 t.Fatal("typ.entries lengths don't match") 594 } 595 for k, nt := range typ.entries { 596 xnt := xtyp.entries[k] 597 if nt == nil { 598 if xnt != nil { 599 t.Fatal("nt is nil but xnt is not") 600 } 601 continue 602 } 603 if nt.size != xnt.size { 604 t.Fatal("entry.size doesn't match") 605 } 606 if nt.flags != xnt.flags { 607 t.Fatal("entry.flags don't match") 608 } 609 if nt.key != xnt.key { 610 t.Fatal("entry.key doesn't match") 611 } 612 613 if nt.parent != xnt.parent { 614 t.Fatal("entry.parent doesn't match") 615 } 616 if nt.count != xnt.count { 617 t.Fatal("entry.count doesn't match") 618 } 619 for l, val := range nt.values { 620 xval := xnt.values[l] 621 if val.name != xval.name { 622 t.Fatal("value.name doesn't match") 623 } 624 } 625 } 626 } 627 } 628 } 629 630 func checkResources(t *testing.T) { 631 t.Helper() 632 sdkdir := os.Getenv("ANDROID_HOME") 633 if sdkdir == "" { 634 t.Skip("ANDROID_HOME env var not set") 635 } 636 rscPath, err := apiResourcesPath() 637 if err != nil { 638 t.Skipf("failed to find resources: %v", err) 639 } 640 if _, err := os.Stat(rscPath); err != nil { 641 t.Skipf("failed to find resources: %v", err) 642 } 643 } 644 645 func BenchmarkTableRefByName(b *testing.B) { 646 sdkdir := os.Getenv("ANDROID_HOME") 647 if sdkdir == "" { 648 b.Fatal("ANDROID_HOME env var not set") 649 } 650 651 b.ReportAllocs() 652 b.ResetTimer() 653 for n := 0; n < b.N; n++ { 654 tbl, err := OpenTable() 655 if err != nil { 656 b.Fatal(err) 657 } 658 _, err = tbl.RefByName("@android:style/Theme.NoTitleBar.Fullscreen") 659 if err != nil { 660 b.Fatal(err) 661 } 662 } 663 }