github.com/TimaSlipko/gomobile@v1.0.8/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 "github.com/TimaSlipko/gomobile/internal/sdkpath" 21 ) 22 23 func init() { 24 skipSynthesize = true 25 } 26 27 func printrecurse(t *testing.T, pl *Pool, el *Element, ws string) { 28 29 t.Logf("%s+elem:ns(%v) name(%s)", ws, el.NS, el.Name.Resolve(pl)) 30 31 for _, attr := range el.attrs { 32 ns := "" 33 if attr.NS != math.MaxUint32 { 34 ns = pl.strings[int(attr.NS)] 35 nss := strings.Split(ns, "/") 36 ns = nss[len(nss)-1] 37 } 38 39 val := "" 40 if attr.RawValue != NoEntry { 41 val = pl.strings[int(attr.RawValue)] 42 } else { 43 switch attr.TypedValue.Type { 44 case DataIntDec: 45 val = fmt.Sprintf("%v", attr.TypedValue.Value) 46 case DataIntBool: 47 val = fmt.Sprintf("%v", attr.TypedValue.Value == 0xFFFFFFFF) 48 default: 49 val = fmt.Sprintf("0x%08X", attr.TypedValue.Value) 50 } 51 } 52 dt := attr.TypedValue.Type 53 54 t.Logf("%s|attr:ns(%v) name(%s) val(%s) valtyp(%s)\n", ws, ns, pl.strings[int(attr.Name)], val, dt) 55 } 56 t.Logf("\n") 57 for _, e := range el.Children { 58 printrecurse(t, pl, e, ws+" ") 59 } 60 } 61 62 func compareBytes(a, b []byte) error { 63 if bytes.Equal(a, b) { 64 return nil 65 } 66 buf := new(bytes.Buffer) 67 x, y := len(a), len(b) 68 if x != y { 69 fmt.Fprintf(buf, "byte length does not match, have %v, want %v\n", y, x) 70 } 71 if x > y { 72 x, y = y, x 73 } 74 mismatch := false 75 for i := 0; i < x; i++ { 76 if mismatch = a[i] != b[i]; mismatch { 77 fmt.Fprintf(buf, "first byte mismatch at %v\n", i) 78 break 79 } 80 } 81 if mismatch { 82 // print out a reasonable amount of data to help identify issues 83 truncate := x > 3300 84 if truncate { 85 x = 3300 86 } 87 buf.WriteString(" HAVE WANT\n") 88 for i := 0; i < x; i += 4 { 89 he, we := 4, 4 90 if i+he >= x { 91 he = x - i 92 } 93 if i+we >= y { 94 we = y - i 95 } 96 notequal := "" 97 if !bytes.Equal(b[i:i+he], a[i:i+we]) { 98 notequal = "***" 99 } 100 fmt.Fprintf(buf, "%3v | % X % X %s\n", i, b[i:i+he], a[i:i+we], notequal) 101 } 102 if truncate { 103 fmt.Fprint(buf, "... output truncated.\n") 104 } 105 } 106 return fmt.Errorf(buf.String()) 107 } 108 109 func TestBootstrap(t *testing.T) { 110 bin, err := ioutil.ReadFile("testdata/bootstrap.bin") 111 if err != nil { 112 log.Fatal(err) 113 } 114 115 // unmarshal binary xml and store byte indices of decoded resources. 116 debugIndices := make(map[encoding.BinaryMarshaler]int) 117 trackUnmarshal := func(buf []byte) (*XML, error) { 118 bx := new(XML) 119 if err := (&bx.chunkHeader).UnmarshalBinary(buf); err != nil { 120 return nil, err 121 } 122 buf = buf[8:] 123 debugIndex := 8 124 for len(buf) > 0 { 125 k, err := bx.unmarshalBinaryKind(buf) 126 if err != nil { 127 return nil, err 128 } 129 debugIndices[k.(encoding.BinaryMarshaler)] = debugIndex 130 debugIndex += k.size() 131 buf = buf[k.size():] 132 } 133 return bx, nil 134 } 135 136 checkMarshal := func(res encoding.BinaryMarshaler, bsize int) { 137 b, err := res.MarshalBinary() 138 if err != nil { 139 t.Error(err) 140 } 141 idx := debugIndices[res] 142 a := bin[idx : idx+bsize] 143 if !bytes.Equal(a, b) { 144 x, y := len(a), len(b) 145 if x != y { 146 t.Errorf("%v: %T: byte length does not match, have %v, want %v", idx, res, y, x) 147 } 148 if x > y { 149 x, y = y, x 150 } 151 mismatch := false 152 for i := 0; i < x; i++ { 153 if mismatch = a[i] != b[i]; mismatch { 154 t.Errorf("%v: %T: first byte mismatch at %v of %v", idx, res, i, bsize) 155 break 156 } 157 } 158 if mismatch { 159 // print out a reasonable amount of data to help identify issues 160 truncate := x > 1300 161 if truncate { 162 x = 1300 163 } 164 t.Log(" HAVE WANT") 165 for i := 0; i < x; i += 4 { 166 he, we := 4, 4 167 if i+he >= x { 168 he = x - i 169 } 170 if i+we >= y { 171 we = y - i 172 } 173 t.Logf("%3v | % X % X\n", i, b[i:i+he], a[i:i+we]) 174 } 175 if truncate { 176 t.Log("... output truncated.") 177 } 178 } 179 } 180 } 181 182 bxml, err := trackUnmarshal(bin) 183 if err != nil { 184 t.Fatal(err) 185 } 186 187 for i, x := range bxml.Pool.strings { 188 t.Logf("Pool(%v): %q\n", i, x) 189 } 190 191 for _, e := range bxml.Children { 192 printrecurse(t, bxml.Pool, e, "") 193 } 194 195 checkMarshal(&bxml.chunkHeader, int(bxml.headerByteSize)) 196 checkMarshal(bxml.Pool, bxml.Pool.size()) 197 checkMarshal(bxml.Map, bxml.Map.size()) 198 checkMarshal(bxml.Namespace, bxml.Namespace.size()) 199 200 for el := range bxml.iterElements() { 201 checkMarshal(el, el.size()) 202 checkMarshal(el.end, el.end.size()) 203 } 204 205 checkMarshal(bxml.Namespace.end, bxml.Namespace.end.size()) 206 checkMarshal(bxml, bxml.size()) 207 } 208 209 func TestEncode(t *testing.T) { 210 f, err := os.Open("testdata/bootstrap.xml") 211 if err != nil { 212 t.Fatal(err) 213 } 214 215 bx, err := UnmarshalXML(f, false) 216 if err != nil { 217 t.Fatal(err) 218 } 219 220 bin, err := ioutil.ReadFile("testdata/bootstrap.bin") 221 if err != nil { 222 log.Fatal(err) 223 } 224 bxml := new(XML) 225 if err := bxml.UnmarshalBinary(bin); err != nil { 226 t.Fatal(err) 227 } 228 229 if err := compareStrings(t, bxml.Pool.strings, bx.Pool.strings); err != nil { 230 t.Error(err) 231 } 232 233 if err := compareUint32s(t, rtou(bxml.Map.rs), rtou(bx.Map.rs)); err != nil { 234 t.Error(err) 235 } 236 237 if err := compareNamespaces(bx.Namespace, bxml.Namespace); err != nil { 238 t.Error(err) 239 } 240 241 if err := compareElements(bx, bxml); err != nil { 242 t.Error(err) 243 } 244 245 // Current output byte-for-byte of pkg binres is close, but not exact, to output of aapt. 246 // The current exceptions to this are as follows: 247 // * sort order of certain attributes 248 // * typed value of minSdkVersion 249 // The below check will produce an error, listing differences in the byte output of each. 250 // have, err := bx.MarshalBinary() 251 // if err != nil { 252 // t.Fatal(err) 253 // } 254 // if err := compareBytes(bin, have); err != nil { 255 // t.Fatal(err) 256 // } 257 } 258 259 func TestRawValueByName(t *testing.T) { 260 f, err := os.Open("testdata/bootstrap.xml") 261 if err != nil { 262 t.Fatal(err) 263 } 264 265 bx, err := UnmarshalXML(f, false) 266 if err != nil { 267 t.Fatal(err) 268 } 269 270 pkgname, err := bx.RawValueByName("manifest", xml.Name{Local: "package"}) 271 if want := "com.zentus.balloon"; err != nil || pkgname != want { 272 t.Fatalf("have (%q, %v), want (%q, nil)", pkgname, err, want) 273 } 274 } 275 276 type byAttrName []*Attribute 277 278 func (a byAttrName) Len() int { return len(a) } 279 func (a byAttrName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 280 func (a byAttrName) Less(i, j int) bool { return a[i].Name < a[j].Name } 281 282 func compareElements(have, want *XML) error { 283 h, w := have.iterElements(), want.iterElements() 284 buf := new(bytes.Buffer) 285 for { 286 a, b := <-h, <-w 287 if a == nil || b == nil { 288 break 289 } 290 if a.Name.Resolve(have.Pool) == "uses-sdk" { 291 a = <-h // discard uses-sdk token from tests since it's synthesized internally 292 } 293 if a.NS != b.NS || 294 a.Name != b.Name { 295 return fmt.Errorf("elements don't match, have %+v, want %+v", a, b) 296 } 297 if a.end.NS != b.end.NS || 298 a.end.Name != b.end.Name { 299 return fmt.Errorf("element ends don't match, have %+v, want %+v", a.end, b.end) 300 } 301 if len(a.attrs) != len(b.attrs) { 302 return fmt.Errorf("element attribute lengths don't match, have %v, want %v", len(a.attrs), len(b.attrs)) 303 } 304 305 // discards order of aapt and binres as some sorting details of apt have eluded this package but do not 306 // affect final output from functioning correctly 307 sort.Sort(byAttrName(a.attrs)) 308 sort.Sort(byAttrName(b.attrs)) 309 310 for i, attr := range a.attrs { 311 bttr := b.attrs[i] 312 if attr.NS != bttr.NS || 313 attr.Name != bttr.Name || 314 attr.RawValue != bttr.RawValue || 315 attr.TypedValue.Type != bttr.TypedValue.Type || 316 attr.TypedValue.Value != bttr.TypedValue.Value { 317 // single exception to check for minSdkVersion which has peculiar output from aapt 318 // but following same format of all other like-types appears to work correctly. 319 // BUG(dskinner) this check is brittle as it will skip over any attribute in 320 // bootstrap.xml that has value == MinSDK. 321 if attr.TypedValue.Value == MinSDK { 322 continue 323 } 324 fmt.Fprintf(buf, "attrs don't match\nhave: %+v\nwant: %+v\n", attr, bttr) 325 } 326 } 327 if buf.Len() > 0 { 328 buf.WriteString("-------------\n") 329 } 330 } 331 if buf.Len() > 0 { 332 return fmt.Errorf(buf.String()) 333 } 334 return nil 335 } 336 337 func compareNamespaces(have, want *Namespace) error { 338 if have == nil || want == nil || 339 have.LineNumber != want.LineNumber || 340 have.Comment != want.Comment || 341 have.prefix != want.prefix || 342 have.uri != want.uri { 343 return fmt.Errorf("namespaces don't match, have %+v, want %+v", have, want) 344 } 345 if have.end != nil || want.end != nil { 346 return compareNamespaces(have.end, want.end) 347 } 348 return nil 349 } 350 351 func rtou(a []TableRef) (b []uint32) { 352 for _, x := range a { 353 b = append(b, uint32(x)) 354 } 355 return 356 } 357 358 func compareUint32s(t *testing.T, a, b []uint32) error { 359 var err error 360 if len(a) != len(b) { 361 err = fmt.Errorf("lengths do not match") 362 } 363 364 n := len(a) 365 if n < len(b) { 366 n = len(b) 367 } 368 369 var buf bytes.Buffer 370 buf.WriteString("a.Map.rs b.Map.rs\n") 371 for i := 0; i < n; i++ { 372 var c, d string 373 if i < len(a) { 374 c = fmt.Sprintf("%0#8x ", a[i]) 375 } else { 376 c = "__________ " 377 } 378 if i < len(b) { 379 d = fmt.Sprintf("%0#8x ", b[i]) 380 } else { 381 d = "__________ " 382 } 383 if err == nil && c != d { 384 err = fmt.Errorf("has missing/incorrect values") 385 } 386 buf.WriteString(c + " " + d + "\n") 387 } 388 389 if err != nil { 390 err = fmt.Errorf("%s\n%s", err, buf.String()) 391 } 392 393 return err 394 } 395 396 func compareStrings(t *testing.T, a, b []string) error { 397 var err error 398 if len(a) != len(b) { 399 err = fmt.Errorf("lengths do not match") 400 } 401 402 buf := new(bytes.Buffer) 403 404 for i, x := range a { 405 v := "__" 406 for j, y := range b { 407 if x == y { 408 v = fmt.Sprintf("%2v", j) 409 break 410 } 411 } 412 if err == nil && v == "__" { 413 if !strings.HasPrefix(x, "4.1.") { 414 // as of the time of this writing, the current version of build tools being targeted 415 // reports 4.1.2-1425332. 416 // 417 // TODO this check has the potential to hide real errors but can be fixed once more 418 // of the xml document is unmarshalled and XML can be queried to assure this is related 419 // to platformBuildVersionName. 420 err = fmt.Errorf("has missing/incorrect values") 421 } 422 } 423 fmt.Fprintf(buf, "Pool(%2v, %s) %q\n", i, v, x) 424 } 425 426 contains := func(xs []string, a string) bool { 427 for _, x := range xs { 428 if x == a { 429 return true 430 } 431 } 432 return false 433 } 434 435 if err != nil { 436 buf.WriteString("\n## only in var a\n") 437 for i, x := range a { 438 if !contains(b, x) { 439 fmt.Fprintf(buf, "Pool(%2v) %q\n", i, x) 440 } 441 } 442 443 buf.WriteString("\n## only in var b\n") 444 for i, x := range b { 445 if !contains(a, x) { 446 fmt.Fprintf(buf, "Pool(%2v) %q\n", i, x) 447 } 448 } 449 } 450 451 if err != nil { 452 err = fmt.Errorf("%s\n%s", err, buf.String()) 453 } 454 455 return err 456 } 457 458 func TestOpenTable(t *testing.T) { 459 if _, err := sdkpath.AndroidHome(); err != nil { 460 t.Skipf("Could not locate Android SDK: %v", err) 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 // Config size can differ after serialization due to the loss of extended fields 581 // during reserialization, but the fixed portions of the Type header must not change. 582 if uint32(typ.headerByteSize)-typ.config.size != uint32(xtyp.headerByteSize)-uint32(xtyp.config.size) { 583 t.Fatal("fixed size header portions don't match") 584 } 585 if len(typ.indices) != len(xtyp.indices) { 586 t.Fatal("typ.indices length don't match") 587 } 588 for k, index := range typ.indices { 589 xindex := xtyp.indices[k] 590 if index != xindex { 591 t.Errorf("type index doesn't match at %v, have %v, want %v", k, xindex, index) 592 } 593 } 594 if len(typ.entries) != len(xtyp.entries) { 595 t.Fatal("typ.entries lengths don't match") 596 } 597 for k, nt := range typ.entries { 598 xnt := xtyp.entries[k] 599 if nt == nil { 600 if xnt != nil { 601 t.Fatal("nt is nil but xnt is not") 602 } 603 continue 604 } 605 if nt.size != xnt.size { 606 t.Fatal("entry.size doesn't match") 607 } 608 if nt.flags != xnt.flags { 609 t.Fatal("entry.flags don't match") 610 } 611 if nt.key != xnt.key { 612 t.Fatal("entry.key doesn't match") 613 } 614 615 if nt.parent != xnt.parent { 616 t.Fatal("entry.parent doesn't match") 617 } 618 if nt.count != xnt.count { 619 t.Fatal("entry.count doesn't match") 620 } 621 for l, val := range nt.values { 622 xval := xnt.values[l] 623 if val.name != xval.name { 624 t.Fatal("value.name doesn't match") 625 } 626 } 627 } 628 } 629 } 630 } 631 632 func checkResources(t *testing.T) { 633 t.Helper() 634 if _, err := sdkpath.AndroidHome(); err != nil { 635 t.Skip("Could not locate Android SDK") 636 } 637 rscPath, err := apiResourcesPath() 638 if err != nil { 639 t.Skipf("failed to find resources: %v", err) 640 } 641 if _, err := os.Stat(rscPath); err != nil { 642 t.Skipf("failed to find resources: %v", err) 643 } 644 } 645 646 func BenchmarkTableRefByName(b *testing.B) { 647 if _, err := sdkpath.AndroidHome(); err != nil { 648 b.Fatal("Could not locate Android SDK") 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 }