| // 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) |
| } |
| }) |
| } |
| |
| } |