blob: fffd1b9afa3119d1bf1798d47d28b892c6fe691b [file] [log] [blame]
// Copyright 2022 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package finalrjar generates a valid final R.jar.
package finalrjar
import (
"bytes"
"sort"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
type fakeFile struct {
reader *strings.Reader
}
func (f fakeFile) Read(b []byte) (int, error) {
return f.reader.Read(b)
}
func (f fakeFile) Close() error {
return nil
}
func TestGetIds(t *testing.T) {
tests := []struct {
name string
rtxtFiles []*strings.Reader
expectedResources []*resource
}{
{
name: "one R.txt",
rtxtFiles: []*strings.Reader{
strings.NewReader(
`int anim abc_fade_in 0
int anim abc_fade_out 0
int attr actionBarDivider 0
int bool abc_action_bar_embed_tabs 0
int color abc_background_cache_hint_selector_material_dark 0
int[] color abc_background_cache_hint_selector_material_light 0
int color abc_btn_colored_borderless_text_material 0
int dimen tooltip_y_offset_non_touch 0
int dimen $avd_hide_password__0 0
int[] dimen tooltip_y_offset_touch 0
int drawable abc_ab_share_pack_mtrl_alpha 0`),
},
expectedResources: []*resource{
&resource{ID: "abc_ab_share_pack_mtrl_alpha", resType: "drawable", varType: "int"},
&resource{ID: "abc_action_bar_embed_tabs", resType: "bool", varType: "int"},
&resource{ID: "abc_background_cache_hint_selector_material_dark", resType: "color", varType: "int"},
&resource{ID: "abc_background_cache_hint_selector_material_light", resType: "color", varType: "int[]"},
&resource{ID: "abc_btn_colored_borderless_text_material", resType: "color", varType: "int"},
&resource{ID: "abc_fade_in", resType: "anim", varType: "int"},
&resource{ID: "abc_fade_out", resType: "anim", varType: "int"},
&resource{ID: "actionBarDivider", resType: "attr", varType: "int"},
&resource{ID: "tooltip_y_offset_non_touch", resType: "dimen", varType: "int"},
&resource{ID: "tooltip_y_offset_touch", resType: "dimen", varType: "int[]"},
},
},
{
name: "multiple R.txt files",
rtxtFiles: []*strings.Reader{
strings.NewReader(
`int styleable toolbar_logo 0
int[] style widget_appcompat_dark 0`),
strings.NewReader(
`int layout custom_dialog 0
int interpolator btn_checkbox 0`),
strings.NewReader(
`int id view_tree 0
int integer cancel_button_image_alpha 0`),
},
expectedResources: []*resource{
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
&resource{ID: "custom_dialog", resType: "layout", varType: "int"},
&resource{ID: "toolbar_logo", resType: "styleable", varType: "int"},
&resource{ID: "view_tree", resType: "id", varType: "int"},
&resource{ID: "widget_appcompat_dark", resType: "style", varType: "int[]"},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
rtxts := make([]rtxtFile, 0, len(tc.rtxtFiles))
for _, f := range tc.rtxtFiles {
file := fakeFile{reader: f}
file.reader.Seek(0, 0)
rtxts = append(rtxts, file)
}
resC := getIds(rtxts)
receivedResources := make([]*resource, 0)
for res := range resC {
receivedResources = append(receivedResources, res)
}
sort.Slice(receivedResources, func(i, j int) bool {
return receivedResources[i].ID < receivedResources[j].ID
})
if diff := cmp.Diff(tc.expectedResources, receivedResources, cmp.AllowUnexported(resource{})); diff != "" {
t.Errorf("getIds(%v) returned diff (-want, +got):\n%v", rtxts, diff)
}
})
}
}
func TestSortResByType(t *testing.T) {
tests := []struct {
name string
resources []*resource
expectedMap map[string][]*resource
}{
{
name: "simple list of resources",
resources: []*resource{
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
&resource{ID: "custom_dialog", resType: "id", varType: "int"},
&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
&resource{ID: "view_tree", resType: "id", varType: "int"},
&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
},
expectedMap: map[string][]*resource{
"interpolator": []*resource{
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
},
"integer": []*resource{
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
},
"id": []*resource{
&resource{ID: "custom_dialog", resType: "id", varType: "int"},
&resource{ID: "view_tree", resType: "id", varType: "int"},
},
"layout": []*resource{
&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
},
},
},
{
name: "list of resources with duplicates",
resources: []*resource{
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
&resource{ID: "custom_dialog", resType: "id", varType: "int"},
&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
&resource{ID: "toolbar_logo", resType: "attr", varType: "int"},
&resource{ID: "view_tree", resType: "id", varType: "int"},
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
},
expectedMap: map[string][]*resource{
"attr": []*resource{
&resource{ID: "toolbar_logo", resType: "attr", varType: "int"},
},
"interpolator": []*resource{
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
},
"integer": []*resource{
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
},
"id": []*resource{
&resource{ID: "custom_dialog", resType: "id", varType: "int"},
&resource{ID: "view_tree", resType: "id", varType: "int"},
},
"layout": []*resource{
&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
resC := make(chan *resource)
go func() {
for _, res := range tc.resources {
resC <- res
}
close(resC)
}()
resMap := groupResByType(resC)
if diff := cmp.Diff(tc.expectedMap, resMap, cmp.AllowUnexported(resource{})); diff != "" {
t.Errorf("groupResByType(%v) returned diff (-want, +got):\n%v", tc.resources, diff)
}
})
}
}
func TestWriteRJavas(t *testing.T) {
tests := []struct {
name string
resMap map[string][]*resource
pkg string
rootPackage string
expectedRJava string
expectedRootRJava string
}{
{
name: "simple map of resources",
resMap: map[string][]*resource{
"interpolator": []*resource{
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
},
"integer": []*resource{
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
},
"id": []*resource{
&resource{ID: "view_tree", resType: "id", varType: "int"},
&resource{ID: "custom_dialog", resType: "id", varType: "int"},
},
"layout": []*resource{
&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
},
},
pkg: "com.google.android.apps.sample",
rootPackage: "mi.rjava",
expectedRJava: `package com.google.android.apps.sample;
public class R {
public static class id {
public static final int custom_dialog=mi.rjava.R.id.custom_dialog;
public static final int view_tree=mi.rjava.R.id.view_tree;
}
public static class integer {
public static final int cancel_button_image_alpha=mi.rjava.R.integer.cancel_button_image_alpha;
}
public static class interpolator {
public static final int btn_checkbox=mi.rjava.R.interpolator.btn_checkbox;
public static final int toolbar_logo=mi.rjava.R.interpolator.toolbar_logo;
}
public static class layout {
public static final int[] widget_appcompat_dark=mi.rjava.R.layout.widget_appcompat_dark;
}
}
`,
expectedRootRJava: `package mi.rjava;
public class R {
public static class id {
public static int custom_dialog=0;
public static int view_tree=0;
}
public static class integer {
public static int cancel_button_image_alpha=0;
}
public static class interpolator {
public static int btn_checkbox=0;
public static int toolbar_logo=0;
}
public static class layout {
public static int[] widget_appcompat_dark=null;
}
}
`,
},
{
name: "with empty class",
resMap: map[string][]*resource{
"interpolator": []*resource{
&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
},
"integer": []*resource{
&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
},
"layout": []*resource{
&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
},
},
pkg: "com.google.android.apps.empty",
rootPackage: "mi.rjava",
expectedRJava: `package com.google.android.apps.empty;
public class R {
public static class integer {
public static final int cancel_button_image_alpha=mi.rjava.R.integer.cancel_button_image_alpha;
}
public static class interpolator {
public static final int btn_checkbox=mi.rjava.R.interpolator.btn_checkbox;
public static final int toolbar_logo=mi.rjava.R.interpolator.toolbar_logo;
}
public static class layout {
public static final int[] widget_appcompat_dark=mi.rjava.R.layout.widget_appcompat_dark;
}
}
`,
expectedRootRJava: `package mi.rjava;
public class R {
public static class integer {
public static int cancel_button_image_alpha=0;
}
public static class interpolator {
public static int btn_checkbox=0;
public static int toolbar_logo=0;
}
public static class layout {
public static int[] widget_appcompat_dark=null;
}
}
`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
var rJavaBuffer bytes.Buffer
var rootRJavaBuffer bytes.Buffer
if err := writeRJavas(&rJavaBuffer, &rootRJavaBuffer, tc.resMap, tc.pkg, tc.rootPackage); err != nil {
t.Fatalf("writeRJavas(%v, %s, %s) unexpected error: %v", tc.resMap, tc.pkg, tc.rootPackage, err)
}
if diff := cmp.Diff(tc.expectedRJava, rJavaBuffer.String()); diff != "" {
t.Errorf("writeRJavas(%v, %s, %s) returned diff for R.java (-want, +got):\n%v", tc.resMap, tc.pkg, tc.rootPackage, diff)
}
if diff := cmp.Diff(tc.expectedRootRJava, rootRJavaBuffer.String()); diff != "" {
t.Errorf("writeRJavas(%v, %s, %s) returned diff for root R.java(-want, +got):\n%v", tc.resMap, tc.pkg, tc.rootPackage, diff)
}
})
}
}
func TestHasReservedKeywords(t *testing.T) {
tests := []struct {
name string
pkg string
expected bool
}{
{
name: "valid package",
pkg: "com.google.android.apps.sampleapp.lib",
expected: false,
},
{
name: "valid package",
pkg: "com.google.android.static.sampleapp.lib",
expected: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
pkgParts := strings.Split(tc.pkg, ".")
invalid := hasJavaReservedWord(pkgParts)
if invalid != tc.expected {
t.Errorf("hasJavaReservedWord(%v) returned %v, want %v", pkgParts, invalid, tc.expected)
}
})
}
}