xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/finalrjar/finalrjar_test.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1// Copyright 2022 The Bazel Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// Package finalrjar generates a valid final R.jar.
16package finalrjar
17
18import (
19	"bytes"
20	"sort"
21	"strings"
22	"testing"
23
24	"github.com/google/go-cmp/cmp"
25)
26
27type fakeFile struct {
28	reader *strings.Reader
29}
30
31func (f fakeFile) Read(b []byte) (int, error) {
32	return f.reader.Read(b)
33}
34
35func (f fakeFile) Close() error {
36	return nil
37}
38
39func TestGetIds(t *testing.T) {
40	tests := []struct {
41		name              string
42		rtxtFiles         []*strings.Reader
43		expectedResources []*resource
44	}{
45		{
46			name: "one R.txt",
47			rtxtFiles: []*strings.Reader{
48				strings.NewReader(
49					`int anim abc_fade_in 0
50int anim abc_fade_out 0
51int attr actionBarDivider 0
52int bool abc_action_bar_embed_tabs 0
53int color abc_background_cache_hint_selector_material_dark 0
54int[] color abc_background_cache_hint_selector_material_light 0
55int color abc_btn_colored_borderless_text_material 0
56int dimen tooltip_y_offset_non_touch 0
57int dimen $avd_hide_password__0 0
58int[] dimen tooltip_y_offset_touch 0
59int drawable abc_ab_share_pack_mtrl_alpha 0`),
60			},
61			expectedResources: []*resource{
62				&resource{ID: "abc_ab_share_pack_mtrl_alpha", resType: "drawable", varType: "int"},
63				&resource{ID: "abc_action_bar_embed_tabs", resType: "bool", varType: "int"},
64				&resource{ID: "abc_background_cache_hint_selector_material_dark", resType: "color", varType: "int"},
65				&resource{ID: "abc_background_cache_hint_selector_material_light", resType: "color", varType: "int[]"},
66				&resource{ID: "abc_btn_colored_borderless_text_material", resType: "color", varType: "int"},
67				&resource{ID: "abc_fade_in", resType: "anim", varType: "int"},
68				&resource{ID: "abc_fade_out", resType: "anim", varType: "int"},
69				&resource{ID: "actionBarDivider", resType: "attr", varType: "int"},
70				&resource{ID: "tooltip_y_offset_non_touch", resType: "dimen", varType: "int"},
71				&resource{ID: "tooltip_y_offset_touch", resType: "dimen", varType: "int[]"},
72			},
73		},
74		{
75			name: "multiple R.txt files",
76			rtxtFiles: []*strings.Reader{
77				strings.NewReader(
78					`int styleable toolbar_logo 0
79int[] style widget_appcompat_dark 0`),
80				strings.NewReader(
81					`int layout custom_dialog 0
82int interpolator btn_checkbox 0`),
83				strings.NewReader(
84					`int id view_tree 0
85int integer cancel_button_image_alpha 0`),
86			},
87			expectedResources: []*resource{
88				&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
89				&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
90				&resource{ID: "custom_dialog", resType: "layout", varType: "int"},
91				&resource{ID: "toolbar_logo", resType: "styleable", varType: "int"},
92				&resource{ID: "view_tree", resType: "id", varType: "int"},
93				&resource{ID: "widget_appcompat_dark", resType: "style", varType: "int[]"},
94			},
95		},
96	}
97	for _, tc := range tests {
98		t.Run(tc.name, func(t *testing.T) {
99			rtxts := make([]rtxtFile, 0, len(tc.rtxtFiles))
100			for _, f := range tc.rtxtFiles {
101				file := fakeFile{reader: f}
102				file.reader.Seek(0, 0)
103				rtxts = append(rtxts, file)
104			}
105
106			resC := getIds(rtxts)
107			receivedResources := make([]*resource, 0)
108			for res := range resC {
109				receivedResources = append(receivedResources, res)
110			}
111			sort.Slice(receivedResources, func(i, j int) bool {
112				return receivedResources[i].ID < receivedResources[j].ID
113			})
114
115			if diff := cmp.Diff(tc.expectedResources, receivedResources, cmp.AllowUnexported(resource{})); diff != "" {
116				t.Errorf("getIds(%v) returned diff (-want, +got):\n%v", rtxts, diff)
117			}
118		})
119	}
120
121}
122
123func TestSortResByType(t *testing.T) {
124	tests := []struct {
125		name        string
126		resources   []*resource
127		expectedMap map[string][]*resource
128	}{
129		{
130			name: "simple list of resources",
131			resources: []*resource{
132				&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
133				&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
134				&resource{ID: "custom_dialog", resType: "id", varType: "int"},
135				&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
136				&resource{ID: "view_tree", resType: "id", varType: "int"},
137				&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
138			},
139			expectedMap: map[string][]*resource{
140				"interpolator": []*resource{
141					&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
142					&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
143				},
144				"integer": []*resource{
145					&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
146				},
147				"id": []*resource{
148					&resource{ID: "custom_dialog", resType: "id", varType: "int"},
149					&resource{ID: "view_tree", resType: "id", varType: "int"},
150				},
151				"layout": []*resource{
152					&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
153				},
154			},
155		},
156		{
157			name: "list of resources with duplicates",
158			resources: []*resource{
159				&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
160				&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
161				&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
162				&resource{ID: "custom_dialog", resType: "id", varType: "int"},
163				&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
164				&resource{ID: "toolbar_logo", resType: "attr", varType: "int"},
165				&resource{ID: "view_tree", resType: "id", varType: "int"},
166				&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
167				&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
168			},
169			expectedMap: map[string][]*resource{
170				"attr": []*resource{
171					&resource{ID: "toolbar_logo", resType: "attr", varType: "int"},
172				},
173				"interpolator": []*resource{
174					&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
175					&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
176				},
177				"integer": []*resource{
178					&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
179				},
180				"id": []*resource{
181					&resource{ID: "custom_dialog", resType: "id", varType: "int"},
182					&resource{ID: "view_tree", resType: "id", varType: "int"},
183				},
184				"layout": []*resource{
185					&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
186				},
187			},
188		},
189	}
190	for _, tc := range tests {
191		t.Run(tc.name, func(t *testing.T) {
192			resC := make(chan *resource)
193			go func() {
194				for _, res := range tc.resources {
195					resC <- res
196				}
197				close(resC)
198			}()
199			resMap := groupResByType(resC)
200
201			if diff := cmp.Diff(tc.expectedMap, resMap, cmp.AllowUnexported(resource{})); diff != "" {
202				t.Errorf("groupResByType(%v) returned diff (-want, +got):\n%v", tc.resources, diff)
203			}
204		})
205	}
206
207}
208
209func TestWriteRJavas(t *testing.T) {
210	tests := []struct {
211		name              string
212		resMap            map[string][]*resource
213		pkg               string
214		rootPackage       string
215		expectedRJava     string
216		expectedRootRJava string
217	}{
218		{
219			name: "simple map of resources",
220			resMap: map[string][]*resource{
221				"interpolator": []*resource{
222					&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
223					&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
224				},
225				"integer": []*resource{
226					&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
227				},
228				"id": []*resource{
229					&resource{ID: "view_tree", resType: "id", varType: "int"},
230					&resource{ID: "custom_dialog", resType: "id", varType: "int"},
231				},
232				"layout": []*resource{
233					&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
234				},
235			},
236			pkg:         "com.google.android.apps.sample",
237			rootPackage: "mi.rjava",
238			expectedRJava: `package com.google.android.apps.sample;
239public class R {
240  public static class id {
241    public static final int custom_dialog=mi.rjava.R.id.custom_dialog;
242    public static final int view_tree=mi.rjava.R.id.view_tree;
243  }
244  public static class integer {
245    public static final int cancel_button_image_alpha=mi.rjava.R.integer.cancel_button_image_alpha;
246  }
247  public static class interpolator {
248    public static final int btn_checkbox=mi.rjava.R.interpolator.btn_checkbox;
249    public static final int toolbar_logo=mi.rjava.R.interpolator.toolbar_logo;
250  }
251  public static class layout {
252    public static final int[] widget_appcompat_dark=mi.rjava.R.layout.widget_appcompat_dark;
253  }
254}
255`,
256			expectedRootRJava: `package mi.rjava;
257public class R {
258  public static class id {
259    public static int custom_dialog=0;
260    public static int view_tree=0;
261  }
262  public static class integer {
263    public static int cancel_button_image_alpha=0;
264  }
265  public static class interpolator {
266    public static int btn_checkbox=0;
267    public static int toolbar_logo=0;
268  }
269  public static class layout {
270    public static int[] widget_appcompat_dark=null;
271  }
272}
273`,
274		},
275		{
276			name: "with empty class",
277			resMap: map[string][]*resource{
278				"interpolator": []*resource{
279					&resource{ID: "toolbar_logo", resType: "interpolator", varType: "int"},
280					&resource{ID: "btn_checkbox", resType: "interpolator", varType: "int"},
281				},
282				"integer": []*resource{
283					&resource{ID: "cancel_button_image_alpha", resType: "integer", varType: "int"},
284				},
285				"layout": []*resource{
286					&resource{ID: "widget_appcompat_dark", resType: "layout", varType: "int[]"},
287				},
288			},
289			pkg:         "com.google.android.apps.empty",
290			rootPackage: "mi.rjava",
291			expectedRJava: `package com.google.android.apps.empty;
292public class R {
293  public static class integer {
294    public static final int cancel_button_image_alpha=mi.rjava.R.integer.cancel_button_image_alpha;
295  }
296  public static class interpolator {
297    public static final int btn_checkbox=mi.rjava.R.interpolator.btn_checkbox;
298    public static final int toolbar_logo=mi.rjava.R.interpolator.toolbar_logo;
299  }
300  public static class layout {
301    public static final int[] widget_appcompat_dark=mi.rjava.R.layout.widget_appcompat_dark;
302  }
303}
304`,
305			expectedRootRJava: `package mi.rjava;
306public class R {
307  public static class integer {
308    public static int cancel_button_image_alpha=0;
309  }
310  public static class interpolator {
311    public static int btn_checkbox=0;
312    public static int toolbar_logo=0;
313  }
314  public static class layout {
315    public static int[] widget_appcompat_dark=null;
316  }
317}
318`,
319		},
320	}
321	for _, tc := range tests {
322		t.Run(tc.name, func(t *testing.T) {
323			var rJavaBuffer bytes.Buffer
324			var rootRJavaBuffer bytes.Buffer
325			if err := writeRJavas(&rJavaBuffer, &rootRJavaBuffer, tc.resMap, tc.pkg, tc.rootPackage); err != nil {
326				t.Fatalf("writeRJavas(%v, %s, %s) unexpected error: %v", tc.resMap, tc.pkg, tc.rootPackage, err)
327			}
328			if diff := cmp.Diff(tc.expectedRJava, rJavaBuffer.String()); diff != "" {
329				t.Errorf("writeRJavas(%v, %s, %s) returned diff for R.java (-want, +got):\n%v", tc.resMap, tc.pkg, tc.rootPackage, diff)
330			}
331			if diff := cmp.Diff(tc.expectedRootRJava, rootRJavaBuffer.String()); diff != "" {
332				t.Errorf("writeRJavas(%v, %s, %s) returned diff for root R.java(-want, +got):\n%v", tc.resMap, tc.pkg, tc.rootPackage, diff)
333			}
334		})
335	}
336
337}
338
339func TestHasReservedKeywords(t *testing.T) {
340	tests := []struct {
341		name     string
342		pkg      string
343		expected bool
344	}{
345		{
346			name:     "valid package",
347			pkg:      "com.google.android.apps.sampleapp.lib",
348			expected: false,
349		},
350		{
351			name:     "valid package",
352			pkg:      "com.google.android.static.sampleapp.lib",
353			expected: true,
354		},
355	}
356	for _, tc := range tests {
357		t.Run(tc.name, func(t *testing.T) {
358			pkgParts := strings.Split(tc.pkg, ".")
359			invalid := hasJavaReservedWord(pkgParts)
360			if invalid != tc.expected {
361				t.Errorf("hasJavaReservedWord(%v) returned %v, want %v", pkgParts, invalid, tc.expected)
362			}
363		})
364	}
365
366}
367