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  }