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  }